Skip to content

Commit 4366218

Browse files
committed
refactor: 회원가입 리펙토링
1 parent 8ec14c4 commit 4366218

File tree

16 files changed

+205
-190
lines changed

16 files changed

+205
-190
lines changed
Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import Input from '@/components/input/Input';
1+
import { Input } from '@/components/input/';
2+
import { forwardRef } from 'react';
23
import { twMerge } from 'tailwind-merge';
34

45
interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
@@ -9,37 +10,35 @@ interface InputFieldProps extends React.InputHTMLAttributes<HTMLInputElement> {
910
actionButton?: React.ReactNode;
1011
}
1112

12-
export default function InputField({
13-
id,
14-
label,
15-
isValid,
16-
message,
17-
actionButton,
18-
...props
19-
}: InputFieldProps) {
20-
return (
21-
<div className="flex flex-col w-full gap-0.5">
22-
{/* 라벨 */}
23-
<label htmlFor={id} className="body-r text-gray-80 pl-[5px]">
24-
{label}
25-
</label>
13+
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(
14+
({ id, label, isValid, message, actionButton, ...props }, ref) => {
15+
return (
16+
<div className="flex flex-col w-full gap-0.5">
17+
{/* 라벨 */}
18+
<label htmlFor={id} className="body-r text-gray-80 pl-[5px]">
19+
{label}
20+
</label>
2621

27-
{/* 입력 필드 + 버튼 */}
28-
<div className="flex gap-2">
29-
<Input id={id} {...props} />
30-
{actionButton && <div className="shrink-0">{actionButton}</div>}
22+
{/* 입력 필드 + 버튼 */}
23+
<div className="flex gap-2">
24+
<Input id={id} ref={ref} {...props} />
25+
{actionButton && <div className="shrink-0">{actionButton}</div>}
26+
</div>
27+
28+
{/* 유효성 검사 메시지 */}
29+
<p
30+
className={twMerge(
31+
'text-[9px]/[18px] pl-[5px]',
32+
isValid ? 'text-functional-success' : 'text-functional-danger',
33+
message === '' && 'invisible',
34+
)}
35+
>
36+
{message || '‎'}
37+
</p>
3138
</div>
39+
);
40+
},
41+
);
3242

33-
{/* 유효성 검사 메시지 */}
34-
<p
35-
className={twMerge(
36-
'text-[9px]/[18px] pl-[5px]',
37-
isValid ? 'text-functional-success' : 'text-functional-danger',
38-
message === '' && 'invisible',
39-
)}
40-
>
41-
{message || '‎'}
42-
</p>
43-
</div>
44-
);
45-
}
43+
InputField.displayName = 'InputField';
44+
export default InputField;

src/components/input/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,2 @@
1-
import Input from '@/components/input/Input';
2-
import InputField from '@/components/input/InputField';
3-
4-
export { Input, InputField };
1+
export { default as Input } from './Input';
2+
export { default as InputField } from './InputField';

src/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './emotions';
22
export * from './validation';
33
export * from './limits';
4+
export * from './email';
45

56
// 사용
67
// import { EMOTIONS, MAX_NICKNAME_LENGTH, PASSWORD_REQUIRED_RULES } from "@/constants";

src/pages/signup/SignUp.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import SignUpForm from '@/pages/signup/components/SignUpForm';
1+
import { SignUpForm } from '@/pages/signup/components';
22

33
function SignUp() {
44
return (

src/pages/signup/components/AuthCodeInput.tsx

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import { postEmailVerificationCheck, postEmailVerificationRequest } from '@/apis/email';
2-
import InputAuthCode from '@/components/InputAuthCode';
3-
import { AUTHCODE_REGEX } from '@/constants';
4-
import { MAX_RESEND_COUNT } from '@/constants/email';
2+
import { LoadingSpinnerButton } from '@/components/button';
3+
import { InputAuthCode } from '@/pages/signup/components/';
4+
import { AUTHCODE_REGEX, MAX_RESEND_COUNT } from '@/constants';
55
import { useModalStore } from '@/store/modalStore';
66
import { useMutation } from '@tanstack/react-query';
77
import { useState } from 'react';
88

99
interface AuthCodeInputProps {
1010
email: string;
11-
authcodeValidity: boolean;
11+
emailValidity: boolean;
1212
setValidity: (val: boolean) => void; // 인증코드 유효성 바꾸는 함수
1313
}
14-
function AuthCodeInput({ email, authcodeValidity, setValidity }: AuthCodeInputProps) {
14+
function AuthCodeInput({ email, emailValidity, setValidity }: AuthCodeInputProps) {
1515
const { openModal, closeModal } = useModalStore(); // 모달
1616
const [resendCount, setResendCount] = useState(0); // 재전송 횟수
1717
const [text, setText] = useState('');
18-
const [validationMessage, setValidationMessage] = useState({
19-
success: false,
18+
const [validationStatus, setValidationStatus] = useState({
19+
isValid: false, // 유효성 통과여부
2020
message: '인증번호가 오지 않았나요?',
2121
});
2222

@@ -28,9 +28,9 @@ function AuthCodeInput({ email, authcodeValidity, setValidity }: AuthCodeInputPr
2828
const isValid = AUTHCODE_REGEX.test(value);
2929

3030
if (isValid) {
31-
setValidationMessage({ success: true, message: '' });
31+
setValidationStatus({ isValid: true, message: '' });
3232
} else {
33-
setValidationMessage({ success: false, message: '올바른 인증번호를 입력해주세요' });
33+
setValidationStatus({ isValid: false, message: '올바른 인증번호를 입력해주세요' });
3434
}
3535
};
3636

@@ -57,25 +57,25 @@ function AuthCodeInput({ email, authcodeValidity, setValidity }: AuthCodeInputPr
5757
mutationFn: () => postEmailVerificationCheck(email, text),
5858
onSuccess: ({ code }) => {
5959
if (code === 200) {
60-
setValidationMessage({ success: true, message: '이메일 인증이 완료되었습니다' });
60+
setValidationStatus({ isValid: true, message: '이메일 인증이 완료되었습니다' });
6161
setValidity(true); // 완료 처리
6262
} else {
63-
setValidationMessage({ success: false, message: '인증 코드가 올바르지 않습니다' });
63+
setValidationStatus({ isValid: false, message: '인증 코드가 올바르지 않습니다' });
6464
}
6565
},
6666

6767
onError: () => {
68-
setValidationMessage({
69-
success: false,
68+
setValidationStatus({
69+
isValid: false,
7070
message: '오류가 발생했습니다. 잠시 후 다시 시도해 주세요.',
7171
});
7272
},
7373
});
7474

7575
// 시간 초과시 실행할 함수
7676
const onTimeout = () => {
77-
setValidationMessage({
78-
success: false,
77+
setValidationStatus({
78+
isValid: false,
7979
message: '인증 시간이 만료되었습니다. 다시 요청해주세요.',
8080
});
8181
};
@@ -95,27 +95,28 @@ function AuthCodeInput({ email, authcodeValidity, setValidity }: AuthCodeInputPr
9595
resendEmailVerification();
9696
};
9797

98-
const buttonHandler = {
99-
disable: validationMessage.success && !authcodeValidity,
100-
text: '인증확인',
101-
isLoading: isPending,
102-
onClick: verifyEmail,
103-
};
104-
10598
return (
10699
<InputAuthCode
107100
id="emailVerificationConfrim"
108101
label="인증번호 확인"
109102
placeholder="인증번호 6자리를 입력해 주세요"
110-
isValid={validationMessage.success} // ✅ 유효성 검사 여부 전달
111-
validationMessage={validationMessage.message} // ✅ 메시지 전달
103+
isValid={validationStatus.isValid} // ✅ 유효성 검사 여부 전달
104+
message={validationStatus.message} // ✅ 메시지 전달
112105
value={text.toUpperCase()}
113106
onChange={handleChange}
114107
onTimeout={onTimeout} // 시간이 만료되었을 때 실행할 함수
115108
onResendEmail={handleResendEmail} // 재전송 요청
116109
resendCount={resendCount} // 재전송 횟수
117-
authcodeValidity={authcodeValidity} // 인증코드 유효성
118-
buttonHandler={buttonHandler}
110+
isEmailVerified={emailValidity}
111+
actionButton={
112+
<LoadingSpinnerButton
113+
isLoading={isPending}
114+
className="w-[65px] flex-shrink-0"
115+
text={emailValidity ? '인증완료' : '인증확인'}
116+
disabled={!validationStatus.isValid || emailValidity}
117+
onClick={() => verifyEmail()}
118+
/>
119+
}
119120
/>
120121
);
121122
}
File renamed without changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { AuthCodeInput, EmailInput } from '@/pages/signup/components';
2+
import { useState } from 'react';
3+
4+
interface EmailGroupSectionProps {
5+
updateValidity: (key: 'email', value: boolean) => void;
6+
validity: { email: boolean };
7+
}
8+
9+
const EmailGroupSection = ({ updateValidity, validity }: EmailGroupSectionProps) => {
10+
const [email, setEmail] = useState('');
11+
return (
12+
<>
13+
<EmailInput
14+
setValidity={(value) => updateValidity('email', value)}
15+
emailValidity={validity.email}
16+
setEmail={setEmail}
17+
/>
18+
{email !== '' && (
19+
<AuthCodeInput
20+
email={email}
21+
emailValidity={validity.email}
22+
setValidity={(value) => updateValidity('email', value)}
23+
/>
24+
)}
25+
</>
26+
);
27+
};
28+
29+
export default EmailGroupSection;

src/pages/signup/components/EmailInput.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
import { getEmailAvailability, postEmailVerificationRequest } from '@/apis/email';
2-
import LoadingSpinnerButton from '@/components/button/LoadingSpinnerButton';
3-
import InputField from '@/components/input/InputField';
2+
import { LoadingSpinnerButton } from '@/components/button';
3+
import { InputField } from '@/components/input';
44
import { EMAIL_REGEX } from '@/constants';
55
import { useModalStore } from '@/store/modalStore';
66
import { useMutation } from '@tanstack/react-query';
77
import { useState } from 'react';
88

99
interface EmailInputProps {
10-
changeFormEmail: (val: string) => void;
1110
setValidity: (val: boolean) => void;
12-
authcodeValidity: boolean; // 인증 코드 유효성
1311
emailValidity: boolean; // 이메일 유효성
12+
setEmail: React.Dispatch<React.SetStateAction<string>>;
1413
}
1514

16-
function EmailInput({
17-
changeFormEmail,
18-
setValidity,
19-
authcodeValidity,
20-
emailValidity,
21-
}: EmailInputProps) {
15+
function EmailInput({ setValidity, emailValidity, setEmail }: EmailInputProps) {
2216
const { openModal, closeModal } = useModalStore(); // 모달
2317
const [text, setText] = useState('');
18+
const [hasRequested, setHasRequested] = useState(false); // 이미 인증 요청을 했는지
2419
const [validationStatus, setValidationStatus] = useState({
2520
isValid: false, // 유효성 통과여부
2621
message: '',
@@ -29,7 +24,6 @@ function EmailInput({
2924
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
3025
const value = e.target.value;
3126
setText(value);
32-
changeFormEmail(value); // form id 업데이트
3327
setValidity(false); // form validity 초기화
3428

3529
// 유효성 검사
@@ -80,8 +74,8 @@ function EmailInput({
8074
message:
8175
'이메일 인증 메일이 발송되었습니다. 메일함에서 인증번호를 확인 후 입력해주세요',
8276
});
83-
84-
setValidity(true); // 폼 유효성 확인 업데이트
77+
setHasRequested(true);
78+
setEmail(text);
8579
}
8680
},
8781
onError: () => {
@@ -101,16 +95,17 @@ function EmailInput({
10195
label="이메일 인증"
10296
placeholder="이메일을 입력해 주세요"
10397
value={text}
98+
name="email"
10499
onChange={handleChange}
105-
isValid={validationStatus.isValid} // ✅ 유효성 검사 여부 전달
106-
message={validationStatus.message} // ✅ 메시지 전달
107-
disabled={authcodeValidity} // input disabled
100+
isValid={validationStatus.isValid}
101+
message={validationStatus.message}
102+
disabled={emailValidity}
108103
actionButton={
109104
<LoadingSpinnerButton
110-
isLoading={isCheckingEmail && isRequestingEmailVerification}
105+
isLoading={isCheckingEmail || isRequestingEmailVerification}
111106
className="w-[65px]"
112-
text="인증요청"
113-
disabled={!validationStatus.isValid || emailValidity}
107+
text={emailValidity ? '인증완료' : '인증요청'}
108+
disabled={!validationStatus.isValid || emailValidity || hasRequested}
114109
onClick={() => checkEmailAvailability()}
115110
/>
116111
}

src/pages/signup/components/IdInput.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { getIdAvailability } from '@/apis/user';
2-
import LoadingSpinnerButton from '@/components/button/LoadingSpinnerButton';
3-
import InputField from '@/components/input/InputField';
2+
import { LoadingSpinnerButton } from '@/components/button';
3+
import { InputField } from '@/components/input';
4+
45
import { ID_REGEX } from '@/constants';
56
import { useModalStore } from '@/store/modalStore';
67
import { useMutation } from '@tanstack/react-query';
78
import { useState } from 'react';
89

910
interface IdInputProps {
10-
changeFormID: (val: string) => void;
1111
setValidity: (val: boolean) => void;
1212
}
13-
const IdInput = ({ changeFormID, setValidity }: IdInputProps) => {
13+
14+
const IdInput = ({ setValidity }: IdInputProps) => {
1415
const { openModal, closeModal } = useModalStore(); // 모달
1516
const [text, setText] = useState('');
1617
const [validationStatus, setValidationStatus] = useState({
@@ -21,7 +22,6 @@ const IdInput = ({ changeFormID, setValidity }: IdInputProps) => {
2122
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
2223
const value = e.target.value;
2324
setText(value);
24-
changeFormID(value); // form id 업데이트
2525
setValidity(false); // form validity 초기화
2626

2727
if (ID_REGEX.test(value)) {
@@ -36,7 +36,7 @@ const IdInput = ({ changeFormID, setValidity }: IdInputProps) => {
3636

3737
// 아이디 중복 확인 API 호출
3838
const { isPending, mutate } = useMutation({
39-
mutationFn: () => getIdAvailability(text),
39+
mutationFn: (value: string) => getIdAvailability(value),
4040
onSuccess: (data) => {
4141
if (data.code === 200) {
4242
setValidationStatus({ isValid: true, message: '사용 가능한 아이디입니다' });
@@ -55,13 +55,15 @@ const IdInput = ({ changeFormID, setValidity }: IdInputProps) => {
5555
});
5656
},
5757
});
58-
// console.log('새로운 mutate', mutate);
58+
5959
return (
6060
<InputField
6161
id="id"
62+
name="id"
6263
label="아이디"
6364
placeholder="아이디를 입력해 주세요"
6465
onChange={handleChange}
66+
value={text}
6567
message={validationStatus.message}
6668
isValid={validationStatus.isValid}
6769
actionButton={
@@ -70,7 +72,7 @@ const IdInput = ({ changeFormID, setValidity }: IdInputProps) => {
7072
className="w-[65px]"
7173
text="중복확인"
7274
disabled={!validationStatus.isValid}
73-
onClick={() => mutate()}
75+
onClick={() => mutate(text)}
7476
/>
7577
}
7678
/>

0 commit comments

Comments
 (0)