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/checklist.ts b/frontend/src/apis/checklist.ts index 7e18eca45..5bf9e96f8 100644 --- a/frontend/src/apis/checklist.ts +++ b/frontend/src/apis/checklist.ts @@ -28,7 +28,7 @@ export const getChecklists = async (isLikeFiltered: boolean = false) => { url: BASE_URL + (isLikeFiltered ? ENDPOINT.CHECKLISTS_LIKE_V1 : ENDPOINT.CHECKLISTS_V1), }); const data = await response.json(); - return data.checklists.map(mapObjNullToUndefined).slice(0, 10); + return data.checklists.map(mapObjNullToUndefined); }; export const postChecklist = async (checklist: ChecklistPostForm) => { 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/ChecklistDetail/RoomInfoSection.tsx b/frontend/src/components/ChecklistDetail/RoomInfoSection.tsx index f6d36a963..ed0471218 100644 --- a/frontend/src/components/ChecklistDetail/RoomInfoSection.tsx +++ b/frontend/src/components/ChecklistDetail/RoomInfoSection.tsx @@ -16,7 +16,7 @@ import LikeButton from '@/components/_common/Like/LikeButton'; import AddressMap from '@/components/_common/Map/AddressMap'; import SubwayStations from '@/components/_common/Subway/SubwayStations'; import { IncludedMaintenancesData } from '@/constants/roomInfo'; -import { flexColumn, flexRow, flexSpaceBetween, title2, title3, title4 } from '@/styles/common'; +import { flexColumn, flexRow, flexSpaceBetween, title2, title3 } from '@/styles/common'; import { Option } from '@/types/option'; import { RoomInfo } from '@/types/room'; import { SubwayStation } from '@/types/subway'; @@ -88,10 +88,11 @@ const RoomInfoSection = ({ nearSubways, room, options, checklistId, isLiked }: P - 방 구조
/ 방 평수 + 방 구조
방 평수
- {formattedUndefined(structure, 'string')}
/ {formattedUndefined(size)} 평 + {formattedUndefined(structure, 'string')}
+ {formattedUndefined(size)} 평
@@ -121,10 +122,12 @@ const RoomInfoSection = ({ nearSubways, room, options, checklistId, isLiked }: P - 계약 기간
/ 입주 가능일 + 계약 기간
+ 입주 가능일
- {formattedUndefined(contractTerm)}개월 계약
/{formattedUndefined(occupancyMonth)}월 {occupancyPeriod} + {formattedUndefined(contractTerm)}개월 계약
+ {formattedUndefined(occupancyMonth)}월 {occupancyPeriod}
@@ -237,7 +240,6 @@ const S = { MoneyText: styled.div` width: 100%; - /* ${title4} */ ${title3} ${flexRow} ${flexSpaceBetween} diff --git a/frontend/src/components/Main/ArticlePreviewCard.tsx b/frontend/src/components/Main/ArticlePreviewCard.tsx index d7b0d99c9..fdb6a0978 100644 --- a/frontend/src/components/Main/ArticlePreviewCard.tsx +++ b/frontend/src/components/Main/ArticlePreviewCard.tsx @@ -17,7 +17,7 @@ const ArticlePreviewCard = ({ index, article }: Props) => { const navigate = useNavigate(); const { articleId, keyword, title } = article; - const { color600, color200 } = getSeqColor(index); + const { color500, color200 } = getSeqColor(index); const handleClickArticle = () => { navigate(ROUTE_PATH.articleOne(articleId)); @@ -31,12 +31,12 @@ const ArticlePreviewCard = ({ index, article }: Props) => { tabIndex={1} aria-label="클릭하면 해당 아티클 페이지로 이동합니다" > - + {keyword} {title} - ); diff --git a/frontend/src/components/NewChecklist/AddressModal/DaumAddressModal.tsx b/frontend/src/components/NewChecklist/AddressModal/DaumAddressModal.tsx index 8ba2024b2..3af7c99e4 100644 --- a/frontend/src/components/NewChecklist/AddressModal/DaumAddressModal.tsx +++ b/frontend/src/components/NewChecklist/AddressModal/DaumAddressModal.tsx @@ -24,9 +24,9 @@ const DaumAddressModal = () => { const { searchSubwayStationsByAddress } = useRoomInfoNonValidated(); - const handleAddress = () => { - openModal(); + const handleClickAddress = () => { loadExternalScriptWithCallback('daumAddress', openPostcodeEmbed); + openModal(); }; const openPostcodeEmbed = () => { @@ -50,7 +50,7 @@ const DaumAddressModal = () => { return ( <> { - // TODO : nonValidated 에서 관리해야함. 일단은 놔뒀음. + // TODO: nonValidated 에서 관리해야함. 일단은 놔뒀음. const includedMaintenances = useStore(roomInfoStore, state => state.includedMaintenances).rawValue; const actions = useStore(roomInfoStore, state => state.actions); diff --git a/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomFloor.tsx b/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomFloor.tsx index 25caadd23..98dcd96b3 100644 --- a/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomFloor.tsx +++ b/frontend/src/components/NewChecklist/NewRoomInfoForm/RoomFloor.tsx @@ -15,6 +15,7 @@ const RoomFloor = () => { floor.set(''); } }; + return ( 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 && } +