Skip to content

Commit

Permalink
Merge pull request #68 from Kernel360/feature-page-signup
Browse files Browse the repository at this point in the history
페이지 기능: 회원가입 유효성 검사 기능 추가
  • Loading branch information
seoye0ng authored Jan 11, 2024
2 parents 73849c9 + 5b92c40 commit e604ae3
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 35 deletions.
1 change: 1 addition & 0 deletions public/assets/icons/closeEye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/assets/icons/openEye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 86 additions & 9 deletions src/app/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable max-len */

'use client';

import { useForm } from 'react-hook-form';

// import VALIDATION_MESSAGE_MAP from '@constants/validationMessage';
import classNames from 'classnames/bind';

import Button from '@components/shared/button/Button';
Expand All @@ -19,21 +20,97 @@ import styles from './page.module.scss';

const cx = classNames.bind(styles);

const VALIDATION_MESSAGE_MAP: {
[key: string]: {
value?: RegExp,
message: string
}
} = {
id: {
value: /^(?=.*[a-z])(?=.*\d)[a-z\d]{8,}$/,
message: '영문 소문자, 숫자 조합 8자 이상 입력해주세요',
},
email: {
value: /^[_a-zA-Z0-9-.]+@[.a-zA-Z0-9-]+\.[a-zA-Z]+$/,
message: '이메일 형식을 확인해주세요',
},
password: {
value: /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,16}$/,
message: '8~16자의 영문 대/소문자, 숫자, 특수문자를 사용해 주세요.',
},
confirmingPassword: {
message: '비밀번호를 확인해주세요.',
},
};

function SignupPage() {
const { register, handleSubmit } = useForm();
// eslint-disable-next-line @typescript-eslint/require-await
const onSubmit = async () => {
const {
register, handleSubmit, formState: { errors }, watch,
} = useForm({
mode: 'onBlur',
});

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

return (
// eslint-disable-next-line @typescript-eslint/no-misused-promises
<form onSubmit={handleSubmit(onSubmit)}>
<Header displayLogo={false} />
<Spacing size={20} />
<Title title="회원가입" />
<TextField label="아이디" required placeholder="아이디" {...register('id')} />
<TextField label="비밀번호" required placeholder="비밀번호" {...register('password')} />
<TextField label="비밀번호 확인" required placeholder="비밀번호 확인" {...register('confirmedPassword')} />
<TextField label="이메일" required placeholder="이메일" {...register('email')} />
<TextField
label="아이디"
required
placeholder="아이디"
{...register('id', {
required: true,
pattern: VALIDATION_MESSAGE_MAP.id.value,
})}
hasError={!!errors.id}
helpMessage={VALIDATION_MESSAGE_MAP.id.message}
/>
<TextField
label="비밀번호"
required
placeholder="비밀번호"
isPasswordType
{...register('password', {
required: true,
pattern: VALIDATION_MESSAGE_MAP.password.value,
})}
hasError={!!errors.password}
helpMessage={VALIDATION_MESSAGE_MAP.password.message}
/>
<TextField
label="비밀번호 확인"
required
placeholder="비밀번호 확인"
isPasswordType
{...register('confirmingPassword', {
required: true,
// eslint-disable-next-line consistent-return
validate: (confirmingPassword: string) => {
if (watch('password') !== confirmingPassword) {
return false;
}
},
})}
hasError={!!errors.confirmingPassword}
helpMessage={VALIDATION_MESSAGE_MAP.confirmingPassword.message}
/>
<TextField
label="이메일"
required
placeholder="이메일"
{...register('email', {
required: true,
pattern: VALIDATION_MESSAGE_MAP.email.value,
})}
hasError={!!errors.email}
helpMessage={VALIDATION_MESSAGE_MAP.email.message}
/>
<Text typography="t6"> 성별</Text>
<Spacing size={20} />
<Flex justify="space-between" gap={10}>
Expand All @@ -51,7 +128,7 @@ function SignupPage() {
<Radio type="ageGroup" label="60대 이상" value="60" {...register('age')} />
</div>
<Spacing size={50} />
<Button size="medium" full>약관 동의하러 가기</Button>
<Button type="submit" size="medium" full>약관 동의하러 가기</Button>
<Spacing size={20} />
</form>
);
Expand Down
20 changes: 20 additions & 0 deletions src/components/icons/CloseEys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
interface CloseEyeProps {
size: number
onClick: () => void
}

function CloseEys({ size, onClick }: CloseEyeProps) {
return (
<svg
height={size}
viewBox="0 0 24 24"
width={size}
xmlns="http://www.w3.org/2000/svg"
onClick={onClick}
>
<path d="M20.0980654,15.8909586 L18.6838245,14.4767177 C19.3180029,13.8356474 19.9009094,13.1592525 20.4222529,12.4831239 C20.5528408,12.3137648 20.673512,12.1521776 20.7838347,12 C20.673512,11.8478224 20.5528408,11.6862352 20.4222529,11.5168761 C19.8176112,10.7327184 19.1301624,9.94820254 18.37596,9.21885024 C16.2825083,7.1943753 14.1050769,6 12,6 C11.4776994,6 10.9509445,6.07352686 10.4221233,6.21501656 L8.84014974,4.63304296 C9.8725965,4.22137709 10.9270589,4 12,4 C14.7275481,4 17.3356792,5.4306247 19.76629,7.78114976 C20.5955095,8.58304746 21.3456935,9.43915664 22.0060909,10.2956239 C22.4045936,10.8124408 22.687526,11.2189945 22.8424353,11.4612025 L23.1870348,12 L22.8424353,12.5387975 C22.687526,12.7810055 22.4045936,13.1875592 22.0060909,13.7043761 C21.4349259,14.4451181 20.7965989,15.1855923 20.0980652,15.8909583 L20.0980654,15.8909586 Z M17.0055388,18.4197523 C15.3942929,19.4304919 13.7209154,20 12,20 C9.27245185,20 6.66432084,18.5693753 4.23371003,16.2188502 C3.40449054,15.4169525 2.65430652,14.5608434 1.99390911,13.7043761 C1.59540638,13.1875592 1.31247398,12.7810055 1.15756471,12.5387975 L0.812965202,12 L1.15756471,11.4612025 C1.31247398,11.2189945 1.59540638,10.8124408 1.99390911,10.2956239 C2.65430652,9.43915664 3.40449054,8.58304746 4.23371003,7.78114976 C4.6043191,7.42275182 4.9790553,7.0857405 5.35771268,6.77192624 L1.29289322,2.70710678 L2.70710678,1.29289322 L22.7071068,21.2928932 L21.2928932,22.7071068 L17.0055388,18.4197523 Z M6.77972015,8.19393371 C6.39232327,8.50634201 6.00677809,8.84872289 5.62403997,9.21885024 C4.86983759,9.94820254 4.18238879,10.7327184 3.57774714,11.5168761 C3.44715924,11.6862352 3.32648802,11.8478224 3.21616526,12 C3.32648802,12.1521776 3.44715924,12.3137648 3.57774714,12.4831239 C4.18238879,13.2672816 4.86983759,14.0517975 5.62403997,14.7811498 C7.71749166,16.8056247 9.89492315,18 12,18 C13.1681669,18 14.3586152,17.6321975 15.5446291,16.9588426 L14.0319673,15.4461809 C13.4364541,15.7980706 12.7418086,16 12,16 C9.790861,16 8,14.209139 8,12 C8,11.2581914 8.20192939,10.5635459 8.55381909,9.96803265 L6.77972015,8.19393371 Z M10.0677432,11.4819568 C10.0235573,11.6471834 10,11.8208407 10,12 C10,13.1045695 10.8954305,14 12,14 C12.1791593,14 12.3528166,13.9764427 12.5180432,13.9322568 L10.0677432,11.4819568 Z" fillRule="evenodd" />
</svg>
);
}

export default CloseEys;
18 changes: 18 additions & 0 deletions src/components/icons/OpenEye.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
interface OpenEyeProps {
size: number
onClick: () => void
}

function OpenEye({ size, onClick }: OpenEyeProps) {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width={size} height={size} onClick={onClick}>
<title />
<g data-name="Layer 2" id="Layer_2">
<path d="M12,5c-6.54,0-9.76,6.29-9.89,6.55L1.88,12l.22.45C2.24,12.71,5.46,19,12,19s9.76-6.29,9.89-6.55l.22-.45-.22-.45C21.76,11.29,18.54,5,12,5Zm0,12c-4.38,0-7-3.7-7.85-5C5,10.7,7.62,7,12,7s7,3.7,7.85,5C19,13.3,16.38,17,12,17Z" />
<path d="M12,8a4,4,0,1,0,4,4A4,4,0,0,0,12,8Zm0,6a2,2,0,1,1,2-2A2,2,0,0,1,12,14Z" />
</g>
</svg>
);
}

export default OpenEye;
45 changes: 28 additions & 17 deletions src/components/shared/Input/Input.module.scss
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
.input {
box-sizing: border-box;
width: 100%;
height: 48px;
padding: 0 16px;
border: 1px solid var(--gray-200);
border-radius: 8px;
font-size: 16px;
font-weight: 500;
.container {
position: relative;

&:focus {
border-color: var(--primary);
outline: none;
}
.input {
box-sizing: border-box;
width: 100%;
height: 48px;
padding: 0 16px;
border: 1px solid var(--gray-200);
border-radius: 8px;
font-size: 16px;
font-weight: 500;

&:focus {
border-color: var(--primary);
outline: none;
}

&[aria-invaild="true"] {
border-color: var(--red);
}

&[aria-invaild="true"] {
border-color: var(--red);
&::placeholder {
color: var(--gray-100);
font-size: 12px;
}
}

&::placeholder {
color: var(--gray-100);
.iconWrapper {
position: absolute;
top: 14px;
right: 14px;
}
}
32 changes: 29 additions & 3 deletions src/components/shared/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
import { forwardRef, InputHTMLAttributes } from 'react';
/* eslint-disable no-nested-ternary */

'use client';

import { forwardRef, InputHTMLAttributes, useState } from 'react';

import classNames from 'classnames/bind';

import CloseEys from '@components/icons/CloseEys';
import OpenEye from '@components/icons/OpenEye';

import styles from './Input.module.scss';

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
isPasswordType?: boolean
}

const cx = classNames.bind(styles);
// eslint-disable-next-line prefer-arrow-callback
const Input = forwardRef<HTMLInputElement, InputHTMLAttributes<HTMLInputElement>>((props, ref) => {
return <input className={cx('input')} {...props} ref={ref} />;
const Input = forwardRef<HTMLInputElement, InputProps>(({ isPasswordType, ...props }, ref) => {
const [isOpen, setIsOpen] = useState(true);

const handleEyeIcon = () => {
setIsOpen((prev) => { return !prev; });
};
return (
<div className={cx('container')}>
<input className={cx('input')} {...props} ref={ref} type={isPasswordType === false ? 'text' : isOpen ? 'password' : 'text'} />
<div className={cx('iconWrapper')}>
{isPasswordType && (isOpen
? <OpenEye size={20} onClick={handleEyeIcon} />
: <CloseEys size={20} onClick={handleEyeIcon} />
)}
</div>
</div>
);
});

export default Input;
19 changes: 15 additions & 4 deletions src/components/shared/text-field/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ interface TextFieldProps extends InputHTMLAttributes<HTMLInputElement> {
hasError?: boolean
helpMessage?: React.ReactNode
required?: boolean
isPasswordType?: boolean
}

// eslint-disable-next-line prefer-arrow-callback
const TextField = forwardRef<HTMLInputElement, TextFieldProps>(function TextField({
label, hasError, helpMessage, required, onFocus, onBlur, ...props
label, hasError, helpMessage, required, onFocus, onBlur, isPasswordType = false, ...props
}, ref) {
const [focused, setFocused] = useState(false);

Expand All @@ -37,10 +38,20 @@ const TextField = forwardRef<HTMLInputElement, TextFieldProps>(function TextFiel
{label && <Text typography="t6" display="inline-block" color={labelColor}>{label}</Text>}
{required && <Text typography="t6" display="inline-block" color="red">*</Text>}
<Spacing size={12} />
<Input ref={ref} aria-vaild={hasError} onFocus={handleFocus} onBlur={handleBlur} {...props} />
<Spacing size={12} />
<Input
ref={ref}
aria-vaild={hasError}
onFocus={handleFocus}
onBlur={handleBlur}
isPasswordType={isPasswordType}
{...props}
/>
{hasError ? <Spacing size={6} /> : <Spacing size={12} />}
{hasError && (
<Text typography="t7" color={labelColor} display="inline-block">{helpMessage}</Text>
<>
<Text typography="t7" color={labelColor} display="inline-block">{helpMessage}</Text>
<Spacing size={6} />
</>
)}
</div>
);
Expand Down
24 changes: 24 additions & 0 deletions src/constants/validationMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const VALIDATION_MESSAGE_MAP: {
[key: string]: {
value?: RegExp,
message: string
}
} = {
id: {
value: /^(?=.*[a-z])(?=.*\d)[a-z\d]{8,}$/,
message: '영문 소문자, 숫자 조합 8자 이상 입력해주세요',
},
email: {
value: /^[_a-zA-Z0-9-.]+@[.a-zA-Z0-9-]+\.[a-zA-Z]+$/,
message: '이메일 형식을 확인해주세요',
},
password: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{8,}$/,
message: '8~16자의 영문 대/소문자, 숫자, 특수문자를 사용해 주세요.',
},
confirmingPassword: {
message: '비밀번호를 확인해주세요.',
},
};

export default VALIDATION_MESSAGE_MAP;
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
"@remote/*": [
"src/remote/*"
],
"@constants": [
"src/constants/index"
"@constants/*": [
"src/constants/*"
],
"@contexts/*": [
"src/contexts/*"
Expand Down

0 comments on commit e604ae3

Please sign in to comment.