Skip to content

Commit c4e7d81

Browse files
[1단계 - 페이먼츠] 카멜(진나영) 미션 제출합니다. (#435)
* feat: 페어 프로그래밍 초기 세팅 Co-authored-by: dev-dino22 <[email protected]> * docs: 미션의 기능목록, 설계 구조 등 작성 Co-authored-by: dev-dino22 <[email protected]> * style: 전역 스타일링 적용 Co-authored-by: dev-dino22 <[email protected]> * feat: Input 컴포넌트 생성 Co-authored-by: dev-dino22 <[email protected]> * feat: InputForm 컴포넌트 생성 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 번호 입력 컴포넌트 작성 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 유효기간 입력 컴포넌트 작성 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 CVC 입력 컴포넌트 작성 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 입력 form 컴포넌트 작성 Co-authored-by: dev-dino22 <[email protected]> * refactor: InputForm이 Input을 children으로 받도록 리팩토링 Co-authored-by: dev-dino22 <[email protected]> * style: 전역 변수로 관리하는 색상 추가 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 브랜드 로고 이미지 생성 Co-authored-by: dev-dino22 <[email protected]> * style: 카드 입력 form 컴포넌트 스타일 적용 Co-authored-by: dev-dino22 <[email protected]> * style: 기본 스타일을 초기화하는 파일 수정 Co-authored-by: dev-dino22 <[email protected]> * style: 카드 입력 컴포넌트의 스타일 수정 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 정보를 입력하는 UI를 렌더링하는 함수 생성 Co-authored-by: dev-dino22 <[email protected]> * style: 카드 정보를 입력하는 UI에 스타일 추가 Co-authored-by: dev-dino22 <[email protected]> * style: 카드 입력 form의 스타일 조정 Co-authored-by: dev-dino22 <[email protected]> * feat: 사용자가 입력한 카드 정보를 프리뷰에 렌더링하는 기능 구현 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 번호와 유효기간 정보를 상태관리 하도록 코드 추가 Co-authored-by: dev-dino22 <[email protected]> * refactor: 사용자 카드 정보 등록 페이지가 렌더링되도록 app 수정 Co-authored-by: dev-dino22 <[email protected]> * feat: 사용자 입력값의 예외 처리 로직 생성 Co-authored-by: dev-dino22 <[email protected]> * feat: 사용자 입력값에 대한 피드백 메시지 렌더링 로직 추가 Co-authored-by: dev-dino22 <[email protected]> * refactor: 유효기간 검증 로직 수정 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 유효기간 검증 로직 추가 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 CVC 검증 로직 추가 Co-authored-by: dev-dino22 <[email protected]> * feat: 카드 번호 검증 로직 추가 Co-authored-by: dev-dino22 <[email protected]> * docs: 구현한 기능 목록 체크 * refactor: 상태 변경 함수의 타입 추가 * style: storybook에 css 적용 Co-authored-by: dev-dino22 <[email protected]> * refactor: 불필요한 props 제거 Co-authored-by: dev-dino22 <[email protected]> * test: storybook 비주얼 테스트 추가 Co-authored-by: dev-dino22 <[email protected]> * fix: 웹 배포 환경에 따라 이미지 경로 수정 * refactor: 불필요한 fragment 제거 * refactor: 카드 정보 상수 분리 * refactor: checkBrand 조건문 startsWith()로 가독성 개선 * refactor: 다시 유효한 값 입력 시 오류 스타일 삭제 * refactor: 유효기간 month, year로 나누고 전용 validator 분리 * refactor: 전역 상태 대신 ref로 유효기간 검증 관리 * refactor: 카드 유효기간입력 컴포넌트의 inputs를 map 함수 렌더링으로 변경 * refactor: CardExpirationDateInput 의 onChangeHandler 함수/상수 분리 및 추상화 * refactor: 카드프리뷰의 let 을 사용한 함수 내 상태관리에서 useMemo()훅 활용 * refactor: Input 컴포넌트에서 이제 불필요한 forwardRef 제거 및 props 전개 방식으로 변경 * chore: Input컴포넌트 폴명 파스칼케이스 수정을 위한 중간임시 폴더명 * feat: input 폴더명 카멜케이스 수정 * refactor: validateCardInput.ts 의 숫자문자열 검사 메서드명 수정 * refactor: validateCardInput.ts의 반환값 에러결과불리언객체로 통일 * refactor: Input 컴포넌트는 isValid 프롭을 전달받고 각 유효 상태를 사용처에서 제어하도록 변경 및 에러메세지 상수분리와 에러메세지 함수 작성 * chore: 불필요한 import 문 제거 --------- Co-authored-by: Jeongeun Lee <[email protected]> Co-authored-by: dev-dino22 <[email protected]>
1 parent 24604b7 commit c4e7d81

36 files changed

+1222
-7
lines changed

.storybook/preview-head.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
7+
<link href="../src/index.css" rel="stylesheet" />
8+
9+
<title>Document</title>
10+
</head>
11+
<body></body>
12+
</html>

README.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,111 @@
11
# react-payments
2+
3+
# 기능 목록
4+
5+
## 사용자 입력
6+
7+
- [x] 사용자는 카드 번호를 입력할 수 있다.
8+
- [x] 16자리의 입력 번호를 실시간 감지하여 알맞는 브랜드 로고를 UI에 표시한다. (Visa: 4로 시작 / MasterCard: 51~55로 시작 / 그외 로고 렌더링 x )
9+
- [x] 예외: 숫자만 가능 / 16자리 / 한칸에 네 자리 -> 피드백: 빨간 border & 포커스 & 밑에 피드백 문구 출력
10+
- [x] 사용자는 카드 유효기간을 입력할 수 있다.
11+
- [x] 예외: 숫자만 가능 / 2자리 / 월은 1~12 까지 연도 25~99까지 / 오늘 날짜를 기준으로 유효한 기간인지 검증 -> 피드백: 빨간 border & 포커스 & 밑에 피드백 문구 출력
12+
- [x] 사용자는 CVC 번호를 입력할 수 있다.
13+
- [x] 예외: 숫자만 가능 / 3자리 -> 피드백: 빨간 border & 포커스 & 밑에 피드백 문구 출력
14+
15+
## UI 업데이트
16+
17+
- [x] 사용자 입력에 따라 동시에 프리뷰를 업데이트한다.
18+
- [x] 입력할 때마다 한자리 단위로 실시간 업데이트
19+
- [x] 카드번호에 유효한 로고 실시간 업데이트
20+
- [x] 카드번호 뒷 8자리는 마스킹 처리
21+
22+
# 테스트 목록 (Storybook)
23+
24+
## 결제 입력 테스트 파일
25+
26+
- ### Describe
27+
28+
- 사용자가 카드 정보를 입력 시
29+
30+
- ### Context
31+
32+
- 사용자가 카드 번호를 입력할 경우
33+
- ### It
34+
35+
- 사용자가 16자리를 다 채우지 않은 채 카드번호 인풋 포커스를 이탈하면 "유효하지 않은 카드번호입니다. 16자리를 입력해주세요." 피드백을 출력한다.
36+
- 한 칸에 4자리를 다 채우지 않은 채 다음 포커스 이동하면 "유효하지 않은 번호입니다. 4자리를 입력해주세요." 피드백을 출력한다.
37+
- 사용자가 숫자 이외를 입력하려고 하면 "유효하지 않은 입력입니다. 숫자만 입력해주세요." 피드백을 출력한다.
38+
39+
- ### Context
40+
- 사용자가 카드 유효 기간을 입력할 경우
41+
- ### It
42+
43+
- 사용자가 4자리를 다 채우지 않은 채 유효기간 인풋 포커스를 이탈하면 "유효하지 않은 카드번호입니다. 16자리를 입력해주세요." 피드백을 출력한다.
44+
- 한 칸에 2자리를 다 채우지 않은 채 다음 포커스 이동하면 "유효하지 않은 번호입니다. 2자리를 입력해주세요." 피드백을 출력한다.
45+
- 사용자가 숫자 이외를 입력하려고 하면 "유효하지 않은 입력입니다. 숫자만 입력해주세요." 피드백을 출력한다.
46+
- 사용자가 현재 날짜 이전 기간을 입력할 경우 "유효하지 않은 카드입니다. 유효 기간을 확인해주세요." 피드백을 출력한다.
47+
48+
- ### Context
49+
- 사용자가 CVC를 입력할 경우
50+
- ### It
51+
- 사용자가 3자리를 다 채우지 않은 채 CVC 인풋 포커스를 이탈하면 "유효하지 않은 CVC입니다. 3자리를 입력해주세요." 피드백을 출력한다.
52+
- 사용자가 숫자 이외를 입력하려고 하면 "유효하지 않은 입력입니다. 숫자만 입력해주세요." 피드백을 출력한다.
53+
54+
## 카드 프리뷰 UI 테스트 파일
55+
56+
- ### Describe
57+
- 사용자가 카드 정보를 입력 시
58+
- ### Context
59+
- 사용자가 카드 번호를 입력할 경우
60+
- ### It
61+
- 카드 번호 첫 자리가 4로 시작하면 VISA 로고가 렌더링된다.
62+
- 카드 번호 첫 자리가 51~55 로 시작하면 MasterCard 로고가 렌더링된다.
63+
- 사용자가 카드번호를 입력할 시 실시간으로 카드 번호가 업데이트된다.
64+
- 카드번호 뒷 8자리는 마스킹 처리하여 표시한다.
65+
- ### Context
66+
- 사용자가 유효 기간을 입력할 경우
67+
- ### It
68+
- 사용자가 유효 기간을 입력할 시 실시간으로 카드 번호가 업데이트된다.
69+
70+
# 설계 구조
71+
72+
## 디렉터리 구조
73+
74+
```js
75+
76+
77+
src/
78+
├── components/
79+
│ ├── common/
80+
│ │ └── inputForm/
81+
│ │ ├── InputForm.tsx
82+
│ │ └── input/
83+
│ │ └── Input.tsx
84+
85+
│ ├── paymentInputPage/
86+
│ │ ├── cardInputForm/
87+
│ │ │ └── cardInput/
88+
│ │ │ ├── CardCVCInput.tsx
89+
│ │ │ ├── CardExperienceInput.tsx
90+
│ │ │ └── CardNumberInput.tsx
91+
│ │ └── cardPreview/
92+
│ │ └── CardPreview.tsx
93+
94+
```
95+
96+
## 컴포넌트 분리
97+
98+
## 상태 관리
99+
100+
paymentsInputPage는 cardInputForm의 상태를 cardPreview에 반영
101+
102+
- cardInputForm 은 paymentsInputPage의 setState 콜백을 받아 자식들(card~input 컴포넌트들) 상태를 변경
103+
104+
- paymentsInputPage는 state를 cardPreview에 전달
105+
- cardPreview는 state에 따라 실시간 변경
106+
107+
# 컨벤션
108+
109+
## CSS 작성 방식
110+
111+
- Module CSS

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"version": "0.0.0",
55
"type": "module",
66
"scripts": {
7-
"dev": "vite",
7+
"dev": "vite dev --host 0.0.0.0",
88
"build": "tsc && vite build",
99
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1010
"preview": "vite preview",

public/Mastercard.png

1.49 KB
Loading

public/Visa.png

1.68 KB
Loading

src/App.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import "./App.css";
2-
2+
import PaymentInputPage from "./components/paymentInputPage/PaymentInputPage";
33
function App() {
4-
return (
5-
<>
6-
<h1>React Payments</h1>
7-
</>
8-
);
4+
return <PaymentInputPage />;
95
}
106

117
export default App;

src/color.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:root {
2+
--grey: #acacac;
3+
--black: #000000;
4+
--white: #ffffff;
5+
--red: #ff3d3d;
6+
--gold: #ddcd78;
7+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.container {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 16px;
5+
}
6+
7+
.inputForm {
8+
display: flex;
9+
justify-content: space-between;
10+
align-items: center;
11+
}
12+
13+
.inputContainer {
14+
display: flex;
15+
justify-content: space-between;
16+
align-items: center;
17+
gap: 10px;
18+
}
19+
20+
.inputBox {
21+
display: flex;
22+
flex-direction: column;
23+
gap: 8px;
24+
}
25+
26+
label {
27+
color: var(--black);
28+
}
29+
30+
.titleBox {
31+
display: flex;
32+
flex-direction: column;
33+
gap: 8px;
34+
35+
& p {
36+
color: var(--grey);
37+
}
38+
}
39+
40+
.feedbackMessage {
41+
color: var(--red);
42+
height: 18px;
43+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import styles from './InputForm.module.css';
2+
3+
export interface InputFormProps {
4+
title: string;
5+
label: string;
6+
description?: string;
7+
feedbackMessage?: string;
8+
children: React.ReactNode;
9+
}
10+
11+
function InputForm(props: InputFormProps) {
12+
const { title, label, description, feedbackMessage } = props;
13+
14+
return (
15+
<div className={styles.container}>
16+
<div className={styles.titleBox}>
17+
<h3 className='tx-xl'>{title}</h3>
18+
{description && <p className='tx-md'>{description}</p>}
19+
</div>
20+
<div className={styles.inputBox}>
21+
<label className='tx-lg'>{label}</label>
22+
<div className={styles.inputContainer}>{props.children}</div>
23+
<p
24+
style={
25+
feedbackMessage
26+
? { visibility: 'visible' }
27+
: { visibility: 'hidden' }
28+
}
29+
className={`${styles.feedbackMessage}`}
30+
>
31+
{feedbackMessage}
32+
</p>
33+
</div>
34+
</div>
35+
);
36+
}
37+
38+
export default InputForm;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.input {
2+
width: 100%;
3+
border: 1px solid var(--grey);
4+
border-radius: 4px;
5+
color: var(--black);
6+
padding: 12px 8px;
7+
&::placeholder {
8+
color: var(--grey);
9+
}
10+
11+
&:focus {
12+
border: 1px solid var(--black);
13+
}
14+
&.isNotValid {
15+
border: 1px solid var(--red);
16+
}
17+
}

0 commit comments

Comments
 (0)