|
1 | | -import React, { useState } from 'react'; |
| 1 | +import React from 'react'; |
2 | 2 | import TicketHostLayout from '../../../shared/ui/backgrounds/TicketHostLayout'; |
3 | 3 | import CreditCard from '../../../../public/assets/payment/CreditCard.svg'; |
4 | 4 | import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField'; |
5 | 5 | import Button from '../../../../design-system/ui/Button'; |
| 6 | +import { useForm } from 'react-hook-form'; |
6 | 7 |
|
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 | +}; |
21 | 15 |
|
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>(); |
27 | 23 |
|
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 | + } |
32 | 42 | }; |
33 | 43 |
|
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 }); |
38 | 48 | }; |
39 | 49 |
|
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); |
44 | 52 | }; |
45 | 53 |
|
46 | 54 | return ( |
47 | 55 | <TicketHostLayout centerContent="새 카드 추가" image={CreditCard}> |
48 | 56 | <div className="p-10 flex flex-col gap-16 mt-20 min-h-[80vh]"> |
49 | 57 | <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> |
65 | 60 | <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')} |
71 | 72 | /> |
| 73 | + {errors.cardNumber && <p className="text-red-500 text-xs md:text-sm">{errors.cardNumber.message}</p>} |
| 74 | + </div> |
| 75 | + |
| 76 | + <div> |
72 | 77 | <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: '카드 소지자 이름을 입력하세요. ' })} |
78 | 82 | /> |
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>} |
80 | 84 | </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> |
84 | 117 | </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> |
85 | 122 | </div> |
86 | 123 | </TicketHostLayout> |
87 | 124 | ); |
|
0 commit comments