Skip to content
Closed
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
49 changes: 0 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json'],
tsconfigRootDir: import.meta.dirname,
},
},
});
```

- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:

```js
// eslint.config.js
import react from 'eslint-plugin-react';

export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
});
```
104 changes: 51 additions & 53 deletions src/pages/payment/ui/CardRegisterPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import TicketHostLayout from '../../../shared/ui/backgrounds/TicketHostLayout';
import CreditCard from '../../../../public/assets/payment/CreditCard.svg';
import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField';
Expand All @@ -8,8 +8,7 @@ import { useForm } from 'react-hook-form';
type CardFormValues = {
cardNumber: string;
cardHolder: string;
expiryMonth: string;
expiryYear: string;
expiry: string;
cvc: string;
};

Expand All @@ -18,10 +17,17 @@ const CardRegisterPage = () => {
register,
handleSubmit,
setValue,
formState: { errors },
} = useForm<CardFormValues>();
watch,
formState: { errors, isValid },
} = useForm<CardFormValues>({ mode: 'onChange' });

const [isDisabled, setIsDisabled] = useState(true);

// 입력값 변경 감지 및 버튼 활성화 여부 결정
useEffect(() => {
setIsDisabled(!isValid);
}, [isValid]);

// 공통 포맷 함수
const formatValue = (type: keyof CardFormValues, value: string): string => {
switch (type) {
case 'cardNumber':
Expand All @@ -30,25 +36,26 @@ const CardRegisterPage = () => {
.slice(0, 16)
.replace(/(\d{4})/g, '$1 ')
.trim();
case 'expiryMonth':
return value.replace(/\D/g, '').slice(0, 2);
case 'expiryYear':
return value.replace(/\D/g, '').slice(0, 4);
case 'expiry':
return value
.replace(/\D/g, '')
.slice(0, 4)
.replace(/(\d{2})(\d{0,2})/, '$1 / $2');
case 'cvc':
return value.replace(/\D/g, '').slice(0, 3);
return value.replace(/\D/g, '').slice(0, 4);
default:
return value;
}
};

// 공통 핸들러
const handleInputChange = (type: keyof CardFormValues) => (e: React.ChangeEvent<HTMLInputElement>) => {
const formattedValue = formatValue(type, e.target.value);
setValue(type, formattedValue, { shouldValidate: true });
};

const onSubmit = (data: CardFormValues) => {
console.log('폼 전체 데이터 객체:', data);
alert('카드 정보가 저장되었습니다!');
console.log('폼 데이터:', data);
};

return (
Expand All @@ -59,14 +66,11 @@ const CardRegisterPage = () => {
<div>
<DefaultTextField
label="카드 번호"
className={`h-12 ${errors.cardNumber ? 'border-2 border-red-500' : ''}`}
placeholder="1234 5678 9012 3456"
className={`h-12 ${errors.cardNumber ? 'border-2 border-red-500' : ''}`}
{...register('cardNumber', {
required: '카드 번호를 입력하세요.',
pattern: {
value: /^\d{4} \d{4} \d{4} \d{4}$/,
message: '올바른 카드 번호 형식이 아닙니다.',
},
pattern: { value: /^\d{4} \d{4} \d{4} \d{4}$/, message: '올바른 카드 번호 형식이 아닙니다.' },
})}
onChange={handleInputChange('cardNumber')}
/>
Expand All @@ -76,48 +80,42 @@ const CardRegisterPage = () => {
<div>
<DefaultTextField
label="카드 소지자 이름"
className={`h-12 ${errors.cardHolder ? 'border-2 border-red-500' : ''}`}
placeholder="홍길동"
{...register('cardHolder', { required: '카드 소지자 이름을 입력하세요. ' })}
className={`h-12 ${errors.cardHolder ? 'border-2 border-red-500' : ''}`}
{...register('cardHolder', { required: '카드 소지자 이름을 입력하세요.' })}
/>
{errors.cardHolder && <p className="text-red-500 text-xs md:text-sm">{errors.cardHolder.message}</p>}
</div>

<div className="flex justify-between w-full gap-2">
<div>
<DefaultTextField
label="만료 월"
className={`h-10 ${errors.expiryMonth ? 'border-2 border-red-500' : ''}`}
placeholder="월"
{...register('expiryMonth', { required: '만료 월을 입력하세요.' })}
onChange={handleInputChange('expiryMonth')}
/>
{errors.expiryMonth && <p className="text-red-500 text-xs md:text-sm">{errors.expiryMonth.message}</p>}
</div>
<div>
<DefaultTextField
label="만료 년도"
className={`h-10 ${errors.expiryYear ? 'border-2 border-red-500' : ''}`}
placeholder="년도"
{...register('expiryYear', { required: '만료 년도를 입력하세요.' })}
onChange={handleInputChange('expiryYear')}
/>
{errors.expiryYear && <p className="text-red-500 text-xs md:text-sm">{errors.expiryYear.message}</p>}
</div>
<div>
<DefaultTextField
label="CVC/CVV"
className={`h-10 ${errors.cvc ? 'border-2 border-red-500' : ''}`}
placeholder="123"
{...register('cvc', { required: 'cvc를 입력하세요.' })}
onChange={handleInputChange('cvc')}
/>
{errors.cvc && <p className="text-red-500 text-xs md:text-sm">{errors.cvc.message}</p>}
</div>
<div>
<DefaultTextField
label="만료 날짜 (MM / YY)"
placeholder="MM / YY"
className={`h-12 ${errors.expiry ? 'border-2 border-red-500' : ''}`}
{...register('expiry', { required: '만료 날짜를 입력하세요.' })}
onChange={handleInputChange('expiry')}
/>
{errors.expiry && <p className="text-red-500 text-xs md:text-sm">{errors.expiry.message}</p>}
</div>

<div>
<DefaultTextField
label="CVC / CVV"
placeholder="123"
className={`h-12 ${errors.cvc ? 'border-2 border-red-500' : ''}`}
{...register('cvc', { required: 'CVC를 입력하세요.' })}
onChange={handleInputChange('cvc')}
/>
{errors.cvc && <p className="text-red-500 text-xs md:text-sm">{errors.cvc.message}</p>}
</div>
<div className="flex flex-grow" />

<form onSubmit={handleSubmit(onSubmit)} className="py-5">
<Button label="저장하기" onClick={() => console.log()} className="rounded-full w-full h-12" />
<Button
label="저장하기"
disabled={isDisabled}
className="rounded-full w-full h-12 bg-blue-500 disabled:bg-gray-400"
onClick={() => console.log()}
/>
</form>
</div>
</TicketHostLayout>
Expand Down