diff --git a/frontend/playwright/tests/mock/signIn.spec.ts b/frontend/playwright/tests/mock/signIn.spec.ts new file mode 100644 index 000000000..e79e9d29d --- /dev/null +++ b/frontend/playwright/tests/mock/signIn.spec.ts @@ -0,0 +1,21 @@ +import { test } from '@playwright/test'; + +import { ROUTE_PATH } from '@/constants/routePath'; + +test('이메일을 통해 로그인을 할 수 있다.', async ({ page }) => { + await page.goto(ROUTE_PATH.signIn); + await page.getByText('비밀번호를 잊으셨나요?').click(); + await page.getByLabel('이메일').click(); + await page.getByLabel('이메일').fill('test@test.com'); + await page.getByRole('button', { name: '전송' }).click(); + await page.getByLabel('다음').click(); + await page.getByLabel('검증 코드').click(); + await page.getByLabel('검증 코드').fill('test@test.com'); + await page.getByRole('button', { name: '확인' }).click(); + await page.getByLabel('다음').click(); + await page.getByLabel('새 비밀번호', { exact: true }).click(); + await page.getByLabel('새 비밀번호', { exact: true }).fill('wagd12'); + await page.getByLabel('새 비밀번호', { exact: true }).press('Tab'); + await page.getByLabel('새 비밀번호 확인').fill('wagd12'); + await page.getByRole('button', { name: '확인' }).click(); +}); diff --git a/frontend/src/apis/url.ts b/frontend/src/apis/url.ts index cdd497cff..7bf577230 100644 --- a/frontend/src/apis/url.ts +++ b/frontend/src/apis/url.ts @@ -14,6 +14,7 @@ export const ENDPOINT = { CHECKLIST_CUSTOM: '/custom-checklist', CHECKLIST_ID: (id: number) => `/checklists/${id}`, CHECKLIST_ID_V1: (id: number) => `/v1/checklists/${id}`, + // like LIKE: (id: number | ':id') => `/checklists/${id}/like`, // category @@ -35,6 +36,9 @@ export const ENDPOINT = { USER_VALID: '/token-exist', USER_ACCESS_TOKEN_REISSUE: '/accessToken/reissue', TOKEN: '/token', + RESET_PASSWORD_SEND_MAIL: '/v1/password-reset/send-code', + RESET_PASSWORD_CONFIRM_CODE: '/v1/password-reset/confirm', + RESET_PASSWORD: '/v1/password-reset', //subway SUBWAY: (position: Position) => `/stations/nearest?latitude=${position.latitude}&longitude=${position.longitude}`, }; diff --git a/frontend/src/apis/user.ts b/frontend/src/apis/user.ts index 2b2fd59b1..f29479b76 100644 --- a/frontend/src/apis/user.ts +++ b/frontend/src/apis/user.ts @@ -1,6 +1,6 @@ import fetcher from '@/apis/fetcher'; import { BASE_URL, ENDPOINT } from '@/apis/url'; -import { User, UserTokenValid } from '@/types/user'; +import { ResetPasswordArgs, User, UserTokenValid } from '@/types/user'; export const postOAuthLogin = async (code: string, redirectUri: string) => { const response = await fetcher.post({ url: BASE_URL + ENDPOINT.OAUTH_LOGIN, body: { code, redirectUri } }); @@ -47,7 +47,6 @@ export const postSignUp = async ({ name, email, password }: { name: string; emai method: 'POST', body: JSON.stringify({ name, email, password }), credentials: 'include', - headers: { 'Content-Type': 'application/json' }, }); }; @@ -56,6 +55,17 @@ export const postSignIn = async ({ email, password }: { email: string; password: url: `${BASE_URL}${ENDPOINT.SIGN_IN}`, body: { email, password }, credentials: 'include', - headers: { 'Content-Type': 'application/json' }, }); }; + +export const postResetPasswordMail = async (email: ResetPasswordArgs['email']) => { + return await fetcher.post({ url: `${BASE_URL}${ENDPOINT.RESET_PASSWORD_SEND_MAIL}`, body: { email } }); +}; + +export const postResetPasswordCode = async ({ email, code }: Pick) => { + return await fetcher.post({ url: `${BASE_URL}${ENDPOINT.RESET_PASSWORD_CONFIRM_CODE}`, body: { email, code } }); +}; + +export const postResetPassword = async ({ email, code, newPassword }: ResetPasswordArgs) => { + return await fetcher.post({ url: `${BASE_URL}${ENDPOINT.RESET_PASSWORD}`, body: { email, code, newPassword } }); +}; diff --git a/frontend/src/components/ResetPassword/EnterVerificationCodeStep.tsx b/frontend/src/components/ResetPassword/EnterVerificationCodeStep.tsx new file mode 100644 index 000000000..5154730ab --- /dev/null +++ b/frontend/src/components/ResetPassword/EnterVerificationCodeStep.tsx @@ -0,0 +1,94 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { BangBangIcon, BangGgoodTextIcon } from '@/assets/assets'; +import Button from '@/components/_common/Button/Button'; +import FlexBox from '@/components/_common/FlexBox/FlexBox'; +import FormField from '@/components/_common/FormField/FormField'; +import Header from '@/components/_common/Header/Header'; +import CS from '@/components/ResetPassword/style'; +import { ROUTE_PATH } from '@/constants/routePath'; +import usePostResetPasswordCode from '@/hooks/query/usePostResetPasswordCode'; +import useValidateInput from '@/hooks/useValidateInput'; +import { ResetPasswordArgs } from '@/types/user'; + +interface Props { + args: Pick; + onNext: (value: Pick) => void; +} + +const EmailVerificationCodeStep = ({ args: { email }, onNext }: Props) => { + const [isComplete, setIsComplete] = useState(false); + const [postErrorMessage, setPostErrorMessage] = useState(''); + const { mutate: postResetCode } = usePostResetPasswordCode(); + + const { + value: code, + getErrorMessage: getCodeErrors, + onChange: onChangeCode, + isValidated: isCodeValid, + } = useValidateInput({ + initialValue: '', + validates: [], + }); + + const handleClickSubmit = () => + postResetCode( + { email, code }, + { + onSuccess: () => setIsComplete(true), + onError: error => setPostErrorMessage(error.message), + }, + ); + + const handleClickNext = () => onNext({ code, email }); + + const canMove = isCodeValid && isComplete; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' && canMove) { + onNext({ code, email }); + } + }; + + const navigate = useNavigate(); + const handleClickBackward = () => navigate(ROUTE_PATH.root); + + return ( + <> +
} /> + + + + + + + 비밀번호 찾기 + + + + +
+ + 확인 + +
+
+ {getCodeErrors() && } +
+ {postErrorMessage && } +