Skip to content

Commit ec8a7d1

Browse files
committed
refact:CardRegisterPage 카드 등록 상태를 react-hook-form로 관리하도록 변경
1 parent cdf5230 commit ec8a7d1

File tree

1 file changed

+98
-61
lines changed

1 file changed

+98
-61
lines changed

src/pages/payment/ui/CardRegisterPage.tsx

Lines changed: 98 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,124 @@
1-
import React, { useState } from 'react';
1+
import React from 'react';
22
import TicketHostLayout from '../../../shared/ui/backgrounds/TicketHostLayout';
33
import CreditCard from '../../../../public/assets/payment/CreditCard.svg';
44
import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField';
55
import Button from '../../../../design-system/ui/Button';
6+
import { useForm } from 'react-hook-form';
67

7-
const CardRegisterPage = () => {
8-
const [cardNumber, setCardNumber] = useState<string>('');
9-
const [cardHolder, setCardHolder] = useState<string>('');
10-
const [expiryMonth, setExpiryMonth] = useState<string>('');
11-
const [expiryYear, setExpiryYear] = useState<string>('');
12-
const [cvc, setCvc] = useState<string>('');
13-
14-
//포맷팅
15-
// 카드 번호 입력 핸들러
16-
const handleCardNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
17-
const value = e.target.value.replace(/\D/g, '').slice(0, 16);
18-
const formattedValue = value.replace(/(\d{4})(?=\d)/g, '$1 '); // 4자리마다 공백 추가
19-
setCardNumber(formattedValue);
20-
};
8+
type CardFormValues = {
9+
cardNumber: string;
10+
cardHolder: string;
11+
expiryMonth: string;
12+
expiryYear: string;
13+
cvc: string;
14+
};
2115

22-
// 카드 소지자 입력 핸들러
23-
const handleCardHolderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
24-
const value = e.target.value.replace(/\d/g, '');
25-
setCardHolder(value);
26-
};
16+
const CardRegisterPage = () => {
17+
const {
18+
register,
19+
handleSubmit,
20+
setValue,
21+
formState: { errors },
22+
} = useForm<CardFormValues>();
2723

28-
// 만료 월 입력 핸들러
29-
const handleExpiryMonthChange = (e: React.ChangeEvent<HTMLInputElement>) => {
30-
const value = e.target.value.replace(/\D/g, '').slice(0, 2);
31-
setExpiryMonth(value);
24+
// 공통 포맷 함수
25+
const formatValue = (type: keyof CardFormValues, value: string): string => {
26+
switch (type) {
27+
case 'cardNumber':
28+
return value
29+
.replace(/\D/g, '')
30+
.slice(0, 16)
31+
.replace(/(\d{4})/g, '$1 ')
32+
.trim();
33+
case 'expiryMonth':
34+
return value.replace(/\D/g, '').slice(0, 2);
35+
case 'expiryYear':
36+
return value.replace(/\D/g, '').slice(0, 4);
37+
case 'cvc':
38+
return value.replace(/\D/g, '').slice(0, 3);
39+
default:
40+
return value;
41+
}
3242
};
3343

34-
// 만료 연도 입력 핸들러
35-
const handleExpiryYearChange = (e: React.ChangeEvent<HTMLInputElement>) => {
36-
const value = e.target.value.replace(/\D/g, '').slice(0, 2);
37-
setExpiryYear(value);
44+
// 공통 핸들러
45+
const handleInputChange = (type: keyof CardFormValues) => (e: React.ChangeEvent<HTMLInputElement>) => {
46+
const formattedValue = formatValue(type, e.target.value);
47+
setValue(type, formattedValue, { shouldValidate: true });
3848
};
3949

40-
// CVC 입력 핸들러
41-
const handleCvcChange = (e: React.ChangeEvent<HTMLInputElement>) => {
42-
const value = e.target.value.replace(/\D/g, '').slice(0, 3);
43-
setCvc(value);
50+
const onSubmit = (data: CardFormValues) => {
51+
console.log('폼 전체 데이터 객체:', data);
4452
};
4553

4654
return (
4755
<TicketHostLayout centerContent="새 카드 추가" image={CreditCard}>
4856
<div className="p-10 flex flex-col gap-16 mt-20 min-h-[80vh]">
4957
<p className="text-gray-400 text-sm">안전한 결제를 위해 카드 정보를 입력해주세요.</p>
50-
<DefaultTextField
51-
label="카드 번호"
52-
className="h-12"
53-
placeholder="1234 5678 9012 3456"
54-
value={cardNumber}
55-
onChange={handleCardNumberChange}
56-
/>
57-
<DefaultTextField
58-
label="카드 소지자 이름"
59-
className="h-12"
60-
placeholder="홍길동"
61-
value={cardHolder}
62-
onChange={handleCardHolderChange}
63-
/>
64-
<div className="flex gap-3">
58+
59+
<div>
6560
<DefaultTextField
66-
label="만료 월"
67-
className="h-12"
68-
placeholder="월"
69-
value={expiryMonth}
70-
onChange={handleExpiryMonthChange}
61+
label="카드 번호"
62+
className={`h-12 ${errors.cardNumber ? 'border-2 border-red-500' : ''}`}
63+
placeholder="1234 5678 9012 3456"
64+
{...register('cardNumber', {
65+
required: '카드 번호를 입력하세요.',
66+
pattern: {
67+
value: /^\d{4} \d{4} \d{4} \d{4}$/,
68+
message: '올바른 카드 번호 형식이 아닙니다.',
69+
},
70+
})}
71+
onChange={handleInputChange('cardNumber')}
7172
/>
73+
{errors.cardNumber && <p className="text-red-500 text-xs md:text-sm">{errors.cardNumber.message}</p>}
74+
</div>
75+
76+
<div>
7277
<DefaultTextField
73-
label="만료 년도"
74-
className="h-12"
75-
placeholder="년도"
76-
value={expiryYear}
77-
onChange={handleExpiryYearChange}
78+
label="카드 소지자 이름"
79+
className={`h-12 ${errors.cardHolder ? 'border-2 border-red-500' : ''}`}
80+
placeholder="홍길동"
81+
{...register('cardHolder', { required: '카드 소지자 이름을 입력하세요. ' })}
7882
/>
79-
<DefaultTextField label="CVC/CVV" className="h-12" placeholder="123" value={cvc} onChange={handleCvcChange} />
83+
{errors.cardHolder && <p className="text-red-500 text-xs md:text-sm">{errors.cardHolder.message}</p>}
8084
</div>
81-
<div className="flex-grow"></div>
82-
<div>
83-
<Button label="저장하기" onClick={() => console.log('카드 정보 저장')} className="rounded-full w-full h-12" />
85+
86+
<div className="flex gap-3">
87+
<div>
88+
<DefaultTextField
89+
label="만료 월"
90+
className={`h-12 ${errors.expiryMonth ? 'border-2 border-red-500' : ''}`}
91+
placeholder="월"
92+
{...register('expiryMonth', { required: '만료 월을 입력하세요.' })}
93+
onChange={handleInputChange('expiryMonth')}
94+
/>
95+
{errors.expiryMonth && <p className="text-red-500 text-xs md:text-sm">{errors.expiryMonth.message}</p>}
96+
</div>
97+
<div>
98+
<DefaultTextField
99+
label="만료 년도"
100+
className={`h-12 ${errors.expiryYear ? 'border-2 border-red-500' : ''}`}
101+
placeholder="년도"
102+
{...register('expiryYear', { required: '만료 년도를 입력하세요.' })}
103+
onChange={handleInputChange('expiryYear')}
104+
/>
105+
{errors.expiryYear && <p className="text-red-500 text-xs md:text-sm">{errors.expiryYear.message}</p>}
106+
</div>
107+
<div>
108+
<DefaultTextField
109+
label="CVC/CVV"
110+
className={`h-12 ${errors.cvc ? 'border-2 border-red-500' : ''}`}
111+
placeholder="123"
112+
{...register('cvc', { required: 'cvc를 입력하세요.' })}
113+
onChange={handleInputChange('cvc')}
114+
/>
115+
{errors.cvc && <p className="text-red-500 text-xs md:text-sm">{errors.cvc.message}</p>}
116+
</div>
84117
</div>
118+
<div className="flex-grow"></div>
119+
<form onSubmit={handleSubmit(onSubmit)}>
120+
<Button label="저장하기" onClick={() => console.log()} className="rounded-full w-full h-12" />
121+
</form>
85122
</div>
86123
</TicketHostLayout>
87124
);

0 commit comments

Comments
 (0)