diff --git a/src/app/(auth)/login/_components/LoginForm/OpenPasswordResetModal.tsx b/src/app/(auth)/login/_components/LoginForm/OpenPasswordResetModal.tsx index 1669e7a5..868eb3aa 100644 --- a/src/app/(auth)/login/_components/LoginForm/OpenPasswordResetModal.tsx +++ b/src/app/(auth)/login/_components/LoginForm/OpenPasswordResetModal.tsx @@ -4,13 +4,11 @@ import ResetPasswordModal from '@/components/common/Modal/content/ResetPasswordM import { postResetPasswordToEmail } from '@/lib/apis/user'; import { ResetPasswordToEmailBody } from '@/lib/apis/user/type'; import { useModalStore } from '@/store/useModalStore'; -import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { toast } from 'react-toastify'; export default function OpenPasswordResetModal({ ...props }) { const { openModal } = useModalStore(); - const router = useRouter(); useEffect(() => { const unsubscribe = useModalStore.subscribe( @@ -30,7 +28,7 @@ export default function OpenPasswordResetModal({ ...props }) { } ); return () => { - unsubscribe(); // 언마운트, 구독 해제 + unsubscribe(); }; }, []); @@ -49,10 +47,9 @@ export default function OpenPasswordResetModal({ ...props }) { if (error instanceof Error) { const errorMessage = error.message; - // 존재하지 않는 유저 : User not found + // User not found if (errorMessage.includes('User not found')) { toast.error('존재하지 않는 이메일입니다. 회원가입을 먼저 해주세요.'); - router.push('/signup'); } else { toast.error(`Error : ${error}`); } @@ -71,14 +68,13 @@ export default function OpenPasswordResetModal({ ...props }) { title: '비밀번호 재설정', description: '비밀번호 재설정 링크를 보내드립니다.', button: { - formId: 'reset-password-form', // formId 연결 + formId: 'reset-password-form', // form 연결 number: 2, text: '링크 보내기', // 기존 모달 컴포넌트의 onRequest 호출 방식과의 일관성을 위해 빈 함수 전달 onRequest: () => {}, }, }, - (() => )() ); }} diff --git a/src/app/(auth)/reset-password/ResetPasswordForm.tsx b/src/app/(auth)/reset-password/ResetPasswordForm.tsx index 402784a8..deefeb67 100644 --- a/src/app/(auth)/reset-password/ResetPasswordForm.tsx +++ b/src/app/(auth)/reset-password/ResetPasswordForm.tsx @@ -2,11 +2,15 @@ import InputWithLabel from '@/components/auth/InputWithLabel'; import Button from '@/components/common/Button'; +import { ROUTES } from '@/constants/routes'; +import { patchResetPassword } from '@/lib/apis/user'; import { validatePassword, validatePasswordConfirm, } from '@/utils/inputValidation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useMemo, useState } from 'react'; +import { toast } from 'react-toastify'; import { z } from 'zod'; // reset password schema @@ -20,7 +24,6 @@ const resetPasswordSchema = z const { password, passwordConfirm } = val; if (!password) { - // 비밀번호가 비어있음 ctx.addIssue({ path: ['password'], code: z.ZodIssueCode.custom, @@ -28,7 +31,6 @@ const resetPasswordSchema = z fatal: true, // 조건 실패 시, 유효성 검사 중단 }); } else if (!validatePassword(password)) { - // 비밀번호가 입력은 되었지만, 유효하지 않음 ctx.addIssue({ path: ['password'], code: z.ZodIssueCode.custom, @@ -38,7 +40,6 @@ const resetPasswordSchema = z } if (!passwordConfirm) { - // 비밀번호 확인이 비어있는 경우 ctx.addIssue({ path: ['passwordConfirm'], code: z.ZodIssueCode.custom, @@ -46,7 +47,6 @@ const resetPasswordSchema = z fatal: true, }); } else if (!validatePasswordConfirm({ password, passwordConfirm })) { - // 비밀번호 확인이 입력은 되었지만, 비밀번호와 일치하지 않음 ctx.addIssue({ path: ['passwordConfirm'], code: z.ZodIssueCode.custom, @@ -71,6 +71,9 @@ export default function ResetPasswordForm() { passwordConfirm: [], }); + const router = useRouter(); + const searchParams = useSearchParams(); + const isFormValid = useMemo(() => { return ( formValues.password !== '' && @@ -96,18 +99,52 @@ export default function ResetPasswordForm() { } else { setFormErrors((prev) => ({ ...prev, - [key]: '', + [key]: [], })); } setFormValues(newFormValues); }; - const handleFormSubmit = () => { - console.log('form submit'); + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!isFormValid) return; + + // formErrors 초기화 setFormErrors({ password: [], passwordConfirm: [], }); + + try { + const token = searchParams.get('token'); + // early return + if (!token) { + toast.error('유효하지 않은 링크입니다. 다시 시도해주세요.'); + return; + } + + const res = await patchResetPassword({ + body: { + passwordConfirmation: formValues.passwordConfirm, + password: formValues.password, + token, + }, + }); + console.log('res', res); + toast.success('비밀번호 재설정이 완료되었습니다.'); + router.push(ROUTES.LOGIN); + } catch (error) { + if (error instanceof Error) { + const errorMessage = error.message; + if (errorMessage.includes('유효하지 않은 토큰입니다.')) { + toast.error( + '비밀번호 재설정 링크카 만료되었습니다. 다시 요청해주세요.' + ); + } + console.error(errorMessage); + } + } }; return (
diff --git a/src/app/(auth)/reset-password/page.tsx b/src/app/(auth)/reset-password/page.tsx index b6cefca2..e2b41cdf 100644 --- a/src/app/(auth)/reset-password/page.tsx +++ b/src/app/(auth)/reset-password/page.tsx @@ -1,10 +1,17 @@ import ResetPasswordForm from '@/app/(auth)/reset-password/ResetPasswordForm'; +import { Suspense } from 'react'; export default function ResetPasswordPage() { return (
- + 로딩 중...
+ } + > + +
); diff --git a/src/components/common/Modal/index.tsx b/src/components/common/Modal/index.tsx index c35f3fd2..4b790d66 100644 --- a/src/components/common/Modal/index.tsx +++ b/src/components/common/Modal/index.tsx @@ -37,7 +37,6 @@ export default function Modal() { button?.onRequest?.(requestBody); closeModal(); isSubmittedRef.current = false; - console.log('handleRequest 실행'); return; } } diff --git a/src/lib/apis/user/index.ts b/src/lib/apis/user/index.ts index 55627af7..c8d9ba65 100644 --- a/src/lib/apis/user/index.ts +++ b/src/lib/apis/user/index.ts @@ -102,7 +102,7 @@ export async function postResetPasswordToEmail({ }); } -// 이메일로 전달받은 링크에서 비밀번호 초기화 (PATCH /user/reset-password) +// 비밀번호 재설정 페이지 : 이메일로 전달받은 링크에서 비밀번호 초기화 (PATCH /user/reset-password) export async function patchResetPassword({ body, }: { @@ -115,7 +115,7 @@ export async function patchResetPassword({ }); } -// 비밀번호 변경 (PATCH /user/password) +// 계정 설정 페이지 : 비밀번호 변경 (PATCH /user/password) export async function patchPassword({ body, }: {