Skip to content

Commit

Permalink
feat: 패스워드, 아이디 찾기 (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
jijiseong authored Jul 15, 2024
1 parent ac6f07a commit 545950a
Show file tree
Hide file tree
Showing 15 changed files with 419 additions and 90 deletions.
71 changes: 71 additions & 0 deletions app/(route)/find/id/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client';

import Spinner from '@app/_components/Spinner';
import { NEW_PATH } from '@app/_constants/urls';
import useFindIdMutation from '@app/_hooks/apis/user/useFindIdMutation';
import {
Alert,
AlertDescription,
AlertTitle,
} from '@app/_shadcn/components/ui/alert';
import { Button, buttonVariants } from '@app/_shadcn/components/ui/button';
import { Input } from '@app/_shadcn/components/ui/input';
import { cn } from '@app/_shadcn/lib/utils';
import { Terminal } from 'lucide-react';
import Link from 'next/link';
import { SubmitHandler, useForm } from 'react-hook-form';

function Page() {
const { mutate, isPending } = useFindIdMutation();
const { register, handleSubmit } = useForm<{ email: string }>();

const onSubmitValid: SubmitHandler<{
email: string;
}> = ({ email }) => {
mutate(email);
};

return (
<section className="small-center absolute-center flex flex-col gap-4">
<Alert>
<Terminal className="size-4" />
<AlertTitle>ID 찾기</AlertTitle>
<AlertDescription>
이메일로 가입하신 ID를 전송해드릴게요.
</AlertDescription>
</Alert>
<form
onSubmit={handleSubmit(onSubmitValid)}
className=" flex flex-col gap-4"
>
<Input
type="email"
label="이메일"
placeholder="가입한 이메일을 입력해주세요."
{...register('email', { required: true })}
/>

<Button type="submit" disabled={isPending}>
{isPending && <Spinner className="mr-2" />}
ID 찾기{' '}
</Button>
<div className="flex justify-between gap-2">
<Link
className={cn(buttonVariants({ variant: 'secondary' }), 'w-full')}
href={NEW_PATH.passwordFind.url}
>
비밀번호 찾으러가기
</Link>
<Link
className={cn(buttonVariants({ variant: 'secondary' }), 'w-full')}
href={NEW_PATH.login.url}
>
로그인 하러가기
</Link>
</div>
</form>
</section>
);
}

export default Page;
70 changes: 70 additions & 0 deletions app/(route)/find/password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client';

import Spinner from '@app/_components/Spinner';
import { NEW_PATH } from '@app/_constants/urls';
import useFindPasswordMutation from '@app/_hooks/apis/user/useFindPasswordMutation';
import {
Alert,
AlertDescription,
AlertTitle,
} from '@app/_shadcn/components/ui/alert';
import { Button, buttonVariants } from '@app/_shadcn/components/ui/button';
import { Input } from '@app/_shadcn/components/ui/input';
import { cn } from '@app/_shadcn/lib/utils';
import { Terminal } from 'lucide-react';
import Link from 'next/link';
import { SubmitHandler, useForm } from 'react-hook-form';

function Page() {
const { mutate, isPending } = useFindPasswordMutation();
const { register, handleSubmit } = useForm<{ email: string }>();

const onSubmitValid: SubmitHandler<{
email: string;
}> = ({ email }) => {
mutate(email);
};

return (
<section className="small-center absolute-center flex flex-col gap-4">
<Alert>
<Terminal className="size-4" />
<AlertTitle>비밀번호 찾기</AlertTitle>
<AlertDescription>
가입하신 이메일로 초기화된 비밀번호를 전송해드릴게요.
</AlertDescription>
</Alert>

<form
onSubmit={handleSubmit(onSubmitValid)}
className="flex flex-col gap-4"
>
<Input
label="이메일"
placeholder="가입한 이메일을 입력해주세요."
{...register('email', { required: true })}
/>
<Button type="submit" disabled={isPending}>
{isPending && <Spinner className="mr-2" />}
비밀번호 초기화하기
</Button>
<div className="flex justify-between gap-2">
<Link
className={cn(buttonVariants({ variant: 'secondary' }), 'w-full')}
href={NEW_PATH.idFind.url}
>
ID 찾으러가기
</Link>
<Link
className={cn(buttonVariants({ variant: 'secondary' }), 'w-full')}
href={NEW_PATH.login.url}
>
로그인 하러가기
</Link>
</div>
</form>
</section>
);
}

export default Page;
8 changes: 7 additions & 1 deletion app/(route)/login/_components/LoginMoreLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PATH } from '@app/_constants/urls';
import { NEW_PATH, PATH } from '@app/_constants/urls';
import { Button } from '@app/_shadcn/components/ui/button';
import Link from 'next/link';

Expand All @@ -8,6 +8,12 @@ function LoginMoreLinks() {
<Button asChild variant="link">
<Link href={PATH.user.join.url}>회원가입</Link>
</Button>
<Button asChild variant="link">
<Link href={NEW_PATH.idFind.url}>아이디 찾기</Link>
</Button>
<Button asChild variant="link">
<Link href={NEW_PATH.passwordFind.url}>비밀번호 찾기</Link>
</Button>
</div>
);
}
Expand Down
16 changes: 11 additions & 5 deletions app/(route)/mypage/_components/AccountSettingSection.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
'use client';

import ButtonWithDialogCheck from '@app/_components/common/WithDialogCheck';
import { NEW_PATH } from '@app/_constants/urls';
import useWithdrawalMutation from '@app/_hooks/apis/user/useWithdrawalMutation';
import { Button } from '@app/_shadcn/components/ui/button';
import { buttonVariants } from '@app/_shadcn/components/ui/button';
import { cn } from '@app/_shadcn/lib/utils';
import { myProfileState } from '@app/_store/permissionAtoms';
import Link from 'next/link';
import { useRecoilValue } from 'recoil';

function AccountSettingSection() {
Expand All @@ -19,9 +22,12 @@ function AccountSettingSection() {

return (
<section className="flex justify-end gap-4">
<Button className="w-full" variant="secondary">
비밀번호 변경(개발 중이에요!)
</Button>
<Link
href={NEW_PATH.passwordUpdate.url}
className={cn(buttonVariants({ variant: 'secondary' }), 'w-full')}
>
비밀번호 변경하러 가기
</Link>
<ButtonWithDialogCheck
title="회원 탈퇴"
description="정말 탈퇴하시겠어요?"
Expand All @@ -30,7 +36,7 @@ function AccountSettingSection() {
variant="destructive"
onClick={onWithdrawalClick}
>
회원 탈퇴
회원 탈퇴하기
</ButtonWithDialogCheck>
</section>
);
Expand Down
6 changes: 3 additions & 3 deletions app/(route)/mypage/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ function Layout({ children }: Props) {
if (!isLoggedIn) return redirect(PATH.user.login.url);

return (
<div className="small-center pb-[200px]">
<>
<PageTitle pageTitle={PAGE_TITLE.myPage} />
{children}
</div>
<div className="small-center relative pb-[200px]">{children}</div>
</>
);
}

Expand Down
83 changes: 83 additions & 0 deletions app/(route)/mypage/password-update/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use client';

import { INPUT_ERROR, REQUIRED_MESSAGE } from '@app/_constants/message';
import useUpdatePasswordMutation from '@app/_hooks/apis/user/useUpdatePassworMutation';
import { Button } from '@app/_shadcn/components/ui/button';
import { Input } from '@app/_shadcn/components/ui/input';
import { PW_REGEX } from '@app/_utils/regex';
import { SubmitHandler, useForm } from 'react-hook-form';

interface UpdatePasswordForm {
newPassword: string;
newPasswordConfirm: string;
}

function Page() {
const {
register,
handleSubmit,
getValues,
formState: { errors },
} = useForm<UpdatePasswordForm>();

const { mutate } = useUpdatePasswordMutation();

const newPasswordRegister = register('newPassword', {
required: REQUIRED_MESSAGE.pw,
pattern: { value: PW_REGEX, message: INPUT_ERROR.pw },
});

const newPasswordConfirmRegister = register('newPasswordConfirm', {
required: REQUIRED_MESSAGE.pwConfirm,
validate: (newPasswordConfirm) => {
if (getValues('newPassword') !== newPasswordConfirm)
return INPUT_ERROR.pwConfirm;
return true;
},
});

const onSubmitValid: SubmitHandler<UpdatePasswordForm> = ({
newPassword,
}) => {
mutate(newPassword);
};

return (
<form
className="flex flex-col gap-4"
onSubmit={handleSubmit(onSubmitValid)}
>
<div>
<Input
className="mb-2"
label="새 비밀번호"
placeholder="새 비밀번호를 입력해 주세요."
type="password"
{...newPasswordRegister}
/>
<span className="text-sm text-destructive">
{errors.newPassword?.message}
</span>
</div>

<div>
<Input
className="mb-2"
label="새 비밀번호 확인"
placeholder="새 비밀번호를 한 번 더 입력해 주세요."
type="password"
{...newPasswordConfirmRegister}
/>
<span className="text-sm text-destructive">
{errors.newPasswordConfirm?.message}
</span>
</div>

<Button className="mt-2" type="submit">
변경하기
</Button>
</form>
);
}

export default Page;
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ function DetailDescription({
return (
<section className="flex w-full flex-col gap-12">
<DetailRow title="정보">
<div className="grid grid-cols-3">
<div className="grid grid-cols-3 gap-x-2">
<span className="text-xs font-thin">이름</span>
<span className="text-xs font-thin">닉네임</span>
<span className="truncate text-xs font-thin">닉네임</span>
<span className="text-xs font-thin">활동 상태</span>

<span>{name}</span>
<span>{nickname}</span>
<span>{ROLE[role]}</span>
<span className="truncate">{name}</span>
<span className="truncate">{nickname}</span>
<span className="truncate">{ROLE[role]}</span>
</div>
</DetailRow>

Expand Down
6 changes: 3 additions & 3 deletions app/_constants/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const INPUT_ERROR = {
noPermission: '권한이 없어요.',
requiredLogin: '로그인이 필요해요.',
id: 'ID 형식이 올바르지 않아요.',
pw: 'PW 형식이 올바르지 않아요. [8자리 이상 + 특수문자 1개 이상]',
pw: '비밀번호 형식이 올바르지 않아요. [8자리 이상 + 특수문자 1개 이상]',
pwConfirm: 'PW가 일치하지 않습니다.',
name: '이름이 이상합니다.',
nickname: '닉네임이 이상합니다.',
Expand All @@ -38,8 +38,8 @@ export const INPUT_ERROR = {

export const REQUIRED_MESSAGE = {
id: 'ID를 입력해 주세요.',
pw: 'PW를 입력해 주세요.',
pwConfirm: 'PW를 한번 더 입력해주세요.',
pw: '비밀번호를 입력해 주세요.',
pwConfirm: '비밀번호를 한 번 더 입력해 주세요.',
name: '이름을 입력해 주세요.',
nickname: '닉네임을 입력해 주세요.',
email: '이메일을 입력해 주세요.',
Expand Down
9 changes: 9 additions & 0 deletions app/_constants/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ const NEW_PATH = {
category: string;
}) => `/boards/${boardType}/list/${category}/${page}`,
},
passwordUpdate: { url: '/mypage/password-update' },
passwordFind: {
url: '/find/password',
},
idFind: {
url: '/find/id',
},
login: { url: '/login' },
mypage: { url: '/mypage' },
};

const PATH = {
Expand Down
23 changes: 23 additions & 0 deletions app/_hooks/apis/user/useFindIdMutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import loginService from '@app/_service/loginService';
import { useToast } from '@app/_shadcn/components/ui/use-toast';
import { useMutation } from '@tanstack/react-query';

function useFindIdMutation() {
const { toast } = useToast();

return useMutation({
mutationFn: (email: string) => loginService.findId(email),
onSuccess: () =>
toast({
description: '이메일로 가입한 아이디를 전송했어요.',
}),

onError: () =>
toast({
variant: 'destructive',
description: '가입한 이메일을 찾을 수 없어요.',
}),
});
}

export default useFindIdMutation;
Loading

0 comments on commit 545950a

Please sign in to comment.