Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -30,7 +28,7 @@ export default function OpenPasswordResetModal({ ...props }) {
}
);
return () => {
unsubscribe(); // 언마운트, 구독 해제
unsubscribe();
};
}, []);

Expand All @@ -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}`);
}
Expand All @@ -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: () => {},
},
},

(() => <ResetPasswordModal />)()
);
}}
Expand Down
51 changes: 44 additions & 7 deletions src/app/(auth)/reset-password/ResetPasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -20,15 +24,13 @@ const resetPasswordSchema = z
const { password, passwordConfirm } = val;

if (!password) {
// 비밀번호가 비어있음
ctx.addIssue({
path: ['password'],
code: z.ZodIssueCode.custom,
message: '비밀번호는 필수 입력입니다.',
fatal: true, // 조건 실패 시, 유효성 검사 중단
});
} else if (!validatePassword(password)) {
// 비밀번호가 입력은 되었지만, 유효하지 않음
ctx.addIssue({
path: ['password'],
code: z.ZodIssueCode.custom,
Expand All @@ -38,15 +40,13 @@ const resetPasswordSchema = z
}

if (!passwordConfirm) {
// 비밀번호 확인이 비어있는 경우
ctx.addIssue({
path: ['passwordConfirm'],
code: z.ZodIssueCode.custom,
message: '비밀번호 확인은 필수 입력입니다.',
fatal: true,
});
} else if (!validatePasswordConfirm({ password, passwordConfirm })) {
// 비밀번호 확인이 입력은 되었지만, 비밀번호와 일치하지 않음
ctx.addIssue({
path: ['passwordConfirm'],
code: z.ZodIssueCode.custom,
Expand All @@ -71,6 +71,9 @@ export default function ResetPasswordForm() {
passwordConfirm: [],
});

const router = useRouter();
const searchParams = useSearchParams();

const isFormValid = useMemo(() => {
return (
formValues.password !== '' &&
Expand All @@ -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<HTMLFormElement>) => {
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 (
<form onSubmit={handleFormSubmit}>
Expand Down
9 changes: 8 additions & 1 deletion src/app/(auth)/reset-password/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import ResetPasswordForm from '@/app/(auth)/reset-password/ResetPasswordForm';
import { Suspense } from 'react';

export default function ResetPasswordPage() {
return (
<div className="flex h-[calc(100vh-60px)] justify-center pt-[140px]">
<div className="flex flex-col">
<ResetPasswordForm />
<Suspense
fallback={
<div className="py-10 text-center text-slate-400">로딩 중...</div>
}
>
<ResetPasswordForm />
</Suspense>
</div>
</div>
);
Expand Down
1 change: 0 additions & 1 deletion src/components/common/Modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export default function Modal() {
button?.onRequest?.(requestBody);
closeModal();
isSubmittedRef.current = false;
console.log('handleRequest 실행');
return;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/apis/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export async function postResetPasswordToEmail({
});
}

// 이메일로 전달받은 링크에서 비밀번호 초기화 (PATCH /user/reset-password)
// 비밀번호 재설정 페이지 : 이메일로 전달받은 링크에서 비밀번호 초기화 (PATCH /user/reset-password)
export async function patchResetPassword({
body,
}: {
Expand All @@ -115,7 +115,7 @@ export async function patchResetPassword({
});
}

// 비밀번호 변경 (PATCH /user/password)
// 계정 설정 페이지 : 비밀번호 변경 (PATCH /user/password)
export async function patchPassword({
body,
}: {
Expand Down