From 3993d27382a96fcc3744324029bd30f80b499df0 Mon Sep 17 00:00:00 2001 From: yoonyesol Date: Tue, 27 Aug 2024 22:44:37 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20#80=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EB=A1=9C=EC=A7=81=20=EC=9D=BC=EB=B6=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20API=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EB=A1=9C=EC=A7=81=EC=97=90=20useMutation?= =?UTF-8?q?=20=ED=9B=85=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.tsx | 7 ++++++- src/pages/user/SignInPage.tsx | 21 +++++++++++++-------- src/services/authService.ts | 14 ++++++++++++++ src/services/axiosProvider.ts | 22 +++++++++++----------- src/stores/useAuthStore.ts | 1 - 5 files changed, 44 insertions(+), 21 deletions(-) create mode 100644 src/services/authService.ts diff --git a/src/main.tsx b/src/main.tsx index ef2936b2..f290fd0d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import '@/globals.css'; import MainRouter from '@routes/MainRouter.tsx'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; // QueryClient와 QueryClientProvider 임포트 async function enableMocking() { if (!import.meta.env.DEV) return; @@ -10,10 +11,14 @@ async function enableMocking() { return worker.start(); } +const queryClient = new QueryClient(); + enableMocking().then(() => { ReactDOM.createRoot(document.getElementById('root')!).render( - + + + , ); }); diff --git a/src/pages/user/SignInPage.tsx b/src/pages/user/SignInPage.tsx index 8b37c5d2..c8348a26 100644 --- a/src/pages/user/SignInPage.tsx +++ b/src/pages/user/SignInPage.tsx @@ -7,10 +7,12 @@ import FooterLinks from '@components/user/auth-form/FooterLinks'; import AuthFormLayout from '@layouts/AuthFormLayout'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import { useMutation } from '@tanstack/react-query'; import type { UserSignInForm } from '@/types/UserType'; -import { authAxios, defaultAxios } from '@/services/axiosProvider'; +import { authAxios } from '@/services/axiosProvider'; import useToast from '@/hooks/useToast'; import { useAuthStore } from '@/stores/useAuthStore'; +import { login } from '@/services/authService'; export default function SignInPage() { const { toastError } = useToast(); @@ -27,11 +29,9 @@ export default function SignInPage() { }, }); - const onSubmit = async (data: UserSignInForm) => { - try { - const response = await defaultAxios.post('user/login', data, { withCredentials: true }); - if (response.status !== 200) return toastError('잘못된 응답입니다. 다시 로그인 해주세요.'); - + const signIn = useMutation({ + mutationFn: (data: UserSignInForm) => login(data), + onSuccess: (response) => { const accessToken = response.headers.authorization; if (!accessToken) return toastError('로그인에 실패했습니다.'); @@ -39,12 +39,17 @@ export default function SignInPage() { useAuthStore.getState().Login(accessToken.replace('Bearer ', '')); navigate('/', { replace: true }); - } catch (error) { + }, + onError: (error: Error) => { if (axios.isAxiosError(error) && error.response?.status === 400) { return toastError('아이디와 비밀번호를 한번 더 확인해 주세요.'); } toastError(`로그인 도중 오류가 발생했습니다: ${error}`); - } + }, + }); + + const onSubmit = async (data: UserSignInForm) => { + signIn.mutate(data); }; return ( diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 00000000..c16ff432 --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,14 @@ +import { defaultAxios } from '@services/axiosProvider'; +import type { User, UserSignInForm } from '@/types/UserType'; + +/** + * 사용자 로그인 API + * + * @export + * @async + * @param {UserSignInForm} loginForm - 로그인 폼 데이터 + * @returns {Promise>} + */ +export async function login(loginForm: UserSignInForm) { + return defaultAxios.post('user/login', loginForm, { withCredentials: true }); +} diff --git a/src/services/axiosProvider.ts b/src/services/axiosProvider.ts index ef431c5e..0c4cd22f 100644 --- a/src/services/axiosProvider.ts +++ b/src/services/axiosProvider.ts @@ -2,7 +2,6 @@ import axios from 'axios'; import { SECOND } from '@constants/units'; import { JWT_TOKEN_DUMMY } from '@mocks/mockData'; import type { AxiosInstance, AxiosRequestConfig } from 'axios'; -import { useNavigate } from 'react-router-dom'; import { useAuthStore } from '@/stores/useAuthStore'; import useToast from '@/hooks/useToast'; @@ -32,7 +31,7 @@ authAxios.interceptors.request.use( (config) => { const modifiedConfig = { ...config }; - const accessToken = useAuthStore.getState(); + const { accessToken } = useAuthStore.getState(); if (accessToken) modifiedConfig.headers.Authorization = `Bearer ${accessToken}`; return modifiedConfig; @@ -47,31 +46,32 @@ authAxios.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; - const nav = useNavigate(); const { toastError } = useToast(); // Access token 만료 시 처리 if (error.response?.status === 401) { try { + // Refresh token을 이용해 새로운 Access token 발급 const refreshResponse = await defaultAxios.post('user/login/refresh', null, { withCredentials: true }); const newAccessToken = refreshResponse.headers.Authorization; // 응답값: `Bearer ${newAccessToken}` + if (!newAccessToken) { + toastError('토큰 발급에 실패했습니다. 다시 로그인 해주세요.'); + useAuthStore.getState().Logout(); + return; + } + authAxios.defaults.headers.Authorization = newAccessToken; useAuthStore.getState().setAccessToken(newAccessToken.replace('Bearer ', '')); + // 기존 요청에 새로운 Access token 적용 originalRequest.headers.Authorization = newAccessToken; return await axios(originalRequest); } catch (refreshError) { - // Refresh token 에러/만료 시 처리 + // Refresh token 에러 시 처리 console.error('Refresh token error:', refreshError); - toastError('로그인 정보가 만료되었습니다. 다시 로그인 해주세요.'); - - // 3초 후 페이지 이동 - setTimeout(() => { - useAuthStore.getState().Logout(); - nav('/signin'); - }, 3000); + useAuthStore.getState().Logout(); return Promise.reject(refreshError); } } diff --git a/src/stores/useAuthStore.ts b/src/stores/useAuthStore.ts index fccc35c0..e3878698 100644 --- a/src/stores/useAuthStore.ts +++ b/src/stores/useAuthStore.ts @@ -23,7 +23,6 @@ export const useAuthStore = createStore( }, Logout: () => { set({ isAuthenticated: false, accessToken: null }); - // TODO: 로그아웃 로직 요청 코드 추가 }, }), {