diff --git a/src/api/auth/type.ts b/src/api/auth/type.ts index e86fdfca..733a34bb 100644 --- a/src/api/auth/type.ts +++ b/src/api/auth/type.ts @@ -5,6 +5,7 @@ export interface SignUpFormData { passwordCheck: string; companyName: string; } + export type SignUpRequest = Omit; //TODO: 병합 후 수정 diff --git a/src/app/auths/signin/_components/LinkToSignUp.tsx b/src/app/auths/signin/_components/LinkToSignUp.tsx new file mode 100644 index 00000000..ba3ef3f9 --- /dev/null +++ b/src/app/auths/signin/_components/LinkToSignUp.tsx @@ -0,0 +1,17 @@ +import Link from 'next/link'; + +const LinkToSignUp = () => { + return ( +
+ WE WRITE가 처음이신가요? + + 회원가입 + +
+ ); +}; + +export default LinkToSignUp; diff --git a/src/app/auths/signin/_components/SignInForm.tsx b/src/app/auths/signin/_components/SignInForm.tsx new file mode 100644 index 00000000..d371e749 --- /dev/null +++ b/src/app/auths/signin/_components/SignInForm.tsx @@ -0,0 +1,67 @@ +'use client'; +import Button from '@/components/common/Button/Button'; +import InputForm from '@/components/common/Form/InputForm'; +import useBoolean from '@/hooks/useBoolean'; +import { useSignInForm } from '@/hooks/api/auth/useSignInForm'; +import { VisibilityOff, VisibilityOn } from '@public/assets/icons'; +import { signInValidate } from '@/utils/validators/auth'; + +const SignInForm = () => { + const { value: isShowPassword, toggle: toggleIsShowPassword } = useBoolean(); + + const { onSubmit, register, handleSubmit, isSubmitting, errors } = + useSignInForm(); + + return ( +
+ signInValidate({ value, name: 'email' }), + })} + type="email" + hasError={!!errors.email} + helperText={errors.email?.message} + /> + + signInValidate({ value, name: 'password' }), + })} + type={isShowPassword ? 'text' : 'password'} + hasError={!!errors.password} + helperText={errors.password?.message} + suffixIcon={ + + } + /> + + + + ); +}; + +export default SignInForm; diff --git a/src/app/auths/signin/page.tsx b/src/app/auths/signin/page.tsx index 64ad034b..47343a81 100644 --- a/src/app/auths/signin/page.tsx +++ b/src/app/auths/signin/page.tsx @@ -1,139 +1,26 @@ -'use client'; -import Button from '@/components/common/Button/Button'; -import InputForm from '@/components/common/Form/InputForm'; +import SignInForm from './_components/SignInForm'; +import getMyInfoOnServer from '@/providers/auth-provider/getMyInfoOnServer'; +import { headers } from 'next/headers'; +import { redirect } from 'next/navigation'; +import LinkToSignUp from './_components/LinkToSignUp'; -import { VisibilityOff, VisibilityOn } from '@public/assets/icons'; -import { usePostSignin } from '@/hooks/api/auth/usePostSignin'; -import { SigninRequest } from '@/api/auth/type'; +const SignIn = async () => { + const { isSignIn } = await getMyInfoOnServer(); + const referer = (await headers()).get('referer'); -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; - -import { useEffect } from 'react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import useBoolean from '@/hooks/useBoolean'; -import { useAuth } from '@/providers/auth-provider/AuthProvider.client'; -import { APP_ROUTES } from '@/constants/appRoutes'; -import useReferer from '@/hooks/useReferer'; - -const SignIn = () => { - const { value: isShowPassword, toggle: toggleIsShowPassword } = useBoolean(); - const { redirectPath } = useReferer(); - const router = useRouter(); - const { - register, - handleSubmit, - setError, - formState: { isSubmitting, errors }, - } = useForm(); - const { mutate: signIn, isPending } = usePostSignin(); - const { isSignIn } = useAuth(); - const onSubmit: SubmitHandler = (data) => { - if (isSubmitting || isPending) return; - signIn(data, { - onSuccess: () => { - router.replace(redirectPath); - }, - onError: (error: Error) => { - const errorData = JSON.parse(error.message); - if ( - errorData.code === 'VALIDATION_ERROR' || - errorData.code === 'USER_NOT_FOUND' - ) { - setError('email', { - type: 'manual', - message: errorData.message, - }); - } - if (errorData.code === 'INVALID_CREDENTIALS') { - setError('password', { - type: 'manual', - message: errorData.message, - }); - } - }, - }); - }; - useEffect(() => { - if (isSignIn) { - router.push(APP_ROUTES.social); - } - }, []); if (isSignIn) { - return
Loading...
; + redirect(referer ?? '/'); } + return (

로그인

-
-
- -
-
- - {isShowPassword ? ( - - ) : ( - - )} - - } - type={isShowPassword ? 'text' : 'password'} - /> -
- -
-
- WE WRITE가 처음이신가요? - - 회원가입 - -
+ + +
); diff --git a/src/app/auths/signup/_components/LinkToSignIn.tsx b/src/app/auths/signup/_components/LinkToSignIn.tsx new file mode 100644 index 00000000..9ce0561e --- /dev/null +++ b/src/app/auths/signup/_components/LinkToSignIn.tsx @@ -0,0 +1,17 @@ +import { APP_ROUTES } from '@/constants/appRoutes'; +import Link from 'next/link'; + +const LinkToSignIn = () => { + return ( +
+ 이미 회원이신가요? + + 로그인 + +
+ ); +}; +export default LinkToSignIn; diff --git a/src/app/auths/signup/_components/SignupForm.tsx b/src/app/auths/signup/_components/SignupForm.tsx new file mode 100644 index 00000000..430723a4 --- /dev/null +++ b/src/app/auths/signup/_components/SignupForm.tsx @@ -0,0 +1,161 @@ +'use client'; + +import Button from '@/components/common/Button/Button'; +import InputForm from '@/components/common/Form/InputForm'; + +import useBoolean from '@/hooks/useBoolean'; +import useSignUpForm from '@/hooks/api/auth/useSignUpForm'; +import { VisibilityOff, VisibilityOn } from '@public/assets/icons'; +import { signUpValidate } from '@/utils/validators/auth'; + +const SignupForm = () => { + const { value: showPassword, toggle: toggleShowPassword } = useBoolean(); + const { value: showPasswordCheck, toggle: toggleShowPasswordCheck } = + useBoolean(); + + const { onSubmit, register, handleSubmit, isSubmitting, errors, getValues } = + useSignUpForm(); + + return ( +
+ signUpValidate({ value, name: 'name' }), + }), + }} + hasError={!!errors.name} + helperText={errors.name?.message} + /> + + signUpValidate({ value, name: 'email' }), + }), + }} + /> + +
+
+ + {showPassword ? ( + + ) : ( + + )} + + } + type={showPassword ? 'text' : 'password'} + placeholder="비밀번호를 입력해주세요" + register={{ + ...register('password', { + validate: (value) => + signUpValidate({ value, name: 'password' }), + }), + }} + hasError={!!errors.password} + helperText={errors.password?.message} + /> +
+
+
+
+ + {showPasswordCheck ? ( + + ) : ( + + )} + + } + type={showPasswordCheck ? 'text' : 'password'} + placeholder="비밀번호를 다시 한 번 입력해주세요" + register={{ + ...register('passwordCheck', { + validate: (value) => + signUpValidate({ + value, + name: 'passwordCheck', + password: getValues('password'), + }), + }), + }} + hasError={!!errors.passwordCheck} + helperText={errors.passwordCheck?.message} + /> +
+
+ + signUpValidate({ value, name: 'companyName' }), + }), + }} + hasError={!!errors.companyName} + helperText={errors.companyName?.message} + /> + + + + ); +}; + +export default SignupForm; diff --git a/src/app/auths/signup/page.tsx b/src/app/auths/signup/page.tsx index 408404a0..66d4f849 100644 --- a/src/app/auths/signup/page.tsx +++ b/src/app/auths/signup/page.tsx @@ -1,67 +1,15 @@ -'use client'; +import getMyInfoOnServer from '@/providers/auth-provider/getMyInfoOnServer'; +import SignupForm from './_components/SignupForm'; +import LinkToSignIn from './_components/LinkToSignIn'; +import { redirect } from 'next/navigation'; +import { headers } from 'next/headers'; -import Link from 'next/link'; -import { useEffect } from 'react'; -import { SubmitHandler, useForm } from 'react-hook-form'; -import { SignUpFormData } from '@/api/auth/type'; -import useCreateUser from '@/hooks/api/auth/useCreateUser'; -import { useRouter } from 'next/navigation'; +const SignUp = async () => { + const { isSignIn } = await getMyInfoOnServer(); + const referer = (await headers()).get('referer'); -import InputForm from '@/components/common/Form/InputForm'; -import { VisibilityOff, VisibilityOn } from '@public/assets/icons'; -import Button from '@/components/common/Button/Button'; -import useBoolean from '@/hooks/useBoolean'; -import { APP_ROUTES } from '@/constants/appRoutes'; -import { useAuth } from '@/providers/auth-provider/AuthProvider.client'; - -const SignUp = () => { - const { value: showPassword, toggle: toggleShowPassword } = useBoolean(); - const { value: showPasswordCheck, toggle: toggleShowPasswordCheck } = - useBoolean(); - const { - register, - handleSubmit, - formState: { isSubmitting, errors }, - getValues, - setError, - } = useForm(); - - const { mutate: createUser } = useCreateUser(); //회원 가입 요청 hooks 호출 - - const router = useRouter(); - - // 회원가입 제출 함수 - const onSubmit: SubmitHandler = (data) => { - const signUpData = { - //form 데이터 중 비밀번호 확인 제외 데이터 - name: data.name, - email: data.email, - password: data.password, - companyName: data.companyName, - }; - - createUser(signUpData, { - onSuccess: () => { - alert('회원가입이 완료되었습니다.'); - router.push(`${APP_ROUTES.signin}`); - }, - onError: (error: Error) => { - // 이메일 중복확인 오류 처리 - setError('email', { - type: 'manual', - message: error.message, - }); - }, - }); - }; - const { isSignIn } = useAuth(); - useEffect(() => { - if (isSignIn) { - router.push(APP_ROUTES.social); - } - }, []); if (isSignIn) { - return
Loading...
; + redirect(referer ?? '/'); } return ( @@ -70,167 +18,8 @@ const SignUp = () => {

회원가입

-
- - - - -
-
- - {showPassword ? ( - - ) : ( - - )} - - } - type={showPassword ? 'text' : 'password'} - placeholder="비밀번호를 입력해주세요" - register={{ - ...register('password', { - required: '비밀번호가 8자 이상이 되도록 해 주세요', - pattern: { - value: /^[^\s]{8,}$/, - message: '비밀번호가 8자 이상이 되도록 해 주세요', - }, - }), - }} - hasError={!!errors.password} - helperText={errors.password?.message} - /> -
-
-
-
- - {showPasswordCheck ? ( - - ) : ( - - )} - - } - type={showPasswordCheck ? 'text' : 'password'} - placeholder="비밀번호를 다시 한 번 입력해주세요" - register={{ - ...register('passwordCheck', { - required: '비밀번호를 다시 한 번 입력해주세요', - validate: (value) => { - if (value !== getValues('password')) { - return '비밀번호가 일치하지 않습니다'; - } - }, - }), - }} - hasError={!!errors.passwordCheck} - helperText={errors.passwordCheck?.message} - /> -
-
- - - - -
- 이미 회원이신가요? - - 로그인 - -
- + + ); diff --git a/src/hooks/api/auth/useCreateUser.ts b/src/hooks/api/auth/useCreateUser.ts index 2ac3ac8b..bffd4534 100644 --- a/src/hooks/api/auth/useCreateUser.ts +++ b/src/hooks/api/auth/useCreateUser.ts @@ -1,12 +1,25 @@ import { createUser } from '@/api/auth/api'; import { SignUpRequest } from '@/api/auth/type'; import { useMutation } from '@tanstack/react-query'; +import toast from '@/utils/toast'; +import { useRouter } from 'next/navigation'; +import { APP_ROUTES } from '@/constants/appRoutes'; const useCreateUser = () => { + const router = useRouter(); return useMutation({ mutationFn: (data: SignUpRequest) => createUser(data), + onSuccess: () => { + toast.success('회원가입이 완료되었습니다.'); + router.push(APP_ROUTES.signin); + }, onError: (error) => { - console.error(error); + toast({ + type: 'error', + title: '회원가입에 실패했습니다.', + message: error.message, + duration: 5, + }); }, }); }; diff --git a/src/hooks/api/auth/usePostSignin.ts b/src/hooks/api/auth/usePostSignin.ts index 5d8c89f8..6150390c 100644 --- a/src/hooks/api/auth/usePostSignin.ts +++ b/src/hooks/api/auth/usePostSignin.ts @@ -1,9 +1,10 @@ -//TODO: 병합 후 수정 import { useMutation, useQueryClient } from '@tanstack/react-query'; import { postSignIn } from '@/api/auth/api'; import { SigninRequest } from '@/api/auth/type'; import { useRouter } from 'next/navigation'; +import toast from '@/utils/toast'; +import { APP_ROUTES } from '@/constants/appRoutes'; export const usePostSignin = () => { const queryClient = useQueryClient(); @@ -11,11 +12,18 @@ export const usePostSignin = () => { return useMutation({ mutationFn: (data: SigninRequest) => postSignIn(data), onSuccess: async () => { + toast.success('로그인에 성공했습니다.'); await queryClient.prefetchQuery({ queryKey: ['myInfo'] }); - router.push('/'); + router.push(APP_ROUTES.social); }, - onError: (error) => { - console.error(error); + onError: (error: Error) => { + const errorData = JSON.parse(error.message); + toast({ + type: 'error', + title: '로그인에 실패했습니다.', + message: errorData.message, + duration: 5, + }); }, }); }; diff --git a/src/hooks/api/auth/useSignInForm.ts b/src/hooks/api/auth/useSignInForm.ts new file mode 100644 index 00000000..5a0b1655 --- /dev/null +++ b/src/hooks/api/auth/useSignInForm.ts @@ -0,0 +1,35 @@ +import { SigninRequest } from '@/api/auth/type'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { usePostSignin } from './usePostSignin'; + +export const useSignInForm = () => { + const { mutate: signIn } = usePostSignin(); + + const { + register, + handleSubmit, + setError, + formState: { isSubmitting, errors }, + } = useForm(); + + const onSubmit: SubmitHandler = (data) => { + signIn(data, { + onError: (error: Error) => { + const errorData = JSON.parse(error.message); + if ( + errorData.code === 'VALIDATION_ERROR' || + errorData.code === 'USER_NOT_FOUND' + ) { + setError('email', { type: 'manual', message: errorData.message }); + } + if (errorData.code === 'INVALID_CREDENTIALS') { + setError('password', { type: 'manual', message: errorData.message }); + } + }, + }); + }; + + return { onSubmit, register, handleSubmit, isSubmitting, errors }; +}; + +export default useSignInForm; diff --git a/src/hooks/api/auth/useSignUpForm.ts b/src/hooks/api/auth/useSignUpForm.ts new file mode 100644 index 00000000..e21e5721 --- /dev/null +++ b/src/hooks/api/auth/useSignUpForm.ts @@ -0,0 +1,38 @@ +'use client'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { SignUpFormData } from '@/api/auth/type'; +import useCreateUser from '@/hooks/api/auth/useCreateUser'; + +const useSignUpForm = () => { + const { mutate: createUser } = useCreateUser(); + + const { + register, + handleSubmit, + formState: { isSubmitting, errors }, + getValues, + setError, + } = useForm(); + + const onSubmit: SubmitHandler = (data) => { + const signUpData = { + name: data.name, + email: data.email, + password: data.password, + companyName: data.companyName, + }; + + createUser(signUpData, { + onError: (error: Error) => { + setError('email', { + type: 'manual', + message: error.message, + }); + }, + }); + }; + + return { onSubmit, register, handleSubmit, isSubmitting, errors, getValues }; +}; + +export default useSignUpForm; diff --git a/src/utils/validators/auth.ts b/src/utils/validators/auth.ts new file mode 100644 index 00000000..19197a9d --- /dev/null +++ b/src/utils/validators/auth.ts @@ -0,0 +1,83 @@ +import { SigninRequest, SignUpFormData } from '@/api/auth/type'; +import { AuthValidateProps } from './type'; + +export const signUpValidate = ({ + value, + name, + password, +}: AuthValidateProps) => { + switch (name) { + case 'email': + return emailValidation(value); + case 'password': + return passwordValidation(value); + case 'passwordCheck': + return passwordCheckValidation(value, password ?? ''); + case 'name': + return nameValidation(value); + case 'companyName': + return favoriteValidation(value); + } +}; + +const emailValidation = (email: string): string | true => { + if (!email) { + return '이메일을 입력해주세요'; + } + if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email)) { + return '이메일 형식이 올바르지 않습니다'; + } + return true; +}; + +const passwordValidation = (password: string): string | true => { + if (!password) { + return '비밀번호를 입력해주세요'; + } + if (password.length < 8) { + return '비밀번호는 8자 이상이 되도록 해 주세요'; + } + if (!/^[^\s]{8,}$/.test(password)) { + return '비밀번호는 숫자와 문자로 이루어진 8자 이상이 되도록 해 주세요'; + } + return true; +}; + +const passwordCheckValidation = ( + passwordCheck: string, + password: string +): string | true => { + if (!passwordCheck) { + return '비밀번호를 다시 한 번 입력해주세요'; + } + if (passwordCheck !== password) { + return '비밀번호가 일치하지 않습니다'; + } + return true; +}; + +const nameValidation = (name: string): string | true => { + if (!name) { + return '닉네임을 입력해주세요'; + } + return true; +}; + +const favoriteValidation = (favorite: string): string | true => { + if (!favorite) { + return '좋아하는 작품을 입력해주세요'; + } + return true; +}; + +export const signInValidate = ({ + value, + name, +}: AuthValidateProps) => { + switch (name) { + case 'email': + return emailValidation(value); + case 'password': + return passwordValidation(value); + } +}; diff --git a/src/utils/validators/type.ts b/src/utils/validators/type.ts new file mode 100644 index 00000000..a797ab80 --- /dev/null +++ b/src/utils/validators/type.ts @@ -0,0 +1,7 @@ +import { SignUpFormData, SigninRequest } from '@/api/auth/type'; + +export interface AuthValidateProps { + value: string; + name: keyof T; + password?: string; +}