Skip to content

Commit

Permalink
[Design] 비밀번호 재설정 뷰 GUI 반영 (#321)
Browse files Browse the repository at this point in the history
* feat: 경우에 따른 supportingText rendering

* chore: api path 변경에 따른 폴더 구조 변경

* chore: 중복 제거

* feat: 인증코드 SupportingText 추가

* feat: 비밀번호 재설정 로직 수정

* refactor: input의 supportingText 사용

* style: 버튼 크기 수정

* chore: 줄바꿈 추가

* fix: signup 메일 api path 변경 & 빌드 에러 해결

* chore: 코드 순서 변경

* chore: 콘솔 제거

* chore: solve lint error

* feat: 인증 메일 전송시 버튼 텍스트 변경

* chore: sort package.json
  • Loading branch information
namdaeun authored Nov 14, 2024
1 parent c68d4d8 commit cc67172
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 124 deletions.
13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"name": "tiki-client",
"private": true,
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build-storybook": "storybook build",
"check": "concurrently \"pnpm lint\" \"pnpm typeCheck\"",
"chromatic": "npx chromatic --project-token=chpt_f4088febbfb82b7",
"dev": "vite",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"chromatic": "npx chromatic --project-token=chpt_f4088febbfb82b7",
"typeCheck": "tsc --noEmit",
"check": "concurrently \"pnpm lint\" \"pnpm typeCheck\""
"typeCheck": "tsc --noEmit"
},
"dependencies": {
"@emotion/react": "^11.11.4",
Expand All @@ -22,7 +22,6 @@
"axios": "^1.7.2",
"framer-motion": "^11.11.11",
"mime": "^4.0.4",
"framer-motion": "^11.11.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.1",
Expand Down
2 changes: 1 addition & 1 deletion src/common/component/CountedInput/CountedInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const CountedInput = (
<span css={countTextStyle}>{`${count}/${maxLength}`}</span>
</div>
{supportingText && (
<SupportingText isError={isError} isNotice={isSuccess}>
<SupportingText isError={isError} isSuccess={isSuccess}>
{supportingText}
</SupportingText>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/common/component/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Input = (
<input ref={ref} css={inputStyle} {...props} />
</div>
{supportingText && (
<SupportingText isError={isError} isNotice={isSuccess}>
<SupportingText isError={isError} isSuccess={isSuccess}>
{supportingText}
</SupportingText>
)}
Expand Down
12 changes: 9 additions & 3 deletions src/common/component/SupportingText/SupportingText.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ import { css } from '@emotion/react';

import { theme } from '@/common/style/theme/theme';

export const textStyle = (isError: boolean, isNotice: boolean) => {
export const textStyle = (isError: boolean, isSuccess: boolean) => {
const textColor = isError
? theme.colors.sementic_red
: isNotice
: isSuccess
? theme.colors.sementic_success
: theme.colors.gray_400;

return css({ color: textColor, paddingLeft: '0.8rem', wordBreak: 'break-word', ...theme.text.body09 });
return css({
color: textColor,
paddingLeft: '0.8rem',
wordBreak: 'break-word',
...theme.text.body09,
whiteSpace: 'pre-line',
});
};
6 changes: 3 additions & 3 deletions src/common/component/SupportingText/SupportingText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { textStyle } from '@/common/component/SupportingText/SupportingText.styl

interface SupportingTextProps extends ComponentPropsWithoutRef<'p'> {
isError?: boolean;
isNotice?: boolean;
isSuccess?: boolean;
}

const SupportingText = ({ isError = false, isNotice = false, children, ...props }: SupportingTextProps) => {
const SupportingText = ({ isError = false, isSuccess = false, children, ...props }: SupportingTextProps) => {
return (
<p {...props} css={textStyle(isError, isNotice)}>
<p {...props} css={textStyle(isError, isSuccess)}>
{children}
</p>
);
Expand Down
19 changes: 10 additions & 9 deletions src/page/login/password/auth/PasswordAuthPage.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { theme } from '@/common/style/theme/theme';
export const pageStyle = css({
flexDirection: 'column',

width: '51.1rem',
height: '78rem',
width: '39rem',
height: '30rem',

justifyContent: 'center',

whiteSpace: 'nowrap',
});
Expand All @@ -15,11 +17,10 @@ export const formStyle = css({
display: 'flex',
position: 'relative',
flexDirection: 'column',
flex: '1',

width: '51.1rem',
width: '39rem',
height: '21.6rem',

paddingTop: '3.2rem',
margin: '0 auto',

alignItems: 'center',
Expand All @@ -29,9 +30,9 @@ export const formStyle = css({
export const timestyle = css({
position: 'absolute',

top: '20rem',
right: '13rem',
top: '7.8rem',
right: '11.2rem',

color: theme.colors.key_500,
...theme.text.body04,
color: theme.colors.gray_500,
...theme.text.body06,
});
119 changes: 70 additions & 49 deletions src/page/login/password/auth/PasswordAuthPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@ import Button from '@/common/component/Button/Button';
import Flex from '@/common/component/Flex/Flex';
import Heading from '@/common/component/Heading/Heading';
import Input from '@/common/component/Input/Input';
import SupportingText from '@/common/component/SupportingText/SupportingText';
import { useInput } from '@/common/hook/useInput';
import { useTimer } from '@/common/hook/useTimer';

import { formStyle, pageStyle, timestyle } from '@/page/login/password/auth/PasswordAuthPage.style';
import { useResendMailMutation } from '@/page/login/password/auth/hook/useResendMailMutation';
import { useResendMailMutation } from '@/page/login/password/auth/hook/api/useResendMailMutation';
import { useSupportingText } from '@/page/login/password/auth/hook/common/useSupportingText';
import { formatTime } from '@/page/signUp/info/util/formatTime';

import { EMAIL_REMAIN_TIME, PLACEHOLDER, SUPPORTING_TEXT } from '@/shared/constant/form';
import { PATH } from '@/shared/constant/path';
import { useVerifyCodeMutation } from '@/shared/hook/api/useVerifyCodeMutation';
import { useToastAction } from '@/shared/store/toast';
import { validateCode, validateEmail } from '@/shared/util/validate';

const PasswordAuthPage = () => {
const [isVerifyCode, setIsVerifyCode] = useState(false);
const [buttonText, setButtonText] = useState('인증 메일 전송');
const navigate = useNavigate();

const { value: email, onChange: onEmailChange } = useInput('');
const { value: authCode, onChange: onAuthCodeChange } = useInput('');
const { emailSupportingText, setEmailSupportingText, codeSupportingText, setCodeSupportingText } =
useSupportingText();

const {
remainTime,
Expand All @@ -31,83 +35,100 @@ const PasswordAuthPage = () => {
reset: handleResetTimer,
} = useTimer(EMAIL_REMAIN_TIME, SUPPORTING_TEXT.EMAIL_EXPIRED);

const navigate = useNavigate();
const { mutate: resendMailMutation } = useResendMailMutation(email);
const { resendMailMutation } = useResendMailMutation(email);
const { mutate, isError } = useVerifyCodeMutation(email, authCode);

const { createToast } = useToastAction();

const handleMailSend = () => {
if (!validateEmail(email)) {
createToast(SUPPORTING_TEXT.EMAIL_INVALID, 'error');
resendMailMutation.mutate(undefined, {
onError: () => {
setEmailSupportingText({ text: SUPPORTING_TEXT.EMAIL_INVALID, type: 'error' });
},
});

return;
}
handleSend();
handleResetTimer();

resendMailMutation();
setEmailSupportingText({ text: SUPPORTING_TEXT.EMAIL_SUCCESS, type: 'success' });
setButtonText('재전송');
};

const handleVerifyCode = useCallback(() => {
if (validateCode(authCode)) {
mutate(undefined, {
onSuccess: () => {
setIsVerifyCode(true);
setCodeSupportingText({ text: SUPPORTING_TEXT.AUTHCODE_SUCCESS, type: 'success' });
},
onError: () => {
setCodeSupportingText({ text: SUPPORTING_TEXT.AUTHCODE_NO_EQUAL, type: 'error' });
},
});
setIsVerifyCode(false);
}
}, [authCode, mutate]);
}, [authCode, mutate, setCodeSupportingText]);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

if (!isError || isMailSent) navigate(PATH.PASSWORD_RESET, { state: email });
};

return (
<Flex style={{ justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<Flex tag="section" css={pageStyle}>
<Heading css={{ padding: '1.6rem 0', alignItems: 'start' }}>비밀번호 재설정</Heading>
<form css={[formStyle, { width: '51.1rem', height: '78rem' }]} onSubmit={handleSubmit}>
<Flex styles={{ direction: 'column', width: '100%', gap: '1.6rem', justify: 'space-between' }}>
<Flex styles={{ align: 'end', width: '100%', gap: '0.8rem' }}>
<Input label="회원 정보" placeholder={PLACEHOLDER.SCHOOL_EMAIL} value={email} onChange={onEmailChange} />
<Button css={{ width: '11.1rem' }} size="large" onClick={handleMailSend} disabled={!validateEmail(email)}>
인증 메일 발송
<Heading tag="H4" css={{ fontWeight: 600, paddingBottom: '6rem', alignItems: 'start' }}>
비밀번호 재설정
</Heading>
<form css={[formStyle]} onSubmit={handleSubmit}>
<Flex styles={{ direction: 'column', width: '100%', gap: '0.2rem', justify: 'space-between' }}>
<Flex styles={{ align: 'baseline', width: '100%', gap: '0.4rem' }}>
<Input
placeholder={PLACEHOLDER.SCHOOL_EMAIL}
value={email}
onChange={onEmailChange}
isSuccess={emailSupportingText.type === 'success'}
isError={emailSupportingText.type === 'error'}
supportingText={emailSupportingText.text}
/>
<Button
css={{ width: '9.7rem', flexShrink: '0' }}
variant="outline"
size="large"
onClick={handleMailSend}
disabled={!validateEmail(email)}>
{buttonText}
</Button>
</Flex>
{isMailSent && (
<>
<SupportingText isNotice={true}>{SUPPORTING_TEXT.AUTH_CODE}</SupportingText>
<Flex
styles={{
align: 'end',
justify: 'space-between',
width: '100%',
marginTop: '1.6rem',
gap: '1.6rem',
}}>
<Input
label="인증 코드"
placeholder={PLACEHOLDER.AUTH_CODE}
value={authCode}
onChange={onAuthCodeChange}
/>
<span css={timestyle}>{formatTime(remainTime)}</span>
<Button
css={{ width: '13rem' }}
size="large"
onClick={handleVerifyCode}
disabled={!validateCode(authCode)}>
인증하기
</Button>
</Flex>
</>
{isMailSent && !resendMailMutation.isError && (
<Flex
styles={{
align: 'baseline',
justify: 'space-between',
width: '100%',
marginTop: '0.4rem',
gap: '0.4rem',
}}>
<Input
placeholder={PLACEHOLDER.AUTH_CODE}
value={authCode}
onChange={onAuthCodeChange}
isSuccess={codeSupportingText.type === 'success'}
isError={codeSupportingText.type === 'error'}
supportingText={codeSupportingText.text}
css={{ position: 'relative' }}
/>
<span css={timestyle}>{formatTime(remainTime)}</span>
<Button
css={{ width: '9.7rem', flexShrink: '0' }}
size="large"
variant="outline"
onClick={handleVerifyCode}
disabled={!validateCode(authCode)}>
인증하기
</Button>
</Flex>
)}
</Flex>
<Button type="submit" variant="primary" size="large" disabled={!isVerifyCode}>
<Button type="submit" variant="primary" size="xLarge" css={{ width: '100%' }} disabled={!isVerifyCode}>
완료
</Button>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { useMutation } from '@tanstack/react-query';

import { isAxiosError } from 'axios';

import { reSendEmail } from '@/shared/api/mail/password';
import { reSendEmail } from '@/shared/api/email-verification/password';
import { useToastAction } from '@/shared/store/toast';

export const useResendMailMutation = (email: string) => {
const { createToast } = useToastAction();

const resendMailMutation = useMutation({
mutationFn: () => reSendEmail(email),
mutationFn: () => {
return reSendEmail(email);
},
onSuccess: () => {
createToast('메일을 성공적으로 전송했습니다.', 'success');
},
Expand All @@ -20,5 +22,5 @@ export const useResendMailMutation = (email: string) => {
},
});

return resendMailMutation;
return { resendMailMutation };
};
24 changes: 24 additions & 0 deletions src/page/login/password/auth/hook/common/useSupportingText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useState } from 'react';

import { SupportingText } from '@/page/login/password/type/supportingText';

import { SUPPORTING_TEXT } from '@/shared/constant/form';

export const useSupportingText = () => {
const [emailSupportingText, setEmailSupportingText] = useState<SupportingText>({
text: SUPPORTING_TEXT.EMAIL_AUTH,
type: 'default',
});

const [codeSupportingText, setCodeSupportingText] = useState<SupportingText>({
text: SUPPORTING_TEXT.AUTHCODE,
type: 'default',
});

return {
emailSupportingText,
setEmailSupportingText,
codeSupportingText,
setCodeSupportingText,
};
};
11 changes: 6 additions & 5 deletions src/page/login/password/reset/PasswordResetPage.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { theme } from '@/common/style/theme/theme';
export const pageStyle = css({
flexDirection: 'column',

height: '78rem',
width: '51.1rem',
width: '39rem',
height: '30rem',

justifyContent: 'center',

whiteSpace: 'nowrap',
});
Expand All @@ -15,11 +17,10 @@ export const formStyle = css({
position: 'relative',
display: 'flex',
flexDirection: 'column',
flex: '1',

width: '51.1rem',
width: '39rem',
height: '21.6rem',

paddingTop: '3.2rem',
margin: '0 auto',

alignItems: 'center',
Expand Down
Loading

0 comments on commit cc67172

Please sign in to comment.