diff --git a/.babelrc.json b/.babelrc.json new file mode 100644 index 0000000000..701fada78c --- /dev/null +++ b/.babelrc.json @@ -0,0 +1,7 @@ +{ + "presets": [ + "@babel/preset-env", + "@babel/preset-react", + "@babel/preset-typescript" + ] +} \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..45050bc1cc --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "plugin:react/recommended", + "airbnb" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "react", + "@typescript-eslint" + ], + "rules": { + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..f4a41a1764 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.idea +*.log \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..cf1966d69d --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "useTabs": false, + "tabWidth": 2, + "singleQuote": true, + "trailingComma": "all", + "semi": true, + "endOfLine": "auto" +} \ No newline at end of file diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 0000000000..ed5258ae08 --- /dev/null +++ b/.storybook/main.js @@ -0,0 +1,22 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/components/**/*.stories.@(js|jsx|ts|tsx)'], + addons: ['@storybook/addon-links', '@storybook/addon-essentials'], + webpackFinal: async (config) => { + config.resolve.alias['@components'] = path.resolve( + __dirname, + '../src/components', + ); + config.resolve.alias['@constant'] = path.resolve( + __dirname, + '../src/constant', + ); + config.resolve.alias['@utils'] = path.resolve(__dirname, '../src/utils'); + config.resolve.alias['@types'] = path.resolve( + __dirname, + '../src/typesCards', + ); + return config; + }, +}; diff --git a/.storybook/preview.js b/.storybook/preview.js new file mode 100644 index 0000000000..48afd568ae --- /dev/null +++ b/.storybook/preview.js @@ -0,0 +1,9 @@ +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +} \ No newline at end of file diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index e69de29bb2..2702d4a95d 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -0,0 +1,25 @@ +# 카드 추가 폼 +- 카드 번호 + - [x] 자동 포커싱 + - [x] 4자리번호 입력 완료시 다음 창으로 이동 + - [ ] 6자리 입력 완료시 카드사 매칭 + - [ ] 매칭되는 카드사가 없는 경우 카드사 선택 모달 + - [x] 뒤에서부터 8자리의 카드번호는 마스킹 처리 + - [x] 카드 번호는 실시간으로 카드에 반영 + - [x] 카드 번호 입력 완료시 카드 만료일로 포커싱 이동 +- 카드 만료시기 + - [x] MM(월) 입력이 완료되면 YY(년) 입력으로 포커스 이동 + - [x] 4자리 모두 입력시 카드 소유자로 포커싱 이동 +- 카드 소유자 + - [ ] 알파벳만 입력가능 + - [ ] 알파벳 대문자로 항상 적용 + - [ ] 이름은 30자 이내만 가능 +- 보안 코드 + - [x] 입력시 마스킹처리 + - [ ] 3자리 입력시 비밀번호 창으로 포커스 이동 +- 비밀번호 + - [x] 입력시 마스킹 처리 +- 카드 등록 + - [ ] 모든 입력이 완료됐을 시 활성화 +# 카드 확인 +- 등록된 카드가 표시된다. \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000..9ab34e367d --- /dev/null +++ b/package.json @@ -0,0 +1,58 @@ +{ + "name": "react-payments", + "version": "1.0.0", + "description": "

\r \r

\r

Level2 - 페이먼츠

\r

React 모바일 페이먼츠 애플리케이션

\r

", + "main": "index.js", + "scripts": { + "start": "webpack serve", + "build": "webpack --config webpack.config.ts --watch", + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/kmin-283/react-payments.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/kmin-283/react-payments/issues" + }, + "homepage": "https://github.com/kmin-283/react-payments#readme", + "devDependencies": { + "@babel/core": "^7.14.6", + "@babel/preset-env": "^7.14.7", + "@babel/preset-react": "^7.14.5", + "@babel/preset-typescript": "^7.14.5", + "@babel/register": "^7.14.5", + "@storybook/addon-actions": "^6.3.2", + "@storybook/addon-essentials": "^6.3.2", + "@storybook/addon-links": "^6.3.2", + "@storybook/react": "^6.3.2", + "@types/node": "^16.0.0", + "@types/react": "^17.0.13", + "@types/react-dom": "^17.0.8", + "@types/webpack": "^5.28.0", + "@types/webpack-dev-server": "^3.11.5", + "@typescript-eslint/eslint-plugin": "^4.28.1", + "@typescript-eslint/parser": "^4.28.1", + "babel-loader": "^8.2.2", + "css-loader": "^5.2.6", + "eslint": "^7.30.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react-hooks": "^4.2.0", + "prettier": "^2.3.2", + "style-loader": "^3.0.0", + "typescript": "^4.3.5", + "webpack": "^5.42.0", + "webpack-cli": "^4.7.2", + "webpack-dev-server": "^3.11.2" + }, + "dependencies": { + "react": "^17.0.2", + "react-dom": "^17.0.2" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000..bc6c83307d --- /dev/null +++ b/public/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + + Payments + + +
+ + + \ No newline at end of file diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000000..b9a7cf1c23 --- /dev/null +++ b/src/app.css @@ -0,0 +1,4 @@ +#app { + font-family: 'Noto Sans KR', sans-serif; + box-sizing: border-box; +} \ No newline at end of file diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 0000000000..e459c38c22 --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { View } from '@constant/constant'; +import CardList from '@components/CardList/cardList'; +import CardAddForm from '@components/CardAddForm/cardAddForm'; +import Card from '@typesCards/types.cards'; +import './app.css'; + +const App = () => { + const [currentView, setCurrentView] = useState(View.List); + const [cardList, setCardList] = useState([ + { + cardBrand: 'blue-card', + cardNumber: ['1234', '1234', '1234', '1234'], + expDate: '0123', + publisher: 'person A', + cvc: '123', + password: '1234', + nickname: '파랑이 좋겠어', + }, + { + cardBrand: 'red-card', + cardNumber: ['3456', '3456', '3456', '3456'], + expDate: '1223', + publisher: 'person A', + cvc: '123', + password: '1234', + nickname: '빨강이 좋겠어', + }, + ]); + + return ( + <> + {currentView === View.List && ( + + )} + {currentView === View.Add && ( + + )} + + ); +}; + +export default App; diff --git a/src/components/CardAddComplete/cardAddComplete.tsx b/src/components/CardAddComplete/cardAddComplete.tsx new file mode 100644 index 0000000000..61fc9cb633 --- /dev/null +++ b/src/components/CardAddComplete/cardAddComplete.tsx @@ -0,0 +1,74 @@ +import React, { Dispatch, SetStateAction, useState } from 'react'; +import Label from '@components/share/Label/label'; +import Input from '@components/share/Input/input'; +import Button from '@components/share/Button/button'; +import { View } from '@constant/constant'; +import Card from '@typesCards/types.cards'; + +interface CardAddCompleteProps { + cardList: Card[]; + brandName: string; + firstNumbers: string; + secondNumbers: string; + thirdNumbers: string; + fourthNumbers: string; + expireDate: string; + publisher: string; + cvc: string; + password: string; + setCurrentView: Dispatch>; + setCardList: Dispatch>; +} + +const CardAddComplete = ({ + cardList, + brandName, + firstNumbers, + secondNumbers, + thirdNumbers, + fourthNumbers, + setCurrentView, + setCardList, + expireDate, + publisher, + cvc, + password, +}: CardAddCompleteProps) => { + const [nickname, setNickname] = useState('기본 카드'); + const handleNicknameChange = ({ target }: { target: HTMLInputElement }) => { + setNickname(target.value); + }; + const goToCardList = (event: Event) => { + event.preventDefault(); + const newCard: Card = { + cardBrand: brandName, + cardNumber: [firstNumbers, secondNumbers, thirdNumbers, fourthNumbers], + expDate: expireDate, + publisher, + cvc, + password, + nickname, + }; + setCurrentView(View.List); + setCardList([...cardList, newCard]); + }; + return ( + <> +