Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
.env
21 changes: 21 additions & 0 deletions src/app/reset-password/[token]/_actions/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use server';

import { ActionStatus } from '@/enums/ActionStatus';

export const requestTokenValidation = async (token: string) => {
try {
// api 개발 완료되면 엔드 포인트 추가 예정
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token }),
});
Comment on lines +8 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에는 Fetcher로 따로 빼서 Endpoint만 신경쓸 수 있게 해도 좋을 것 같아요


return { status: ActionStatus.Success, fields: response.json() };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

응답 자체가 해당 방식으로 오는 거에요, 따로 호출하는 API에서 지정해주는 게 아니구요.
그리고 fields로 담기는 게 아니고 payload 안에 필요한 fields가 담겨오는 거구요.
ActionStatus는 헷갈렸을 것 같긴 한데 백엔드랑 회의 전에 제가 임의로 설정해놓은 거라 enum의 형태라 현재 공유해놓은 응답 Status랑은 값이 달라요.
image

} catch (error) {
alert('토큰 확인 요망 👾');
return { status: ActionStatus.Error, issues: [error] };
}
Comment on lines +17 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떨 때 catch로 들어오는지 알고 쓰신 건 아닌 것 같아서, fetch의 Error Handling에 대해서 알아볼 필요가 있을 것 같아요.
여기에 잡히는 catch는 네트워크 레벨에서 발생하는 오류이거나 CORS 오류가 오게 되고, 400, 500으로 오는 건 잡히지 않아요.

그래서 response의 status값을 확인해서(주로 response.ok) 에러에 따라 throw를 던져주는 형태가 되어야 합니다.

try {
  const res = await fetch("~");

  if (!res.ok) {
    // 아니면 응답에서 확인한다면 json으로 serialize하고 status를 확인
    if (res.status === "400") {
      throw new Error("404 Error")
    }

   if (res.status === "500") {
     throw new Error("500 Error")
   }

    throw new Error(res.status)
  }

  return res.json()
} catch (e) {
  
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

물론 이것도 Fetcher를 만들어서 따로 수정하면 되겠지만, 관련 내용은 알고 계시는 게 좋을 것 같아서~

};
8 changes: 6 additions & 2 deletions src/app/reset-password/[token]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ import Link from 'next/link';
import ResetPasswordForm from '../_forms/ResetPasswordForm';
import { HeadingWithDescription } from '@/components/HeadingWithDescription/HeadingWithDescription';
import { redirect } from 'next/navigation';
import { requestTokenValidation } from './_actions/actions';

export default async function Page({ params: { token } }: { params: { token: string } }) {
const response = await requestTokenValidation(token);
const { email } = await response.fields;
Comment on lines +9 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러 처리가 안 되어 있어요. fetch 과정에서 에러가 발생하면 작성하신 코드상으론 fields가 존재하지 않으므로 undefined가 반환될 텐데, undefined에서 email을 구조분해 하려고 하니 오류가 발생하게 될 거에요 ^~^


export default function Page() {
const handleSuccess = async () => {
'use server';

Expand All @@ -21,7 +25,7 @@ export default function Page() {

<HeadingWithDescription heading="비밀번호 재설정" description="변경할 비밀번호를 입력해주세요." />

<ResetPasswordForm onSuccess={handleSuccess} />
<ResetPasswordForm onSuccess={handleSuccess} email={email} />
</section>
);
}
8 changes: 8 additions & 0 deletions src/app/reset-password/_forms/ResetPasswordForm.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ export async function resetPassword(prevState: FormState, data: FormData): Promi
setTimeout(() => resolve(null), 1500);
});

// const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/password/confirm`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify(Object.fromEntries(data)),
// });

// return { status: ActionStatus.Error, issues: ['에러여 에러'] };
return { status: ActionStatus.Success, fields: Object.fromEntries(data) as Record<string, string> };
}
30 changes: 25 additions & 5 deletions src/app/reset-password/_forms/ResetPasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,26 @@ import { Control, useForm } from 'react-hook-form';
import { z } from 'zod';
import { resetPassword } from './ResetPasswordForm.action';

const formSchema = z.object({
password: z.string(),
passwordConfirm: z.string(),
});
const passwordRegex = new RegExp(/^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$/);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맨 마지막 정규식은 앞단 min()과 max()로 처리해주고 있는데, 그럼에도 필요한 정규식일까요~?


const formSchema = z
.object({
password: z
.string()
.min(8, { message: '비밀번호는 최소 8자 이상이어야 합니다.' })
.max(15, { message: '비밀번호는 최대 15자 이내이어야 합니다.' })
.regex(passwordRegex, { message: '영문, 숫자, 특수문자 포함해서 머시기해라.' }),
passwordConfirm: z.string(),
})
.superRefine(({ password, passwordConfirm }, ctx) => {
if (password !== passwordConfirm) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: '비밀번호가 일치하지 않네요.',
path: ['passwordConfirm'],
});
}
});

export type ResetPasswordRequest = z.infer<typeof formSchema>;

Expand All @@ -25,9 +41,10 @@ const initialValues: ResetPasswordRequest = {

type ResetPasswordFormProps = {
onSuccess: () => void;
email: string;
};

export default function ResetPasswordForm({ onSuccess }: ResetPasswordFormProps) {
export default function ResetPasswordForm({ onSuccess, email }: ResetPasswordFormProps) {
const [isPending, startTransition] = useTransition();

const [state, setState] = useState<FormState>({
Expand All @@ -52,6 +69,9 @@ export default function ResetPasswordForm({ onSuccess }: ResetPasswordFormProps)

const formData = new FormData(formRef.current);

// input hidden 대신 email 값을 formData에 포함시켰어요.
formData.append('email', email);
Comment on lines +72 to +73
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

관련해서 디자인이 바뀔 것 같아서 input hidden으로 해놓는 게 좋을 것 같다는 거였는데, 제가 말씀을 못드렸네요😇
가져온 이메일을 disabled로 input에 채울 예정이에요. 관련해서는 나중에 따로 디자인 수정 후 알려드릴게요.


setState({
status: ActionStatus.Idle,
fields: { ...(Object.fromEntries(formData) as Record<string, string>) },
Expand Down