diff --git a/src/api/auth/index.ts b/src/api/auth/index.ts index 32b5b919..1fe51251 100644 --- a/src/api/auth/index.ts +++ b/src/api/auth/index.ts @@ -1,6 +1,7 @@ import { post } from '@/lib/axios'; import { env } from '@/lib/env'; import { + GoogleLoginRequest, LoginRequest, SendRequest, SignupRequest, @@ -41,9 +42,18 @@ const login = async (request: LoginRequest): Promise> => { return result; }; +const googleLogin = async (request: GoogleLoginRequest) => { + const response = await post>( + AUTH_API.GOOGLELOGIN, + request, + ); + return response.data; +}; + export const auth = { signUp, verifySend, verifyConfirm, login, + googleLogin, }; diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 32c32faa..d0148aa9 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,9 +1,14 @@ 'use client'; +import { Suspense } from 'react'; import Login from '@/components/feature/Login'; const page = () => { - return ; + return ( + }> + + + ); }; export default page; diff --git a/src/components/shared/Auth/LoginBody/index.tsx b/src/components/shared/Auth/LoginBody/index.tsx index ee4fe622..02282a22 100644 --- a/src/components/shared/Auth/LoginBody/index.tsx +++ b/src/components/shared/Auth/LoginBody/index.tsx @@ -1,10 +1,13 @@ 'use client'; -import { useState } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { env } from '@/lib/env'; import { zodResolver } from '@hookform/resolvers/zod'; import { SubmitHandler, useForm } from 'react-hook-form'; import { loginSchema } from '@/schema/authSchema'; import { LoginRequest } from '@/type/auth/authRequest'; +import { saveTokens } from '@/utils/saveTokens'; import Button from '@/components/common/Button/Button'; import Icon from '@/components/common/Icon'; import { Input } from '@/components/common/Input'; @@ -16,6 +19,7 @@ import { Subtitle2Black, } from '@/components/common/Typography'; import { BASE_ROUTES } from '@/constants/_navbar'; +import { usePostGoogleLogin } from '@/hooks/auth/usePostGoogleLogin'; import { usePostLogin } from '@/hooks/auth/usePostLogin'; import { useAuth } from '@/hooks/useAuth'; import useNavigate from '@/hooks/useNavigate'; @@ -24,12 +28,17 @@ import { useUserStore } from '@/stores/useUserStore'; const LoginBody = () => { const { navigate } = useNavigate(); - const [isShowPassword, setIsShowPassword] = useState(false); - const { mutate: loginMutate } = usePostLogin(); const { login } = useAuth(); + const searchParams = useSearchParams(); + + const [isShowPassword, setIsShowPassword] = useState(false); + const showToast = useToastStore((set) => set.showToast); const setUserInfo = useUserStore((set) => set.setUserInfo); + const { mutate: loginMutate } = usePostLogin(); + const { mutate: googleLoginMutate } = usePostGoogleLogin(); + const { register, handleSubmit, @@ -52,12 +61,40 @@ const LoginBody = () => { login(); }, onError: (error) => { - error.message; showToast(`${error.message} 로그인 실패`, 'warning', 1000); }, }); }; + const googleLoginHandler = () => { + navigate( + `https://accounts.google.com/o/oauth2/v2/auth?client_id=${env.GOOGLE_CLIENT_ID}&redirect_uri=${env.GOOGLE_REDIRECT_URL}&response_type=code&scope=email%20profile%20openid&access_type=offline`, + ); + }; + + useEffect(() => { + const authCode = searchParams.get('code'); + if (authCode) { + googleLoginMutate( + { authCode: authCode || '' }, + { + onSuccess: (data) => { + const { username, userId, email } = data.data; + setUserInfo(username, userId, email); + login(); + saveTokens({ + accessToken: data.data.accessToken, + refreshToken: data.data.refreshToken, + }); + }, + onError: () => { + showToast('구글 로그인 실패', 'warning', 1000); + }, + }, + ); + } + }, [searchParams]); + return (
@@ -131,6 +168,7 @@ const LoginBody = () => { width='full' size='md' variant='outline' + onClick={() => googleLoginHandler()} > 구글로 시작하기 diff --git a/src/constants/_apiPath.ts b/src/constants/_apiPath.ts index 5622ef2f..b747a052 100644 --- a/src/constants/_apiPath.ts +++ b/src/constants/_apiPath.ts @@ -20,6 +20,7 @@ export const AUTH_API = { LOGIN: `${BASE_API.AUTHS}/login`, REISSUE: `${BASE_API.AUTHS}/reissue`, LOGOUT: `${BASE_API.AUTHS}/logout`, + GOOGLELOGIN: `${BASE_API.AUTHS}/oauth2/google`, }; /** diff --git a/src/hooks/auth/usePostGoogleLogin.ts b/src/hooks/auth/usePostGoogleLogin.ts new file mode 100644 index 00000000..1d4140df --- /dev/null +++ b/src/hooks/auth/usePostGoogleLogin.ts @@ -0,0 +1,19 @@ +import { useMutation, UseMutationOptions } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; +import { auth } from '@/api/auth'; +import { GoogleLoginRequest } from '@/type/auth/authRequest'; +import { LoginResponse } from '@/type/auth/authResponse'; +import { Result } from '@/type/response'; + +export const usePostGoogleLogin = ( + options?: UseMutationOptions< + Result, + AxiosError>, + GoogleLoginRequest + >, +) => { + return useMutation({ + mutationFn: (request) => auth.googleLogin(request), + ...options, + }); +}; diff --git a/src/lib/env.ts b/src/lib/env.ts index 4e65a928..ec401072 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -3,4 +3,6 @@ export const env = Object.freeze({ QR_API_URL: `${process.env.NEXT_PUBLIC_QR_API_URL}`, QR_APP_KEY: `${process.env.NEXT_PUBLIC_QR_APPKEY}`, GA_ID: process.env.NEXT_PUBLIC_GA_ID, + GOOGLE_CLIENT_ID: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, + GOOGLE_REDIRECT_URL: process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URL, }); diff --git a/src/type/auth/authRequest.ts b/src/type/auth/authRequest.ts index d7377e21..f6ddedf4 100644 --- a/src/type/auth/authRequest.ts +++ b/src/type/auth/authRequest.ts @@ -20,3 +20,7 @@ export interface VerifyRequest { email: string; verifyCode: string; } + +export interface GoogleLoginRequest { + authCode: string; +}