From ed3fbba59150aca8ef4595d682b948a7291d0d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=B4=EC=A7=84?= <86779590+haejinyun@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:57:30 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20OAuth=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?callback=20=EB=A1=9C=EB=94=A9=20=EC=8A=A4=ED=94=BC=EB=84=88=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: OAuth 로그인 상황 callback 로딩 스피너 구현 * fix: api 호출 axios로 전환 * fix: 코드리뷰 반영 및 build에러 수정 --- src/apis/queryFunctions/Auth.ts | 7 +++ src/apis/queryFunctions/apiClient.ts | 11 +++- src/apis/queryFunctions/setTokenCookies.ts | 34 ------------- src/apis/queryHooks/Auth/useLogin.ts | 8 +-- .../oauthcallback/OauthCallbackHandler.tsx | 50 +++++++++++++++++++ src/app/oauthcallback/page.tsx | 17 +++++++ .../LoadingSpinner/LoadingSpinner.css.ts | 25 ++++++++++ src/components/LoadingSpinner/index.tsx | 11 ++++ 8 files changed, 123 insertions(+), 40 deletions(-) delete mode 100644 src/apis/queryFunctions/setTokenCookies.ts create mode 100644 src/app/oauthcallback/OauthCallbackHandler.tsx create mode 100644 src/app/oauthcallback/page.tsx create mode 100644 src/components/LoadingSpinner/LoadingSpinner.css.ts create mode 100644 src/components/LoadingSpinner/index.tsx diff --git a/src/apis/queryFunctions/Auth.ts b/src/apis/queryFunctions/Auth.ts index f2048820..2e4ecd0d 100644 --- a/src/apis/queryFunctions/Auth.ts +++ b/src/apis/queryFunctions/Auth.ts @@ -5,6 +5,8 @@ import { PostRegisterResponseData, PostEmailAuthParams, CheckEmailAuthParams, + LoginParams, + PostLoginResponseData, } from '@/apis/types/Auth'; import { Response } from '@/apis/types/common'; @@ -29,3 +31,8 @@ export const postEmailValidationCode = async (params: CheckEmailAuthParams) => { const response = await apiClient.post>('v2/mail/code', params); return response.data; }; + +export const postLogin = async (params: LoginParams) => { + const response = await apiClient.post>('v2/auth/login', params); + return response.data; +}; diff --git a/src/apis/queryFunctions/apiClient.ts b/src/apis/queryFunctions/apiClient.ts index df6fe06d..a6eb6908 100644 --- a/src/apis/queryFunctions/apiClient.ts +++ b/src/apis/queryFunctions/apiClient.ts @@ -33,7 +33,6 @@ function createApiClient() { }, async error => { const refreshToken = localStorage.getItem('refreshToken'); - const originalRequest = error.config; if (error.response?.status === 401) { @@ -60,10 +59,18 @@ function createApiClient() { originalRequest.headers.Authorization = newAccessToken; return await client(originalRequest); } + + console.error('refreshToken도 만료되었습니다.'); + alert('재로그인이 필요합니다.'); + localStorage.removeItem('refreshToken'); + sessionStorage.removeItem('accessToken'); + window.location.href = '/login'; } catch (e) { - console.error('Failed to reissue token:', e); + alert('재로그인이 필요합니다.'); + console.error('accessToken 재발급 실패'); localStorage.removeItem('refreshToken'); sessionStorage.removeItem('accessToken'); + window.location.href = '/login'; } } } diff --git a/src/apis/queryFunctions/setTokenCookies.ts b/src/apis/queryFunctions/setTokenCookies.ts deleted file mode 100644 index 3cf5bc79..00000000 --- a/src/apis/queryFunctions/setTokenCookies.ts +++ /dev/null @@ -1,34 +0,0 @@ -'use server'; - -import { LoginParams } from '../types/Auth'; - -async function setTokenCookies(params: LoginParams) { - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_SERVER_API_URL}/api/v2/auth/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(params), - }); - - if (!response.ok) { - // 서버 응답이 실패 상태인 경우 에러 처리 - const errorData = await response.json(); - - throw new Error(`Failed to reissue token: ${errorData.message || 'Unknown error'}`); - } - - const loginData = await response.json(); - - const accessToken = loginData.result_data?.access_token; - const refreshToken = loginData.result_data?.refresh_token; - - return { accessToken, refreshToken }; - } catch (error) { - console.error('Failed to bring token:', error); - throw error; // 호출자에게 에러 전달 - } -} - -export default setTokenCookies; diff --git a/src/apis/queryHooks/Auth/useLogin.ts b/src/apis/queryHooks/Auth/useLogin.ts index aa167da7..643c9440 100644 --- a/src/apis/queryHooks/Auth/useLogin.ts +++ b/src/apis/queryHooks/Auth/useLogin.ts @@ -2,7 +2,7 @@ import { useMutation } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { useRouter } from 'next/navigation'; -import setTokenCookies from '@/apis/queryFunctions/setTokenCookies'; +import { postLogin } from '@/apis/queryFunctions/Auth'; import { LoginParams } from '@/apis/types/Auth'; import { Response } from '@/apis/types/common'; import { useAuth } from '@/provider/authProvider'; @@ -17,10 +17,10 @@ function useLogin() { const { setIsLoggedIn } = useAuth(); const { mutate } = useMutation({ - mutationFn: (loginParams: LoginParams) => setTokenCookies(loginParams), + mutationFn: (loginParams: LoginParams) => postLogin(loginParams), onSuccess: data => { - sessionStorage.setItem('accessToken', data.accessToken); - localStorage.setItem('refreshToken', data.refreshToken); + sessionStorage.setItem('accessToken', data.result_data.access_token); + localStorage.setItem('refreshToken', data.result_data.refresh_token); setIsLoggedIn(true); showToast('success', '로그인 되었습니다.'); diff --git a/src/app/oauthcallback/OauthCallbackHandler.tsx b/src/app/oauthcallback/OauthCallbackHandler.tsx new file mode 100644 index 00000000..76026740 --- /dev/null +++ b/src/app/oauthcallback/OauthCallbackHandler.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { useEffect } from 'react'; + +import { useRouter, useSearchParams } from 'next/navigation'; + +import LoadingSpinner from '@/components/LoadingSpinner'; +import { useAuth } from '@/provider/authProvider'; +import { useToast } from '@/provider/toastProvider'; + +function OauthCallbackHandler() { + const router = useRouter(); + + const { showToast } = useToast(); + const { setIsLoggedIn } = useAuth(); + + const searchParams = useSearchParams(); + + useEffect(() => { + const accessToken = searchParams.get('access_token'); + const refreshToken = searchParams.get('refresh_token'); + + const handleLogin = () => { + if (accessToken && refreshToken) { + sessionStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', refreshToken); + setIsLoggedIn(true); + showToast('success', '로그인 되었습니다.'); + + router.push('/'); + } else { + localStorage.removeItem('refreshToken'); + sessionStorage.removeItem('accessToken'); + showToast('error', '로그인에 실패하였습니다.'); + router.push('/login'); + } + }; + + const timer = setTimeout(handleLogin, 500); + + return () => clearTimeout(timer); + }, [router, searchParams, setIsLoggedIn, showToast]); + return ( +
+ +
+ ); +} + +export default OauthCallbackHandler; diff --git a/src/app/oauthcallback/page.tsx b/src/app/oauthcallback/page.tsx new file mode 100644 index 00000000..f058f0c9 --- /dev/null +++ b/src/app/oauthcallback/page.tsx @@ -0,0 +1,17 @@ +'use client'; + +import { Suspense } from 'react'; + +import OauthCallbackHandler from './OauthCallbackHandler'; + +// TODO 로그인을 이미 했다면 로그인 페이지로 못들어가게 하기 + +function Oauthcallback() { + return ( + + + + ); +} + +export default Oauthcallback; diff --git a/src/components/LoadingSpinner/LoadingSpinner.css.ts b/src/components/LoadingSpinner/LoadingSpinner.css.ts new file mode 100644 index 00000000..362483d6 --- /dev/null +++ b/src/components/LoadingSpinner/LoadingSpinner.css.ts @@ -0,0 +1,25 @@ +import { style, keyframes } from '@vanilla-extract/css'; + +import colors from '@/styles/color'; + +const spin = keyframes({ + '0%': { transform: 'rotate(0deg)' }, + '100%': { transform: 'rotate(360deg)' }, +}); + +export const spinnerContainer = style({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100vh', + width: '100%', +}); + +export const spinner = style({ + width: '48px', + height: '48px', + border: '6px solid #f3f3f3', + borderTop: `6px solid ${colors.primary9}`, + borderRadius: '50%', + animation: `${spin} 1s linear infinite`, +}); diff --git a/src/components/LoadingSpinner/index.tsx b/src/components/LoadingSpinner/index.tsx new file mode 100644 index 00000000..ae2776bf --- /dev/null +++ b/src/components/LoadingSpinner/index.tsx @@ -0,0 +1,11 @@ +import * as S from './LoadingSpinner.css'; + +function LoadingSpinner() { + return ( +
+
+
+ ); +} + +export default LoadingSpinner;