-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 페이먼츠 구현 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a4742b2
75ff394
e21351b
c3e6810
66e7749
fd390cb
bf5d9bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,65 @@ | ||
| # react-payments | ||
|
|
||
| ## 📂 파일 구조 | ||
| ``` | ||
| 📦src | ||
| ┣ 📂assets | ||
| ┃ ┣ 📜Mastercard.svg | ||
| ┃ ┗ 📜Visa.svg | ||
| ┣ 📂components | ||
| ┃ ┣ 📜Card.tsx | ||
| ┃ ┣ 📜CardForm.tsx | ||
| ┃ ┗ 📜CardFormInput.tsx | ||
| ┣ 📂constants | ||
| ┃ ┣ 📜errorMessage.ts | ||
| ┃ ┣ 📜index.ts | ||
| ┃ ┣ 📜textConstants.ts | ||
| ┃ ┗ 📜usingNumbers.ts | ||
| ┣ 📂pages | ||
| ┃ ┗ 📜Home.tsx | ||
| ┣ 📂stories | ||
| ┃ ┗ 📜App.stories.tsx | ||
| ┣ 📂styles | ||
| ┃ ┣ 📜Card.module.css | ||
| ┃ ┣ 📜CardForm.module.css | ||
| ┃ ┗ 📜CardFormInput.module.css | ||
| ┣ 📂types | ||
| ┃ ┣ 📜cardTypes.ts | ||
| ┃ ┣ 📜errorType.ts | ||
| ┃ ┗ 📜index.ts | ||
| ┣ 📂utils | ||
| ┃ ┣ 📜cardNumberValidators.tsx | ||
| ┃ ┣ 📜errorHelpers.tsx | ||
| ┃ ┣ 📜expirationDateValidators.tsx | ||
| ┃ ┣ 📜index.ts | ||
| ┃ ┣ 📜inputFilters.tsx | ||
| ┃ ┗ 📜ownerNameValidators.tsx | ||
| ┣ 📜App.tsx | ||
| ┣ 📜index.css | ||
| ┣ 📜main.tsx | ||
| ┗ 📜vite-env.d.ts | ||
| ``` | ||
|
|
||
| ## 💳 기능 요구 사항 | ||
|
|
||
| - 카드 번호 입력 및 식별 | ||
| - 카드 번호의 3~4번 블럭은 숨김 처리한다. | ||
| - 숫자를 입력하지 않으면 올바르게 입력하라는 피드백을 보여주고, 입력을 제한한다. | ||
| - 각 카드 번호 입력 블럭은 0~9의 숫자 4자리로 이루어져있다. | ||
| - 입력은 숫자만 가능하며, 유효하지 않은 번호 입력 시 피드백을 제공한다. | ||
| - 카드 번호 4자리 미만으로 입력하는 중에 포커스를 넘기려고 한다면 에러로 막는다. | ||
| - 카드 유효기간 입력 | ||
| - 입력은 숫자만 가능하며 숫자가 아닐시 피드백을 제공한다. | ||
| - 유효하지 않은 월을 입력 시(ex 13월) 피드백을 제공한다. | ||
| - 현재보다 이전 날짜를 입력 시 피드백을 제공한다. | ||
| - 한 자리 숫자를 입력 시 자동으로 형식에 맞춰 0을 넣어준다. | ||
| - 카드 소유자 이름 입력 | ||
| - 소문자로 입력 시 강제로 대문자로 변환한다. | ||
| - 영어가 아닌 문자 입력 시 입력을 제한하고 피드백을 제공한다. | ||
| - (추가) 사용자 이름은 최소 두글자 이상 입력해야 한다. | ||
| - 실시간 프리뷰 업데이트 | ||
| - 카드 번호가 4로 시작하면 Visa카드 로고를 카드 프리뷰에 업데이트한다. | ||
| - 카드 번호가 51~55로 시작하면 MasterCard 로고를 카드 프리뷰에 업데이트한다. | ||
| - 사용자가 카드 번호 입력 시 실시간으로 카드 프리뷰에 업데이트한다. | ||
| - 사용자가 카드 유효기간 입력 시 실시간으로 카드 프리뷰에 업데이트한다. | ||
| - 사용자가 카드 소유자 이름 입력 시 실시간으로 카드 프리뷰에 업데이트한다. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,11 +1,7 @@ | ||||||
| import "./App.css"; | ||||||
| import Home from "../src/pages/Home"; | ||||||
|
|
||||||
| function App() { | ||||||
| return ( | ||||||
| <> | ||||||
| <h1>React Payments</h1> | ||||||
| </> | ||||||
| ); | ||||||
| return <Home></Home>; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사소한 내용이지만, 안에 children을 받지 않는 컴포넌트의 경우 아래처럼 명확히 단일 태그라는 것을 보여주면 좋아요.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| export default App; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import styles from "../styles/Card.module.css"; | ||
| import type { CardNumbers, ExpirationDate } from "../types/cardTypes"; | ||
| import { hideNumber, setCardLogo } from "../utils/cardNumberValidators"; | ||
|
|
||
| interface CardProps { | ||
| cardNumbers: CardNumbers; | ||
| expirationDate: ExpirationDate; | ||
| owner: string; | ||
| } | ||
|
|
||
| function Card({ owner, expirationDate, cardNumbers }: CardProps) { | ||
| const expirationMonth = expirationDate.month; | ||
| const expirationYear = expirationDate.year; | ||
| const cardLogo = setCardLogo(cardNumbers.firstBlock); | ||
| const date = | ||
| expirationMonth.length > 0 || expirationYear.length > 0 | ||
| ? `${expirationMonth}/${expirationYear}` | ||
| : ""; | ||
| const numbers: string = `${cardNumbers.firstBlock} ${cardNumbers.secondBlock} ${hideNumber(cardNumbers.thirdBlock)} ${hideNumber(cardNumbers.fourthBlock)}`; | ||
|
|
||
| return ( | ||
| <> | ||
| <div className={styles.card}> | ||
| <div className={styles["card-header"]}> | ||
| <div className={styles["card-ic"]}></div> | ||
| {cardLogo ? ( | ||
| <img className={styles.logo} src={cardLogo} /> | ||
| ) : ( | ||
| <div className={styles.logo}></div> | ||
| )} | ||
| </div> | ||
| <p className={styles["card-detail"]}>{numbers}</p> | ||
| <p className={styles["card-detail"]}>{date}</p> | ||
| <p className={styles["card-detail"]}>{owner}</p> | ||
| </div> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default Card; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import styles from "../styles/CardForm.module.css"; | ||
|
|
||
| interface CardFormProps { | ||
| cardFormLabelText: string; | ||
| cardFormLabelCaption?: string; | ||
| cardLabelText: string; | ||
| } | ||
|
|
||
| function CardForm({ | ||
| cardFormLabelText, | ||
| cardFormLabelCaption, | ||
| cardLabelText, | ||
| }: CardFormProps) { | ||
| return ( | ||
| <> | ||
| <h3 className={styles.label}>{cardFormLabelText}</h3> | ||
| {cardFormLabelCaption && ( | ||
| <p className={styles.caption}>{cardFormLabelCaption}</p> | ||
| )} | ||
| <p className={styles["form-label"]}>{cardLabelText}</p> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default CardForm; |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 한번 input의 조건을 만족하면 다음 input으로 자동으로 포커스가 이동하게 설계해 보는 건 어떨까요? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import React from "react"; | ||
| import type { ChangeEvent } from "../types/cardTypes"; | ||
| import styles from "../styles/CardFormInput.module.css"; | ||
|
|
||
| interface CardFormProps { | ||
| name: string; | ||
| cardPlaceHolder: string; | ||
| cardInput: number | string; | ||
| handleChange: (e: ChangeEvent) => void; | ||
| width: string; | ||
| isDisable?: boolean; | ||
| hasError: boolean; | ||
| } | ||
|
|
||
| function CardFormInput({ | ||
| name, | ||
| cardPlaceHolder, | ||
| cardInput, | ||
| handleChange, | ||
| width, | ||
| isDisable, | ||
| hasError, | ||
| }: CardFormProps) { | ||
| return ( | ||
| <> | ||
| <input | ||
| className={styles.inputBox} | ||
| name={name} | ||
| value={cardInput} | ||
| placeholder={cardPlaceHolder} | ||
| type="text" | ||
| onChange={handleChange} | ||
| style={{ | ||
| width: width, | ||
| borderColor: hasError ? "red" : "#d5d5d5", | ||
| }} | ||
| disabled={isDisable} | ||
| ></input> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| export default CardFormInput; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| export const ERROR_MESSAGE = { | ||
| ONLY_NUMBER: "숫자만 입력 가능합니다.", | ||
| ONLY_ENGLISH: "영어만 입력 가능합니다.", | ||
| MIN_LENGTH_REQUIRED: "두글자 이상 입력하세요.", | ||
| MONTH_OUT_OF_RANGE: "유효한 월을 입력하세요.", | ||
| EXPIRATION_DATE_IN_PAST: "유효기간 날짜가 지났습니다.", | ||
| VALID_MONTH_RANGE: "1월부터 12월 사이만 입력하세요.", | ||
| PAST_DATE: "유효기간이 지난 연도입니다.", | ||
| OVER_MAX_VALID_YEAR: "유효기간은 최대 10년까지만 가능합니다.", | ||
| MIN_LENGTH_TWO: "숫자 두 자리를 입력하세요.", | ||
| REQUIRE_FOUR_DIGIT_NUMBER: "4자리 숫자를 입력하세요.", | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export * from "./errorMessege"; | ||
| export * from "./textConstants"; | ||
| export * from "./usingNumbers"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| const CARD_FORM_LABELS = { | ||
| CARD_NUMBER: "결제할 카드 번호를 입력해 주세요", | ||
| CARD_NUMBER_CAPTION: "본인 명의의 카드만 결제 가능합니다.", | ||
| EXPIRATION_DATE: "카드 유효기간을 입력해 주세요", | ||
| EXPIRATION_DATE_CAPTION: "월/년도(MMYY)를 순서대로 입력해 주세요.", | ||
| CARD_OWNER: "카드 소유자 이름을 입력해주세요", | ||
| }; | ||
|
|
||
| const CARD_LABELS = { | ||
| CARD_NUMBER: "카드 번호", | ||
| EXPIRATION_DATE: "유효기간", | ||
| CARD_OWNER: "소유자 이름", | ||
| }; | ||
|
|
||
| const CARD_PLACEHOLDERS = { | ||
| CARD_NUMBER: "1234", | ||
| EXPIRATION_MONTH: "MM", | ||
| EXPIRATION_YEAR: "YY", | ||
| CARD_OWNER: "JOHN DOE", | ||
| }; | ||
|
|
||
| export { CARD_FORM_LABELS, CARD_LABELS, CARD_PLACEHOLDERS }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| export const CARD_LOGO_NUMBER = { | ||
| VISA_NUMBER: "4", | ||
| MASTER_MIN_NUMBER: 51, | ||
| MASTER_MAX_NUMBER: 55, | ||
| }; | ||
|
|
||
| export const CARD_PREFIX_LENGTH = { | ||
| VISA: 1, | ||
| MASTER: 2, | ||
| }; | ||
|
|
||
| export const CARD_NUMBER_MAX_LENGTH = 4; | ||
|
|
||
| export const MASK_SYMBOL = "*"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,10 @@ | ||
| import React from 'react' | ||
| import ReactDOM from 'react-dom/client' | ||
| import App from './App.tsx' | ||
| import './index.css' | ||
| import React from "react"; | ||
| import ReactDOM from "react-dom/client"; | ||
| import App from "./App.tsx"; | ||
| import "./index.css"; | ||
|
|
||
| ReactDOM.createRoot(document.getElementById('root')!).render( | ||
| ReactDOM.createRoot(document.getElementById("root")!).render( | ||
| <React.StrictMode> | ||
| <App /> | ||
| </React.StrictMode>, | ||
| ) | ||
| </React.StrictMode> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
폴더 구조를 굉장히 깔끔하게 잘 짜주셨네요 👍