diff --git a/src/common/apis/fetcher.ts b/src/common/apis/fetcher.ts new file mode 100644 index 00000000..154c8536 --- /dev/null +++ b/src/common/apis/fetcher.ts @@ -0,0 +1,45 @@ +const baseURL = import.meta.env.VITE_BASE_URL; + +type StandardHeaders = 'Content-Type' | 'Authorization' | 'Accept' | 'Cache-Control' | 'User-Agent'; +type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; + +export class CustomError extends Error { + status: number; + + constructor(message: string, status: number) { + super(message); + this.status = status; + } +} + +interface FetchOptions extends Omit { + method?: RequestMethod; + headers?: Partial>; + body?: Record; + params?: Record; +} + +const fetcher = async (url: string, options: FetchOptions = {}) => { + const { body, params, headers = {}, ...rest } = options; + const urlWithParams = params ? `${url}?${new URLSearchParams(params).toString()}` : url; + const isFormData = body instanceof FormData; + + if (!isFormData) { + headers['Content-Type'] = 'application/json'; + } + + const response = await fetch(`${baseURL}${urlWithParams}`, { + headers, + body: isFormData ? body : JSON.stringify(body), + ...rest, + }); + + if (!response.ok) { + const errMsg = await response.json(); + throw new CustomError(errMsg.userMessage, response.status); + } + + return response.json(); +}; + +export default fetcher; diff --git a/src/common/apis/getRecruitingInfo.ts b/src/common/apis/getRecruitingInfo.ts index ad2ed290..d62c84b6 100644 --- a/src/common/apis/getRecruitingInfo.ts +++ b/src/common/apis/getRecruitingInfo.ts @@ -1,7 +1,7 @@ -import instance from '@apis/instance'; +import fetcher from '@apis/fetcher'; export const getRecruitingInfo = async () => { - const res = await instance('/recruiting-season/latest', { method: 'GET' }); + const res = await fetcher('/recruiting-season/latest', { method: 'GET' }); return res; }; diff --git a/src/common/apis/instance.ts b/src/common/apis/instance.ts deleted file mode 100644 index 3dc78203..00000000 --- a/src/common/apis/instance.ts +++ /dev/null @@ -1,27 +0,0 @@ -const baseURL = import.meta.env.VITE_BASE_URL; - -type StandardHeaders = 'Content-Type' | 'Authorization' | 'Accept' | 'Cache-Control' | 'User-Agent'; -type RequestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; - -interface FetchOptions extends RequestInit { - method?: RequestMethod; - headers?: Record; -} - -const instance = async (url: string, options: FetchOptions = {}) => { - const response = await fetch(`${baseURL}${url}`, { - headers: { - 'Content-Type': 'application/json', - ...options.headers, - }, - ...options, - }); - - if (!response.ok) { - throw new Error('network response가 도착하지 않았어요'); - } - - return response.json(); -}; - -export default instance; diff --git a/src/common/components/Input/apis.ts b/src/common/components/Input/apis.ts index 67730417..d8f63a0e 100644 --- a/src/common/components/Input/apis.ts +++ b/src/common/components/Input/apis.ts @@ -1,43 +1,43 @@ -import instance from '@apis/instance'; +import fetcher from '@apis/fetcher'; import { CheckUserRequest } from './types'; export const checkUser = async (userInfo: CheckUserRequest) => { const { email, name, season, group } = userInfo; - const res = await instance('/recruiting-auth/check/user', { + const res = await fetcher('/recruiting-auth/check/user', { method: 'POST', - body: JSON.stringify({ + body: { email, name, season, group, - }), + }, }); return res; }; export const sendVerificationCode = async (email: string, season: number, group: string, isSignup: boolean) => { - const res = await instance('/recruiting-auth/verify/send', { + const res = await fetcher('/recruiting-auth/verify/send', { method: 'POST', - body: JSON.stringify({ + body: { email, season, group, isSignup, - }), + }, }); return res; }; export const checkVerificationCode = async (email: string, code: string) => { - const res = await instance('/recruiting-auth/verify/email', { + const res = await fetcher('/recruiting-auth/verify/email', { method: 'POST', - body: JSON.stringify({ + body: { email, code, - }), + }, }); return res; diff --git a/src/common/components/Input/hooks/useMutateCheckCode.tsx b/src/common/components/Input/hooks/useMutateCheckCode.tsx index 9864c0f1..39759789 100644 --- a/src/common/components/Input/hooks/useMutateCheckCode.tsx +++ b/src/common/components/Input/hooks/useMutateCheckCode.tsx @@ -6,8 +6,7 @@ import { VALIDATION_CHECK } from '@constants/validationCheck'; import { checkVerificationCode } from '../apis'; import type { CheckVerificationCodeRequest, EmailResponse } from '../types'; -import type { ErrorResponse } from '@type/errorResponse'; -import type { AxiosError, AxiosResponse } from 'axios'; +import type { CustomError } from '@apis/fetcher'; interface MutateCheckCodeProps { onSuccess: () => void; @@ -17,14 +16,14 @@ const useMutateCheckCode = ({ onSuccess }: MutateCheckCodeProps) => { const { setError } = useFormContext(); const { mutate: checkVerificationCodeMutate, isPending: checkVerificationCodeIsPending } = useMutation< - AxiosResponse, - AxiosError, + EmailResponse, + CustomError, CheckVerificationCodeRequest >({ mutationFn: ({ email, code }: CheckVerificationCodeRequest) => checkVerificationCode(email, code), onSuccess, onError(error) { - if (error.response?.status === 400) { + if (error.status === 400) { setError('code', { type: 'not-match', message: VALIDATION_CHECK.verificationCode.errorText, diff --git a/src/common/components/Input/hooks/useMutateCheckUser.tsx b/src/common/components/Input/hooks/useMutateCheckUser.tsx index 63bde7a2..b4a2ecf2 100644 --- a/src/common/components/Input/hooks/useMutateCheckUser.tsx +++ b/src/common/components/Input/hooks/useMutateCheckUser.tsx @@ -6,8 +6,7 @@ import { VALIDATION_CHECK } from '@constants/validationCheck'; import { checkUser } from '../apis'; import type { CheckUserRequest, EmailResponse } from '../types'; -import type { ErrorResponse } from '@type/errorResponse'; -import type { AxiosError, AxiosResponse } from 'axios'; +import type { CustomError } from '@apis/fetcher'; interface MutateCheckUserProps { onSendCode: () => void; @@ -17,8 +16,8 @@ const useMutateCheckUser = ({ onSendCode }: MutateCheckUserProps) => { const { clearErrors, setError } = useFormContext(); const { mutate: checkUserMutate, isPending: checkUserIsPending } = useMutation< - AxiosResponse, - AxiosError, + EmailResponse, + CustomError, CheckUserRequest >({ mutationFn: (userInfo: CheckUserRequest) => checkUser(userInfo), @@ -27,7 +26,7 @@ const useMutateCheckUser = ({ onSendCode }: MutateCheckUserProps) => { onSendCode(); }, onError: (error) => { - if (error.response?.status === 400 || error.response?.status === 403) { + if (error.status === 400 || error.status === 403) { setError('name', { type: 'non-existence', message: VALIDATION_CHECK.name.errorTextNonexistence, diff --git a/src/common/components/Input/hooks/useMutateSendCode.tsx b/src/common/components/Input/hooks/useMutateSendCode.tsx index bc976dbe..1ab3d9f1 100644 --- a/src/common/components/Input/hooks/useMutateSendCode.tsx +++ b/src/common/components/Input/hooks/useMutateSendCode.tsx @@ -6,8 +6,7 @@ import { VALIDATION_CHECK } from '@constants/validationCheck'; import { sendVerificationCode } from '../apis'; import type { EmailResponse, SendVerificationCodeRequest } from '../types'; -import type { ErrorResponse } from '@type/errorResponse'; -import type { AxiosError, AxiosResponse } from 'axios'; +import type { CustomError } from '@apis/fetcher'; interface MutateSendCodeProps { onChangeVerification: (bool: boolean) => void; @@ -18,8 +17,8 @@ const useMutateSendCode = ({ onChangeVerification, onSetTimer }: MutateSendCodeP const { setError } = useFormContext(); const { mutate: sendVerificationCodeMutate, isPending: sendVerificationCodeIsPending } = useMutation< - AxiosResponse, - AxiosError, + EmailResponse, + CustomError, SendVerificationCodeRequest >({ mutationFn: ({ email, season, group, isSignup }: SendVerificationCodeRequest) => @@ -29,7 +28,7 @@ const useMutateSendCode = ({ onChangeVerification, onSetTimer }: MutateSendCodeP onSetTimer(); }, onError: (error) => { - if (error.response?.status === 400 || error.response?.status === 403) { + if (error.status === 400 || error.status === 403) { setError('email', { type: 'already-existence', message: VALIDATION_CHECK.email.errorTextExistence, diff --git a/src/common/hooks/useDate.tsx b/src/common/hooks/useDate.tsx index 88245903..bef80380 100644 --- a/src/common/hooks/useDate.tsx +++ b/src/common/hooks/useDate.tsx @@ -35,7 +35,7 @@ const useDate = () => { obInterviewStart, ybInterviewEnd, obInterviewEnd, - } = data?.data.season || {}; + } = data?.season || {}; const applicationStart = group === 'YB' ? ybApplicationStart : obApplicationStart; // 서류 시작 const applicationEnd = group === 'YB' ? ybApplicationEnd : obApplicationEnd; // 서류 마감 @@ -98,7 +98,7 @@ const useDate = () => { ]); return { - ...data?.data.season, + ...data?.season, NoMoreRecruit, NoMoreApply, NoMoreScreeningResult, diff --git a/src/common/hooks/useGetRecruitingInfo.tsx b/src/common/hooks/useGetRecruitingInfo.tsx index 27d58d27..256aba58 100644 --- a/src/common/hooks/useGetRecruitingInfo.tsx +++ b/src/common/hooks/useGetRecruitingInfo.tsx @@ -2,17 +2,11 @@ import { useQuery } from '@tanstack/react-query'; import { getRecruitingInfo } from '@apis/getRecruitingInfo'; -import type { ErrorResponse } from '@type/errorResponse'; import type { RecruitingResponse } from '@type/recruitingInfo'; -import type { AxiosError, AxiosResponse } from 'axios'; +import type { CustomError } from '@apis/fetcher'; const useGetRecruitingInfo = () => { - const { data, isLoading } = useQuery< - AxiosResponse, - AxiosError, - AxiosResponse, - string[] - >({ + const { data, isLoading } = useQuery({ queryKey: ['get-recruiting-info'], queryFn: getRecruitingInfo, }); diff --git a/src/views/PasswordPage/apis.ts b/src/views/PasswordPage/apis.ts index df138c15..31044164 100644 --- a/src/views/PasswordPage/apis.ts +++ b/src/views/PasswordPage/apis.ts @@ -1,18 +1,18 @@ -import instance from '@apis/instance'; +import fetcher from '@apis/fetcher'; import type { PasswordRequest } from './types'; export const sendPasswordChange = async (userInfo: PasswordRequest) => { const { email, season, group, password, passwordCheck } = userInfo; - const res = await instance('/recruiting-auth/change/password', { + const res = await fetcher('/recruiting-auth/change/password', { method: 'POST', - body: JSON.stringify({ + body: { email, season, group, password, passwordCheck, - }), + }, }); return res; diff --git a/src/views/PasswordPage/hooks/useMutateChangePassword.tsx b/src/views/PasswordPage/hooks/useMutateChangePassword.tsx index ccc00950..54387ada 100644 --- a/src/views/PasswordPage/hooks/useMutateChangePassword.tsx +++ b/src/views/PasswordPage/hooks/useMutateChangePassword.tsx @@ -1,10 +1,9 @@ import { useMutation } from '@tanstack/react-query'; -import { AxiosError, AxiosResponse } from 'axios'; import { sendPasswordChange } from '../apis'; import type { PasswordRequest, PasswordResponse } from '../types'; -import type { ErrorResponse } from '@type/errorResponse'; +import type { CustomError } from '@apis/fetcher'; interface MutateChangePasswordProps { onSuccess: () => void; @@ -12,8 +11,8 @@ interface MutateChangePasswordProps { const useMutateChangePassword = ({ onSuccess }: MutateChangePasswordProps) => { const { mutate: changePasswordMutate, isPending: changePasswordIsPending } = useMutation< - AxiosResponse, - AxiosError, + PasswordResponse, + CustomError, PasswordRequest >({ mutationFn: (userInfo: PasswordRequest) => sendPasswordChange(userInfo), diff --git a/src/views/SignInPage/apis.ts b/src/views/SignInPage/apis.ts index 0cf22390..655da4e6 100644 --- a/src/views/SignInPage/apis.ts +++ b/src/views/SignInPage/apis.ts @@ -1,17 +1,17 @@ -import instance from '@apis/instance'; +import fetcher from '@apis/fetcher'; import type { SignInRequest } from './types'; export const sendSignIn = async (userInfo: SignInRequest) => { const { email, season, group, password } = userInfo; - const res = await instance('/recruiting-auth/login', { + const res = await fetcher('/recruiting-auth/login', { method: 'POST', - body: JSON.stringify({ + body: { email, season, group, password, - }), + }, }); return res; diff --git a/src/views/SignInPage/hooks/useMutateSignIn.tsx b/src/views/SignInPage/hooks/useMutateSignIn.tsx index 85351fe4..4f2b5ccc 100644 --- a/src/views/SignInPage/hooks/useMutateSignIn.tsx +++ b/src/views/SignInPage/hooks/useMutateSignIn.tsx @@ -6,8 +6,7 @@ import { VALIDATION_CHECK } from '@constants/validationCheck'; import { sendSignIn } from '../apis'; import type { SignInRequest, SignInResponse } from '../types'; -import type { ErrorResponse } from '@type/errorResponse'; -import type { AxiosError, AxiosResponse } from 'axios'; +import type { CustomError } from '@apis/fetcher'; interface MutateSignInProps { finalPassConfirmEnd?: string; @@ -15,20 +14,16 @@ interface MutateSignInProps { } const useMutateSignIn = ({ finalPassConfirmEnd, onSetError }: MutateSignInProps) => { - const { mutate: signInMutate, isPending: signInIsPending } = useMutation< - AxiosResponse, - AxiosError, - SignInRequest - >({ + const { mutate: signInMutate, isPending: signInIsPending } = useMutation({ mutationFn: (userInfo: SignInRequest) => sendSignIn(userInfo), - onSuccess: ({ data: { email, token } }) => { + onSuccess: ({ email, token }) => { setUserId(email); localStorage.setItem('soptApplyAccessToken', token); localStorage.setItem('soptApplyAccessTokenExpiredTime', finalPassConfirmEnd || ''); window.location.reload(); }, onError(error) { - if (error.response?.status === 403) { + if (error.status === 403) { onSetError('email', 'not-match', VALIDATION_CHECK.email.notMatchErrorText); onSetError('password', 'not-match', VALIDATION_CHECK.password.notMatchErrorText); } diff --git a/src/views/SignupPage/apis.ts b/src/views/SignupPage/apis.ts index fa38bde1..9803c327 100644 --- a/src/views/SignupPage/apis.ts +++ b/src/views/SignupPage/apis.ts @@ -1,12 +1,12 @@ -import instance from '@apis/instance'; +import fetcher from '@apis/fetcher'; import type { SignUpRequest } from './types'; export const sendSignUp = async (userInfo: SignUpRequest) => { const { email, password, passwordCheck, name, phone, season, group } = userInfo; - const res = await instance('/recruiting-auth/signup', { + const res = await fetcher('/recruiting-auth/signup', { method: 'POST', - body: JSON.stringify({ + body: { email, password, passwordCheck, @@ -14,7 +14,7 @@ export const sendSignUp = async (userInfo: SignUpRequest) => { phone, season, group, - }), + }, }); return res; diff --git a/src/views/SignupPage/hooks/useMutateSignUp.tsx b/src/views/SignupPage/hooks/useMutateSignUp.tsx index 0fc547dd..b42d9aae 100644 --- a/src/views/SignupPage/hooks/useMutateSignUp.tsx +++ b/src/views/SignupPage/hooks/useMutateSignUp.tsx @@ -5,8 +5,7 @@ import { useNavigate } from 'react-router-dom'; import { sendSignUp } from '../apis'; import type { SignUpRequest, SignUpResponse } from '../types'; -import type { ErrorResponse } from '@type/errorResponse'; -import type { AxiosError, AxiosResponse } from 'axios'; +import type { CustomError } from '@apis/fetcher'; interface MutateSignUpProps { onCheckExistence: () => void; @@ -15,18 +14,14 @@ interface MutateSignUpProps { const useMutateSignUp = ({ onCheckExistence }: MutateSignUpProps) => { const navigate = useNavigate(); - const { mutate: signUpMutate, isPending: signUpIsPending } = useMutation< - AxiosResponse, - AxiosError, - SignUpRequest - >({ + const { mutate: signUpMutate, isPending: signUpIsPending } = useMutation({ mutationFn: (userInfo: SignUpRequest) => sendSignUp(userInfo), onSuccess: () => { track('done-signup-apply'); navigate('/'); }, onError: (error) => { - if (error.response?.status === 400) { + if (error.status === 400) { onCheckExistence(); } },