Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: #35 ID/PW 찾기 페이지 UI 작성 및 Validation 체크 #58

Merged
merged 11 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9,007 changes: 9,007 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/components/common/ValidationInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default function ValidationInput({
{isButtonInput && (
<button
type="button"
className="flex h-20 w-75 items-center justify-center rounded bg-sub px-8 font-bold shadow-md"
className="flex h-20 w-75 items-center justify-center rounded bg-sub px-8 font-bold"
onClick={onButtonClick}
>
{buttonLabel}
Expand Down
22 changes: 22 additions & 0 deletions src/components/user/authForm/AuthForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FormEvent, ReactNode } from 'react';

type AuthFormProps = {
children: ReactNode;
onSubmit: (e: FormEvent<HTMLFormElement>) => void;
marginTop: 'mt-34.9' | 'mt-40';
};

export default function AuthForm({ children, onSubmit, marginTop }: AuthFormProps) {
return (
<>
<section className="mt-40 text-large text-main">
Welcome to our site!
<br />
Grow Up your Life with us.
</section>
<form onSubmit={onSubmit} className={`${marginTop} flex h-screen w-300 flex-col justify-center gap-8 py-30`}>
{children}
</form>
</>
);
}
57 changes: 57 additions & 0 deletions src/components/user/authForm/FooterLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useNavigate } from 'react-router-dom';

type FooterLinksProps = {
type: 'signIn' | 'searchId' | 'searchPassword';
};

const texts = {
signIn: '로그인',
findId: '아이디 찾기',
findPassword: '비밀번호 찾기',
};

const paths = {
signIn: '/signin',
findId: '/search/id',
findPassword: '/search/password',
};

const links = {
signIn: [
{ text: texts.findId, path: paths.findId },
{ text: texts.findPassword, path: paths.findPassword },
],
searchId: [
{ text: texts.signIn, path: paths.signIn },
{ text: texts.findPassword, path: paths.findPassword },
],
searchPassword: [
{ text: texts.signIn, path: paths.signIn },
{ text: texts.findId, path: paths.findId },
],
};
Seok93 marked this conversation as resolved.
Show resolved Hide resolved

export default function FooterLinks({ type }: FooterLinksProps) {
const nav = useNavigate();

return (
<>
<div className="flex flex-row justify-center">
{links[type].map((link, index) => (
<div key={link.text} className="flex flex-row">
<button type="button" className="cursor-pointer bg-inherit font-bold" onClick={() => nav(link.path)}>
{link.text}
</button>
{index < links[type].length - 1 && <p className="mx-8">|</p>}
</div>
))}
</div>
<div className="mb-35 mt-15 flex flex-row items-center justify-center gap-8">
<p className="items-center font-bold">회원이 아니신가요?</p>
<button type="button" className="auth-btn" onClick={() => nav('/signup')}>
회원가입
</button>
</div>
</>
);
}
46 changes: 35 additions & 11 deletions src/constants/formValidationRules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Validator from '@utils/Validator';
import { deepFreeze } from '@utils/deepFreeze';
import { EMAIL_REGEX, PASSWORD_REGEX, PHONE_REGEX } from './regex';
import { EMAIL_REGEX, ID_REGEX, NICKNAME_REGEX, PASSWORD_REGEX, PHONE_REGEX } from './regex';

export const STATUS_VALIDATION_RULES = deepFreeze({
STATUS_NAME: (nameList: string[]) => ({
Expand All @@ -24,29 +24,37 @@ export const STATUS_VALIDATION_RULES = deepFreeze({
duplicatedName: (value: string) => !Validator.isDuplicatedName(colorList, value) || '이미 사용중인 색상입니다.',
},
}),
EMAIL: () => ({
EMAIL: {
required: '이메일 인증을 진행해 주세요.',
pattern: {
value: EMAIL_REGEX,
message: '이메일 형식에 맞지 않습니다.',
},
}),
CERTIFICATION: () => ({ required: '인증번호를 입력해 주세요.' }),
PHONE: () => ({
},
CERTIFICATION: { required: '인증번호를 입력해 주세요.' },
PHONE: {
required: '휴대폰 번호 인증을 진행해 주세요.',
pattern: {
value: PHONE_REGEX,
message: '휴대폰 번호를 정확히 입력해 주세요.',
},
}),
NICKNAME: () => ({
},
NICKNAME: {
required: '닉네임을 입력해 주세요.',
minLength: {
value: 2,
message: '닉네임은 최소 2자 이상이어야 합니다.',
},
maxLength: {
value: 20,
message: '닉네임은 최대 20자까지 입력 가능합니다.',
},
}),
PASSWORD: () => ({
pattern: {
value: NICKNAME_REGEX,
message: '닉네임은 영문, 한글, 숫자만 포함 가능합니다.',
},
},
PASSWORD: {
required: '비밀번호를 입력해 주세요.',
minLength: {
value: 8,
Expand All @@ -60,8 +68,24 @@ export const STATUS_VALIDATION_RULES = deepFreeze({
value: PASSWORD_REGEX,
message: '비밀번호는 영문자, 숫자, 기호를 모두 포함해야 합니다.',
},
}),
PASSWORD_CONFIRM: () => ({
},
PASSWORD_CONFIRM: (password: string) => ({
required: '비밀번호를 한 번 더 입력해 주세요.',
validate: (value: string) => value === password || '비밀번호가 일치하지 않습니다.',
}),
ID: {
required: '아이디를 입력해 주세요.',
minLength: {
value: 2,
message: '아이디는 최소 2자 이상이어야 합니다.',
},
maxLength: {
value: 255,
message: '아이디는 최대 255자 이하여야 합니다.',
},
pattern: {
value: ID_REGEX,
message: '아이디는 영문, 한글, 숫자 및 특수기호(., @, _, +, -)만 포함 가능합니다.',
},
},
});
2 changes: 2 additions & 0 deletions src/constants/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export const EMAIL_REGEX = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}(?:\.[a-z]{2,}
export const PASSWORD_REGEX =
/^(?=.*[A-Za-z])(?=.*\d)(?=.*[~`!@#$%^&*()_\-+={[}\]|\\:;"'<,>.?/])[A-Za-z\d~`!@#$%^&*()_\-+={[}\]|\\:;"'<,>.?/]{8,16}$/;
export const PHONE_REGEX = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/;
export const NICKNAME_REGEX = /^[a-zA-Z0-9가-힣]+$/;
export const ID_REGEX = /^[a-z0-9._+@가-힣-]+(?:@[a-z0-9.-]+\.[a-z]{2,}(?:\.[a-z]{2,}))?$/i;
6 changes: 1 addition & 5 deletions src/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,6 @@
}

.auth-btn {
@apply flex h-30 items-center justify-center rounded-lg bg-sub px-8 font-bold;
}

.auth-input {
@apply h-30 flex-grow rounded-lg border border-input px-8 text-sm outline-none placeholder:text-emphasis;
@apply flex h-30 cursor-pointer items-center justify-center rounded-lg bg-sub px-8 font-bold;
}
}
51 changes: 50 additions & 1 deletion src/pages/user/SearchIdPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,52 @@
import { useForm } from 'react-hook-form';
import ValidationInput from '@/components/common/ValidationInput';
import { STATUS_VALIDATION_RULES } from '@/constants/formValidationRules';
import { SearchIDForm } from '@/types/UserType';
import AuthForm from '@/components/user/authForm/AuthForm';
import FooterLinks from '@/components/user/authForm/FooterLinks';

export default function SearchIdPage() {
return <div>SearchIdPage</div>;
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<SearchIDForm>({
mode: 'onChange',
defaultValues: {
email: '',
code: '',
},
});

const onSubmit = (data: SearchIDForm) => {
console.log(data);
};

return (
<AuthForm onSubmit={handleSubmit(onSubmit)} marginTop="mt-40">
{/* 이메일 */}
<ValidationInput
isButtonInput
buttonLabel="인증번호 발송"
placeholder="이메일"
errors={errors.email?.message}
register={register('email', STATUS_VALIDATION_RULES.EMAIL)}
/>

{/* 이메일 인증 */}
<ValidationInput
placeholder="인증번호"
errors={errors.code?.message}
register={register('code', STATUS_VALIDATION_RULES.CERTIFICATION)}
/>

<div className="flex flex-col text-center">
<button type="submit" className="auth-btn" disabled={isSubmitting}>
Seok93 marked this conversation as resolved.
Show resolved Hide resolved
아이디 찾기
</button>
</div>

<FooterLinks type="searchId" />
</AuthForm>
);
}
59 changes: 58 additions & 1 deletion src/pages/user/SearchPasswordPage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,60 @@
import { useForm } from 'react-hook-form';
import ValidationInput from '@/components/common/ValidationInput';
import { STATUS_VALIDATION_RULES } from '@/constants/formValidationRules';
import AuthForm from '@/components/user/authForm/AuthForm';
import FooterLinks from '@/components/user/authForm/FooterLinks';
import { SearchPasswordForm } from '@/types/UserType';

export default function SearchPasswordPage() {
return <div>SearchPasswordPage</div>;
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<SearchPasswordForm>({
mode: 'onChange',
defaultValues: {
id: '',
email: '',
code: '',
},
});

const onSubmit = (data: SearchPasswordForm) => {
console.log(data);
};

return (
<AuthForm onSubmit={handleSubmit(onSubmit)} marginTop="mt-34.9">
{/* 아이디 */}
<ValidationInput
placeholder="아이디"
errors={errors.id?.message}
register={register('id', STATUS_VALIDATION_RULES.ID)}
/>

{/* 이메일 */}
<ValidationInput
isButtonInput
buttonLabel="인증번호 발송"
placeholder="이메일"
errors={errors.email?.message}
register={register('email', STATUS_VALIDATION_RULES.EMAIL)}
/>

{/* 이메일 인증 */}
<ValidationInput
placeholder="인증번호"
errors={errors.code?.message}
register={register('code', STATUS_VALIDATION_RULES.CERTIFICATION)}
/>

<div className="flex flex-col text-center">
<button type="submit" className="auth-btn" disabled={isSubmitting}>
비밀번호 찾기
</button>
</div>

<FooterLinks type="searchPassword" />
</AuthForm>
);
}
Loading