Skip to content

Commit

Permalink
로그인 > 비밀번호 재설정 API 연동 (#239)
Browse files Browse the repository at this point in the history
* ✨ #234 - URL query로부터 데이터 획득

* ✨ #234 - 비밀번호 재설정 API_PATH 추가

* ✨ #234 - 비밀번호 재설정 API 추가

* ✨ #234 - 비밀번호 재설정 API 연결

* ✨#234 - queryParam을 항상 string[]로 반환하는 util 함수 추가

* 🎨:#234 - getQueryParam 통해 query param 가져오도록 변경

* 🏷️:#234 - TempTokenValidation response, request 타입 추가

* ✨:#234 - tempTokenValidation api path 추가

* ✨:#234 - tempTokenValidation api 추가

* ✨:#234 - 비밀번호 재설정 페이지 접근 전 email과 token이 매칭되지 확인

* ♻️#234 - api import 방식 변경

* 💡#234 - 주석 추가

* 🎨#234 - 네이밍 및 구조 개선

* 🔧#234 - eslint @typescript-eslint/strict-boolean-expressions rule 추가

* 🏷️#234 - InferGetServerSidePropsType 적용

* 🏷️#234 axios 응답 타입 적용
  • Loading branch information
sohyeonAn authored Jun 21, 2024
1 parent 8fe1270 commit 96d9673
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 13 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
],
"alphabetize": { "order": "asc" }
}
],
"@typescript-eslint/strict-boolean-expressions": [
"error",
{
"allowNullableString": true
}
]
},
"settings": {
Expand Down
35 changes: 34 additions & 1 deletion src/api/users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { LoginRequest, LoginResponse } from 'types/login';
import type { PasswordResetLinkRequest } from 'types/password';
import type {
PasswordResetLinkRequest,
PasswordResetRequest,
TempTokenValidationRequest,
TempTokenValidationResponse,
} from 'types/password';
import type { ExistsRequest, RegisterRequest } from 'types/register';
import type { OnlyMessageResponse, SuccessResponse } from 'types/response';
import { API_PATH } from 'constants/services';
Expand Down Expand Up @@ -58,3 +63,31 @@ export const passwordResetLink = async ({
{ email, redirectUrl },
);
};

export const resetPassword = async ({
email,
tempToken,
password,
}: PasswordResetRequest) => {
return await axios.put<SuccessResponse<OnlyMessageResponse>>(
API_PATH.users.password,
{
email,
tempToken,
password,
},
);
};

export const tempTokenValidation = async ({
email,
tempToken,
}: TempTokenValidationRequest) => {
return await axios.post<SuccessResponse<TempTokenValidationResponse>>(
API_PATH.users.tempTokenValidation,
{
email,
tempToken,
},
);
};
4 changes: 2 additions & 2 deletions src/components/account/FindPasswordForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Dispatch, SetStateAction } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import type { PasswordFindForm } from 'types/password';
import type { ErrorResponse } from 'types/response';
import { passwordResetLink } from 'api';
import * as api from 'api';
import { Button } from 'components/common';
import { FormInput } from 'components/form';
import { ERROR_MESSAGE, VALID_VALUE } from 'constants/validation';
Expand All @@ -25,7 +25,7 @@ export const FindPasswordForm = ({ setIsSubmitted }: FindPasswordFormProps) => {
const onSubmit: SubmitHandler<PasswordFindForm> = async (data) => {
try {
const { email } = data;
await passwordResetLink({
await api.passwordResetLink({
email,
redirectUrl: `${window.location.origin}/account/resetPassword`,
});
Expand Down
31 changes: 24 additions & 7 deletions src/components/account/ResetPasswordForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import styled from '@emotion/styled';
import { isAxiosError } from 'axios';
import router from 'next/router';
import { useForm } from 'react-hook-form';
import type { SubmitHandler } from 'react-hook-form';
import type { PasswordResetForm } from 'types/password';
import type { ErrorResponse } from 'types/response';
import * as api from 'api';
import { Button } from 'components/common';
import { FormInput } from 'components/form';
import { PAGE_PATH } from 'constants/common';
Expand All @@ -11,20 +15,33 @@ import {
VALID_VALUE,
} from 'constants/validation';

export const ResetPasswordForm = () => {
interface ResetPasswordFormProps {
email: string;
token: string;
}

export const ResetPasswordForm = ({ email, token }: ResetPasswordFormProps) => {
const {
register,
getValues,
handleSubmit,
formState: { isValid, errors, isSubmitting },
} = useForm<PasswordResetForm>({ mode: 'onChange' });

const onSubmit = async () => {
/**
* @todo
* 비밀번호 재설정 API 요청
*/
await router.replace(PAGE_PATH.account.login);
const onSubmit: SubmitHandler<PasswordResetForm> = async (data) => {
try {
const { password } = data;
await api.resetPassword({ email, tempToken: token, password });
await router.replace(PAGE_PATH.account.login);
} catch (error) {
if (isAxiosError<ErrorResponse>(error)) {
/**
* @todo
* 에러 처리
*/
console.log(error.response?.status);
}
}
};

return (
Expand Down
2 changes: 2 additions & 0 deletions src/constants/services/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export const API_PATH = {
register: '/users/register',
login: '/users/login',
passwordResetLink: '/users/password-reset-link',
password: '/users/password',
tempTokenValidation: '/users/temp-token-validation',
},
diaries: {
index: '/diaries',
Expand Down
43 changes: 40 additions & 3 deletions src/pages/account/resetPassword.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,56 @@
import styled from '@emotion/styled';
import type { NextPage } from 'next/types';
import type {
GetServerSideProps,
InferGetServerSidePropsType,
NextPage,
} from 'next/types';
import * as api from 'api';
import { ResetPasswordForm } from 'components/account';
import { Seo } from 'components/common';
import { PAGE_PATH } from 'constants/common';
import { getQueryParams } from 'utils';

const ResetPassword: NextPage = () => {
const ResetPassword: NextPage<
InferGetServerSidePropsType<typeof getServerSideProps>
> = ({ email, token }) => {
return (
<>
<Seo title={'비밀번호 재성정 | a daily diary'} />
<ContentWrapper>
<ResetPasswordForm />
<ResetPasswordForm email={email} token={token} />
</ContentWrapper>
</>
);
};

export const getServerSideProps = (async (context) => {
const { query } = context;
const [email] = getQueryParams(query.email);
const [token] = getQueryParams(query.token);

const REDIRECT_LOGIN_PAGE_PROPS = {
redirect: {
destination: PAGE_PATH.account.login,
permanent: false,
},
};

if (!email || !token) {
return REDIRECT_LOGIN_PAGE_PROPS;
}

try {
await api.tempTokenValidation({
email,
tempToken: token,
});

return { props: { email, token } };
} catch (error) {
return REDIRECT_LOGIN_PAGE_PROPS;
}
}) satisfies GetServerSideProps;

export default ResetPassword;

const ContentWrapper = styled.section`
Expand Down
18 changes: 18 additions & 0 deletions src/types/password.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ export interface PasswordResetLinkRequest {
redirectUrl: string;
}

export interface PasswordResetRequest {
email: string;
tempToken: string;
password: string;
}

export interface TempTokenValidationRequest {
email: string;
tempToken: string;
}

/**
* Response
*/
export interface TempTokenValidationResponse {
isValidate: boolean;
}

/**
* Others
*/
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './date';
export * from './ErrorResponseMessage';
export * from './Formatter';
export * from './query';
export { default as textareaAutosize } from './TextareaAutosize';
16 changes: 16 additions & 0 deletions src/utils/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* 쿼리 파라미터를 받아 항상 문자열 배열로 반환합니다.
*/
export const getQueryParams = (
param: string | string[] | undefined,
): string[] => {
if (Array.isArray(param)) {
return param;
}

if (param === undefined || param === '') {
return [];
}

return [param];
};

0 comments on commit 96d9673

Please sign in to comment.