diff --git a/src/api/auth.ts b/src/api/auth.ts index 1ffe2082..8c7badf1 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -29,5 +29,5 @@ export const checkToken = (): Promise => { }; export const signInKakao = (data: KakakoSignInRequest): Promise => { - return apiClient.post(`/${process.env.NEXT_PUBLIC_TEAM}/auth/signIn/KAKAO`, data); + return apiClient.post(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/signIn/KAKAO`, data); }; diff --git a/src/lib/form/schemas.js b/src/lib/form/schemas.js index f93bd7ca..baafc1ef 100644 --- a/src/lib/form/schemas.js +++ b/src/lib/form/schemas.js @@ -1,7 +1,11 @@ import z from 'zod'; export const emailSchema = z .string() + .trim() .min(1, '이메일은 필수 입력입니다.') + .refine((val) => !/\s/.test(val), { + message: '이메일에는 공백이 포함될 수 없습니다.', + }) .refine( (val) => { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; @@ -14,13 +18,21 @@ export const emailSchema = z export const nicknameSchema = z .string() + .trim() .min(1, '닉네임은 필수 입력입니다.') - .max(20, '닉네임은 최대 20자까지 가능합니다.'); + .max(20, '닉네임은 최대 20자까지 가능합니다.') + .refine((val) => !/\s/.test(val), { + message: '닉네임에는 공백이 포함될 수 없습니다.', + }); export const passwordSchema = z .string() + .trim() .min(1, '비밀번호는 필수 입력입니다.') .min(8, '비밀번호는 최소 8자 이상입니다.') + .refine((val) => !/\s/.test(val), { + message: '비밀번호는 공백이 포함될 수 없습니다.', + }) .refine( (val) => { // 이메일 정규식 직접 검사 @@ -34,5 +46,9 @@ export const passwordSchema = z export const passwordConfirmationSchema = z .string() + .trim() .min(1, '비밀번호 확인을 입력해주세요') - .min(8, '비밀번호 확인을 8자 이상 입력해주세요'); + .min(8, '비밀번호 확인을 8자 이상 입력해주세요') + .refine((val) => !/\s/.test(val), { + message: '비밀번호 확인은 공백이 포함될 수 없습니다.', + }); diff --git a/src/pages/api/auth/check-token.ts b/src/pages/api/auth/check-token.ts index b310f862..87d898f4 100644 --- a/src/pages/api/auth/check-token.ts +++ b/src/pages/api/auth/check-token.ts @@ -4,7 +4,6 @@ import { parseCookie } from '@/lib/cookie'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const cookieHeader = req.headers.cookie; - console.log('test'); if (cookieHeader) { const cookies = parseCookie(cookieHeader); diff --git a/src/pages/api/auth/signIn/KAKAO.ts b/src/pages/api/auth/signIn/KAKAO.ts new file mode 100644 index 00000000..cd5b4089 --- /dev/null +++ b/src/pages/api/auth/signIn/KAKAO.ts @@ -0,0 +1,28 @@ +import { AxiosError } from 'axios'; +import { NextApiRequest, NextApiResponse } from 'next'; + +import apiClient from '@/api/apiClient'; +import { setAuthCookies } from '@/lib/cookie'; +import { KakakoSignInResponse } from '@/types/AuthTypes'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const url = `/${process.env.NEXT_PUBLIC_TEAM}/auth/signIn/KAKAO`; + const data = (await apiClient.post(url, req.body)) as KakakoSignInResponse; + const { accessToken, refreshToken } = data; + setAuthCookies(res, accessToken, refreshToken); + + res.status(200).json({ + user: data.user, + message: '로그인이 성공했습니다', + success: true, + }); + res.end(); + } catch (error) { + const err = error as AxiosError; + const message = error instanceof AxiosError ? error.message : '서버 오류가 발생했습니다.'; + const status = err.response?.status || 500; + + res.status(status).json({ message }); + } +} diff --git a/src/pages/api/auth/signIn.ts b/src/pages/api/auth/signIn/index.ts similarity index 100% rename from src/pages/api/auth/signIn.ts rename to src/pages/api/auth/signIn/index.ts diff --git a/src/pages/oauth/signup/kakao.tsx b/src/pages/oauth/signup/kakao.tsx index 3101671c..b965d5f1 100644 --- a/src/pages/oauth/signup/kakao.tsx +++ b/src/pages/oauth/signup/kakao.tsx @@ -1,50 +1,78 @@ import { useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; import { useRouter } from 'next/router'; import { signInKakao } from '@/api/auth'; +import { getUser } from '@/api/user'; import ErrorModal from '@/components/common/Modal/ErrorModal'; import useErrorModal from '@/hooks/useErrorModal'; -import { KakakoSignInRequest, KakakoSignInResponse } from '@/types/AuthTypes'; +import { useUser } from '@/hooks/useUser'; /* 카카오 로그인 버튼 클릭 시 API 호출하여 로그인/회원가입 처리 */ const KakaoLoginCallbackPage = () => { + const { setUser } = useUser(); const router = useRouter(); const { code } = router.query; const { open, setOpen, handleError, errorMessage } = useErrorModal(); /* 카카오 회원가입/로그인 요청 */ - const handleKakaoAuth = async (): Promise => { - const params: KakakoSignInRequest = { - state: '', - redirectUri: process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI ?? '', - token: code as string, - }; - return await signInKakao(params); - }; + const params = + typeof code === 'string' + ? { + state: '', + redirectUri: process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI ?? '', + token: code, + } + : undefined; const { data, error } = useQuery({ queryKey: ['handleKakaoAuth'], - queryFn: handleKakaoAuth, - enabled: typeof code === 'string', + queryFn: async () => { + if (!params) return; + return await signInKakao(params); + }, + enabled: !!params, + retry: false, + }); + + const { refetch: userRefecth } = useQuery({ + queryKey: ['getUser'], + queryFn: getUser, + enabled: false, retry: false, }); useEffect(() => { - /* API 에러 시 모달로 에러 출력 */ + if (!data) return; + + /* API 성공 시 전역 유저 데이터 세팅 */ + const setUserData = async () => { + try { + const { data: userData } = await userRefecth(); + if (!userData) return; + + setUser(userData); + router.push('/'); + } catch (error) { + if (axios.isAxiosError(error) && error.response) { + // 유저 정보 API 에러를 모달로 출력 + handleError(error.response?.data as Error); + } + } + }; + + setUserData(); + }, [data, router, setUser, userRefecth, handleError]); + + useEffect(() => { + /* 카카오 API 에러 시 모달로 에러 출력 */ if (error) { handleError(new Error('카카오 로그인을 재시도해주세요')); } - - /* API 성공 시 로그인 처리 */ - if (data) { - localStorage.setItem('accessToken', data.accessToken); - localStorage.setItem('refreshToken', data.refreshToken); - router.replace('/'); - } - }, [data, error, router, code, handleError]); + }, [error, handleError]); return ( /* 에러 모달에서 확인 버튼 클릭 시 로그인 화면으로 리디렉트 처리 */ diff --git a/src/pages/signin/index.tsx b/src/pages/signin/index.tsx index ba27cae3..432cbe3e 100644 --- a/src/pages/signin/index.tsx +++ b/src/pages/signin/index.tsx @@ -101,7 +101,7 @@ const SignIn = () => { {/* 폼 시작 */}
-
+
{/* 이메일 */}
diff --git a/src/pages/signup/index.tsx b/src/pages/signup/index.tsx index 1195dbbf..507e4ac1 100644 --- a/src/pages/signup/index.tsx +++ b/src/pages/signup/index.tsx @@ -68,7 +68,7 @@ const Signup = () => { loginMutation.mutate({ email, password }); }, onError: (error) => { - if (error.response?.status === 400) { + if (error.response?.status === 500) { // 로그인 오류인 경우 공통 에러 메시지 setError('root', { message: '닉네임이 중복되었습니다.' }); } else { @@ -131,7 +131,7 @@ const Signup = () => { {/* 폼 시작 */} -
+
{/* 이메일 */}