diff --git a/src/apis/auth.ts b/src/apis/auth.ts new file mode 100644 index 00000000..60ba8f4d --- /dev/null +++ b/src/apis/auth.ts @@ -0,0 +1,30 @@ +import type { PostSigninRequestType, PostSigninResponseType } from '@/schema/auth'; +import { AxiosError } from 'axios'; +import httpClient from '.'; + +const postSignin = async (request: PostSigninRequestType): Promise => { + try { + const response = await httpClient.post('/auth/signIn', request); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + // Axios 에러인 경우 + const axiosError = error as AxiosError; + if (axiosError.response) { + // 서버에서 응답이 온 경우 (예: 4xx, 5xx) + throw new Error('로그인 요청 처리 중 문제가 발생했습니다.'); + } else if (axiosError.request) { + // 요청을 보냈지만 응답을 받지 못한 경우 + throw new Error('서버 응답을 받지 못했습니다. 잠시 후 다시 시도해 주세요.'); + } else { + // 요청을 설정하는 과정에서 문제가 발생한 경우 + throw new Error('로그인 요청을 처리하는 동안 문제가 발생했습니다.'); + } + } else { + // Axios 에러가 아닌 경우 (네트워크 문제 등) + throw new Error('알 수 없는 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.'); + } + } +}; + +export default postSignin; diff --git a/src/apis/index.ts b/src/apis/index.ts index 29949fc2..a1b6aaec 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -8,3 +8,42 @@ const httpClient = axios.create({ }); export default httpClient; + +// NOTE: eslint-disable no-param-reassign 미해결로 인한 설정 +httpClient.interceptors.request.use((config) => { + const accessToken = localStorage.getItem('accessToken'); + /* eslint-disable no-param-reassign */ + if (accessToken) config.headers.Authorization = `Bearer ${accessToken}`; + /* eslint-enable no-param-reassign */ + return config; +}); + +httpClient.interceptors.response.use( + (response) => response, + + (error) => { + if (error.response && error.response.status === 401) { + const refreshToken = localStorage.getItem('refreshToken'); + + if (!refreshToken) { + window.location.href = '/auth/SignIn'; + } else { + httpClient + .post('/auth/refresh-token', null, { + headers: { Authorization: `Bearer ${refreshToken}` }, + }) + .then((response) => { + const { accessToken } = response.data; + const { refreshToken: newRefreshToken } = response.data; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', newRefreshToken); + }) + .catch(() => { + window.location.href = '/auth/SignIn'; + }); + } + } else { + throw new Error(error.response.status); + } + }, +); diff --git a/src/hooks/userQueryHooks.ts b/src/hooks/userQueryHooks.ts index 7c28fe75..005fcf8c 100644 --- a/src/hooks/userQueryHooks.ts +++ b/src/hooks/userQueryHooks.ts @@ -1,8 +1,10 @@ +import postSignin from '@/apis/auth'; import quries from '@/apis/queries'; import { updateMe } from '@/apis/user'; import { GetUserReponseType, GetUserRequestType, PatchMeRequestType } from '@/schema/user'; import { MutationOptions } from '@/types/query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useRouter } from 'next/router'; export const useMeQuery = () => useQuery(quries.user.getMe()); @@ -21,3 +23,21 @@ export const useUpdateMe = (options: MutationOptions) => { }, }); }; + +export const useSignin = () => { + const router = useRouter(); + + return useMutation({ + mutationFn: postSignin, + onSuccess: (data) => { + localStorage.setItem('accessToken', data.accessToken); + localStorage.setItem('refreshToken', data.refreshToken); + router.push('/'); + }, + onError: (error) => { + // NOTE: 임시 테스트용 콘솔, 토스트 추가 예정 + /* eslint-disable no-console */ + console.error(error); + }, + }); +}; diff --git a/src/pages/auth/SignIn.tsx b/src/pages/auth/SignIn.tsx index 49a869f3..d19775e1 100644 --- a/src/pages/auth/SignIn.tsx +++ b/src/pages/auth/SignIn.tsx @@ -7,8 +7,10 @@ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form'; import { PostSigninRequest, PostSigninRequestType } from '@/schema/auth'; +import { useSignin } from '@/hooks/userQueryHooks'; export default function SignIn() { + const mutationSignin = useSignin(); // 폼 정의 const form = useForm({ resolver: zodResolver(PostSigninRequest), @@ -20,9 +22,7 @@ export default function SignIn() { }); function onSubmit(values: PostSigninRequestType) { - // NOTE : 테스트를 위해서 콘솔 넣음 - /* eslint-disable no-console */ - console.log(values); + mutationSignin.mutate(values); } // TODO: 나중에 컴포넌트 분리하기 diff --git a/src/schema/auth.ts b/src/schema/auth.ts index 9fcd92dd..33466608 100644 --- a/src/schema/auth.ts +++ b/src/schema/auth.ts @@ -5,4 +5,21 @@ export const PostSigninRequest = z.object({ password: z.string().min(1, { message: '비밀번호는 필수 입력입니다.' }), }); +const User = z.object({ + id: z.number(), + email: z.string().email(), + nickname: z.string(), + teamId: z.string(), + updatedAt: z.coerce.date(), + createdAt: z.coerce.date(), + image: z.string(), +}); + +export const PostSigninResponse = z.object({ + accessToken: z.string(), + refreshToken: z.string(), + user: User, +}); + export type PostSigninRequestType = z.infer; +export type PostSigninResponseType = z.infer;