From 2213063184e5cb22d4bd9e12a64ee10ee705c486 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 14:44:57 +0900 Subject: [PATCH 01/65] =?UTF-8?q?chore:=20prettier=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- .prettierrc.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .prettierrc.js diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000000..7cfd53ce2e --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,12 @@ +module.exports = { + printWidth: 120, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + trailingComma: 'all', + bracketSpacing: true, + arrowParens: 'avoid', + proseWrap: 'never', + endOfLine: 'auto' + }; From 890c4c3eba79ff5414d5c70eba9ac09adf6e9c78 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 16:43:21 +0900 Subject: [PATCH 02/65] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EB=B6=84=EC=84=9D=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=84=A4=EA=B3=84=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- README.md | 19 +++++++++++++++++++ docs/REQUIREMENTS.md | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 docs/REQUIREMENTS.md diff --git a/README.md b/README.md index 8d917806e0..2a0a4be1b9 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ # react-payments + +
} /> + +const Form = () => { + title + subtitle + Inputs +} + +Container +Wrapper +Section +Box + +const cardForm = () => { + return ( + + ) +} diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md new file mode 100644 index 0000000000..255d875b75 --- /dev/null +++ b/docs/REQUIREMENTS.md @@ -0,0 +1,36 @@ +# react-payments +스타일링 : styled-components + +1. 사용자 관점에서 보이는 기능들을 기준으로 분류 +2. 개발자 관점에서 컴포넌트 기준으로 분류 + +# 기능 요구 사항 분석 +- [ ] 카드 번호를 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) + - [ ] 4자리 숫자만 입력할 수 있다. + - [ ] 카드 번호 뒤의 8자리는 `*` 로 표시한다. +- [ ] 입력한 카드 번호에 맞는 브랜드 로고를 UI에 표시한다. (해당하는 경우) + - [ ] Visa: 4로 시작하는 16자리 숫자 + - [ ] MasterCard: 51~55로 시작하는 16자리 숫자 +- [ ] 카드 유효기간을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) + - [ ] 월(1월~12월)과 년도(2024년~2040년)를 범위 내에서만 입력할 수 있다. + - [ ] 숫자만 입력할 수 있다. +- [ ] 카드 소유자 이름을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) + - [ ] 영문자 대문자만 입력할 수 있다. + - [ ] 이름은 최소 1자에서 100자 내로 입력할 수 있다. + - [ ] 소문자로 입력해도 대문자로 자동 변환된다. (optional) +- [ ] 실시간으로 입력하는 카드 정보를 보여준다. + +# 컴포넌트 설계 +- 공통 컴포넌트 + - Input + - Container + +- 컴포넌트 + - CardRegistration (카드 번호, 유효기간, 소유자 이름을 1개의 input state로 관리) + - CardPreview (카드 프리뷰) + - CardNumberContainer (카드 번호) + - CardExpiryDateContainer (카드 유효기간) + - CardHolderNameContainer (카드 소유자 이름) + + + From b4f99b345779ccc62a9e05539d85dc7af4eb10c0 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 17:57:48 +0900 Subject: [PATCH 03/65] =?UTF-8?q?chore(README):=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- README.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/README.md b/README.md index 2a0a4be1b9..8d917806e0 100644 --- a/README.md +++ b/README.md @@ -1,20 +1 @@ # react-payments - -} /> - -const Form = () => { - title - subtitle - Inputs -} - -Container -Wrapper -Section -Box - -const cardForm = () => { - return ( - - ) -} From 0e697e1631862ea20901bfc6e07c631e4ea2d157 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 17:58:07 +0900 Subject: [PATCH 04/65] =?UTF-8?q?chore(prettier):=20=ED=99=95=EC=9E=A5?= =?UTF-8?q?=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- .prettierrc.cjs | 12 ++++++++++++ .prettierrc.js | 12 ------------ 2 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 .prettierrc.cjs delete mode 100644 .prettierrc.js diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000000..648fefde7e --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,12 @@ +module.exports = { + printWidth: 120, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + trailingComma: 'all', + bracketSpacing: true, + arrowParens: 'avoid', + proseWrap: 'never', + endOfLine: 'auto', +}; diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 7cfd53ce2e..0000000000 --- a/.prettierrc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - printWidth: 120, - tabWidth: 2, - useTabs: false, - semi: true, - singleQuote: true, - trailingComma: 'all', - bracketSpacing: true, - arrowParens: 'avoid', - proseWrap: 'never', - endOfLine: 'auto' - }; From 565fdd07988f26a40a32e0ab94c685cba597a379 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 17:59:38 +0900 Subject: [PATCH 05/65] =?UTF-8?q?chore(eslint):=20eslint=EC=97=90=20pretti?= =?UTF-8?q?er=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- .eslintrc.cjs | 1 + package-lock.json | 153 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 + 3 files changed, 157 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 200a166584..6b8931c929 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,6 +2,7 @@ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ + "plugin:prettier/recommended", "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended", diff --git a/package-lock.json b/package-lock.json index 9afcbd2b07..e3bcc0dd9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,9 +27,12 @@ "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-storybook": "^0.8.0", + "prettier": "^3.2.5", "storybook": "^8.0.8", "typescript": "^5.2.2", "vite": "^5.2.0" @@ -2956,6 +2959,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -6885,6 +6900,48 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", @@ -7293,6 +7350,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -10098,6 +10161,18 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -11311,6 +11386,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -14245,6 +14342,12 @@ "dev": true, "optional": true }, + "@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true + }, "@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -17110,6 +17213,23 @@ } } }, + "eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "requires": {} + }, + "eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + } + }, "eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", @@ -17383,6 +17503,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -19372,6 +19498,15 @@ "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -20297,6 +20432,24 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "requires": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + } + } + }, "tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", diff --git a/package.json b/package.json index 1613fb4bec..eecb4650f2 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,12 @@ "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-storybook": "^0.8.0", + "prettier": "^3.2.5", "storybook": "^8.0.8", "typescript": "^5.2.2", "vite": "^5.2.0" From c8840e2c9e2d711cc8af3003c102d16730663c94 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 18:00:21 +0900 Subject: [PATCH 06/65] =?UTF-8?q?feat(useInput):=20useInput=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=ED=9B=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/hooks/useInput.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/hooks/useInput.ts diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts new file mode 100644 index 0000000000..7fa36c1555 --- /dev/null +++ b/src/hooks/useInput.ts @@ -0,0 +1,23 @@ +import { useState } from 'react'; + +interface UseInputProps { + validate: (value: string) => boolean; + handleError: () => void; +} + +const useInput = ({ validate, handleError }: UseInputProps) => { + const [value, setValue] = useState(''); + + const handleChange = (value: string) => { + if (!validate(value)) { + handleError(); + return; + } + + setValue(value); + }; + + return [value, handleChange]; +}; + +export default useInput; From 33ee18074ff23a44b1e7123d679a0f869d8467d7 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 19:26:24 +0900 Subject: [PATCH 07/65] =?UTF-8?q?feat(Input):=20Input=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/common/Input.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/components/common/Input.tsx diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx new file mode 100644 index 0000000000..efc3c9aaab --- /dev/null +++ b/src/components/common/Input.tsx @@ -0,0 +1,24 @@ +interface InputProps { + value: string; + setValue: (value: string) => void; + validate: (value: string) => boolean; + handleError: () => void; + width?: string; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const Input = ({ value, setValue, validate, handleError, width = '100%' }: InputProps) => { + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value; + if (!validate(value)) { + handleError(); + return; + } + + setValue(value); + }; + + return ; +}; + +export default Input; From bfcc7ef7965dc30803e02539dd3834c1e5d1687b Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 22:10:17 +0900 Subject: [PATCH 08/65] =?UTF-8?q?chore:=20=20styled-components=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- package-lock.json | 241 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 3 +- 2 files changed, 232 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index e3bcc0dd9c..274a70ca69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "0.0.0", "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "styled-components": "^6.1.8" }, "devDependencies": { "@chromatic-com/storybook": "^1.3.3", @@ -2210,6 +2211,24 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", @@ -4873,6 +4892,11 @@ "@types/send": "*" } }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -5849,6 +5873,14 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001610", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", @@ -6267,6 +6299,24 @@ "node": ">=8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -9377,7 +9427,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -10043,8 +10092,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -10137,6 +10185,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11074,6 +11127,11 @@ "node": ">=8" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11147,7 +11205,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11362,6 +11419,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -13893,6 +14019,24 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, + "@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", @@ -15706,6 +15850,11 @@ "@types/send": "*" } }, + "@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -16398,6 +16547,11 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-lite": { "version": "1.0.30001610", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001610.tgz", @@ -16703,6 +16857,21 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -18964,8 +19133,7 @@ "nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -19430,8 +19598,7 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { "version": "2.3.1", @@ -19486,6 +19653,11 @@ "source-map-js": "^1.2.0" } }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -20200,6 +20372,11 @@ "kind-of": "^6.0.2" } }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -20254,8 +20431,7 @@ "source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-support": { "version": "0.5.21", @@ -20417,6 +20593,49 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "requires": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "dependencies": { + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + } + } + }, + "stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index eecb4650f2..c1a136f945 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "dependencies": { "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "styled-components": "^6.1.8" }, "devDependencies": { "@chromatic-com/storybook": "^1.3.3", From 67350009b9d5f4178a566be271683abf6c74735a Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 22:13:30 +0900 Subject: [PATCH 09/65] =?UTF-8?q?docs(REQUIREMENTS):=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=9A=94=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- docs/REQUIREMENTS.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 255d875b75..a7cd049029 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -1,6 +1,5 @@ # react-payments -스타일링 : styled-components - +- 스타일링 : styled-components 1. 사용자 관점에서 보이는 기능들을 기준으로 분류 2. 개발자 관점에서 컴포넌트 기준으로 분류 @@ -14,9 +13,9 @@ - [ ] 카드 유효기간을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - [ ] 월(1월~12월)과 년도(2024년~2040년)를 범위 내에서만 입력할 수 있다. - [ ] 숫자만 입력할 수 있다. -- [ ] 카드 소유자 이름을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - - [ ] 영문자 대문자만 입력할 수 있다. - - [ ] 이름은 최소 1자에서 100자 내로 입력할 수 있다. +- [x] 카드 소유자 이름을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) + - [x] 영문자 대문자만 입력할 수 있다. + - [x] 이름은 100자 내로 입력할 수 있다. - [ ] 소문자로 입력해도 대문자로 자동 변환된다. (optional) - [ ] 실시간으로 입력하는 카드 정보를 보여준다. @@ -30,7 +29,7 @@ - CardPreview (카드 프리뷰) - CardNumberContainer (카드 번호) - CardExpiryDateContainer (카드 유효기간) - - CardHolderNameContainer (카드 소유자 이름) + - CardholderNameContainer (카드 소유자 이름) From 8545e93c2e9dfb8fc50e977639ffdc554924d457 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 22:19:36 +0900 Subject: [PATCH 10/65] =?UTF-8?q?refactor(useInput):=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=EB=B0=8F=20=EC=97=90=EB=9F=AC=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/hooks/useInput.ts | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts index 7fa36c1555..79f9542aeb 100644 --- a/src/hooks/useInput.ts +++ b/src/hooks/useInput.ts @@ -1,23 +1,27 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; -interface UseInputProps { - validate: (value: string) => boolean; - handleError: () => void; -} +type ErrorMessage = string; -const useInput = ({ validate, handleError }: UseInputProps) => { - const [value, setValue] = useState(''); +const useInput = (initialValue = '', inquireValidity: (value: string) => ErrorMessage) => { + const [value, setValue] = useState(initialValue); + const [errorMessage, setErrorMessage] = useState(''); - const handleChange = (value: string) => { - if (!validate(value)) { - handleError(); - return; + const handleChange = (e: React.ChangeEvent) => { + setValue(() => e.target.value); + }; + + useEffect(() => { + if (inquireValidity) { + setErrorMessage(() => inquireValidity(value)); } + }, [value, inquireValidity]); - setValue(value); + return { + value, + setValue, + handleChange, + errorMessage, }; - - return [value, handleChange]; }; export default useInput; From c86cd981bde3889bc674ca285ce0cdf3cf63b997 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 22:22:55 +0900 Subject: [PATCH 11/65] =?UTF-8?q?refactor(Input):=20handleChange=EB=A5=BC?= =?UTF-8?q?=20props=EB=A1=9C=20=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/common/Input.tsx | 53 +++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index efc3c9aaab..889d0890ce 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -1,24 +1,47 @@ +import React from 'react'; +import styled from 'styled-components'; + interface InputProps { + id: string; value: string; - setValue: (value: string) => void; - validate: (value: string) => boolean; - handleError: () => void; + handleChange: (e: React.ChangeEvent) => void; + isError: boolean; + placeholder: string; width?: string; } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const Input = ({ value, setValue, validate, handleError, width = '100%' }: InputProps) => { - const handleChange = (e: React.ChangeEvent) => { - const value = e.target.value; - if (!validate(value)) { - handleError(); - return; - } +const Input = ({ id, value, handleChange, isError, placeholder, width = '100%' }: InputProps) => { + return ( + + ); +}; + +interface StyledInputProps { + width: string; + isError: boolean; +} + +const StyledInput = styled.input` + width: ${props => props.width}; + padding: 12px 10px; - setValue(value); - }; + border: 1.2px solid ${props => (props.isError ? '#ff3d3d' : '#acacac')}; + border-radius: 5px; + font-size: 16px; - return ; -}; + &::placeholder { + color: #acacac; + } + &:focus { + outline: none; + } +`; export default Input; From 6bca6fb582d578f4b5836716dcc102eee602a2a0 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Tue, 16 Apr 2024 22:24:10 +0900 Subject: [PATCH 12/65] =?UTF-8?q?feat(App):=20=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=86=8C=EC=9C=A0=EC=9E=90=20=EC=9D=B4=EB=A6=84=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/App.tsx | 40 ++++++++++++++++++++++++++++++++++++---- src/main.tsx | 14 +++++++------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ef7e3632d2..705bc1fddc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,43 @@ -import "./App.css"; +import './App.css'; +import Input from './components/common/Input'; +import styled from 'styled-components'; +import useInput from './hooks/useInput'; + +const inquireCardholderName = (cardholderName: string) => { + const isValidLength = cardholderName.length < 101; + if (!isValidLength) { + return '카드 소유자 이름은 100자 이하로 입력해주세요'; + } + + const isEnglish = /^[a-zA-Z]*$/.test(cardholderName); + if (!isEnglish) { + return '카드 소유자 이름은 영어로 입력해주세요'; + } + return ''; +}; + +const App = () => { + const { value: cardholderName, handleChange, errorMessage } = useInput('', inquireCardholderName); -function App() { return ( <> -

React Payments

+

카드 소유자 이름을 입력해주세요

+ + + {errorMessage} ); -} +}; + +const ErrorText = styled.p` + color: red; + font-size: 14px; +`; export default App; diff --git a/src/main.tsx b/src/main.tsx index 3d7150da80..5958b0bbae 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' +import ReactDOM from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; +import { StrictMode } from 'react'; ReactDOM.createRoot(document.getElementById('root')!).render( - + - , -) + , +); From 702f5ca50f1e2704a04d83492ca293f797b0123c Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 13:32:35 +0900 Subject: [PATCH 13/65] =?UTF-8?q?refactor(CardholderNameContainer):=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/App.tsx | 27 +++++++----------- src/components/CardholderNameContainer.tsx | 32 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 src/components/CardholderNameContainer.tsx diff --git a/src/App.tsx b/src/App.tsx index 705bc1fddc..cbbfbb066c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,6 @@ import './App.css'; -import Input from './components/common/Input'; -import styled from 'styled-components'; import useInput from './hooks/useInput'; +import CardholderNameContainer from './components/CardholderNameContainer'; const inquireCardholderName = (cardholderName: string) => { const isValidLength = cardholderName.length < 101; @@ -17,27 +16,21 @@ const inquireCardholderName = (cardholderName: string) => { }; const App = () => { - const { value: cardholderName, handleChange, errorMessage } = useInput('', inquireCardholderName); + const { + value: cardholderName, + handleChange: handleChangeCardholderName, + errorMessage: cardholderNameErrorMessage, + } = useInput('', inquireCardholderName); return ( <> -

카드 소유자 이름을 입력해주세요

- - - {errorMessage} ); }; -const ErrorText = styled.p` - color: red; - font-size: 14px; -`; - export default App; diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx new file mode 100644 index 0000000000..30f333dd5d --- /dev/null +++ b/src/components/CardholderNameContainer.tsx @@ -0,0 +1,32 @@ +import styled from 'styled-components'; +import Input from './common/Input'; + +interface CardholderNameContainerProps { + cardholderName: string; + handleChange: (e: React.ChangeEvent) => void; + errorMessage: string; +} + +const CardholderNameContainer = ({ cardholderName, handleChange, errorMessage }: CardholderNameContainerProps) => { + return ( + <> +

카드 소유자 이름을 입력해주세요

+ + + {errorMessage} + + ); +}; + +const ErrorText = styled.p` + color: red; + font-size: 14px; +`; + +export default CardholderNameContainer; From 7b92fce67b71a202a33e3ba5178bc8c74b29fc98 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:26:23 +0900 Subject: [PATCH 14/65] =?UTF-8?q?feat(useInput):=20input=20blur=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EB=90=A0=20=EB=95=8C=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/hooks/useInput.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts index 79f9542aeb..a75440b885 100644 --- a/src/hooks/useInput.ts +++ b/src/hooks/useInput.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; type ErrorMessage = string; @@ -10,16 +10,15 @@ const useInput = (initialValue = '', inquireValidity: (value: string) => ErrorMe setValue(() => e.target.value); }; - useEffect(() => { - if (inquireValidity) { - setErrorMessage(() => inquireValidity(value)); - } - }, [value, inquireValidity]); + const updateErrorMessage = () => { + setErrorMessage(inquireValidity(value)); + }; return { value, setValue, handleChange, + updateErrorMessage, errorMessage, }; }; From c0152c40bc20745a85e1bfa64ebf15a2161c8ac2 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:28:42 +0900 Subject: [PATCH 15/65] =?UTF-8?q?feat(CardholderNameContainer):=20onBlur?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=9F=AC=EB=A1=9CupdateErrorMessage=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardholderNameContainer.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index 30f333dd5d..477bfff332 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -4,20 +4,28 @@ import Input from './common/Input'; interface CardholderNameContainerProps { cardholderName: string; handleChange: (e: React.ChangeEvent) => void; + updateErrorMessage: () => void; errorMessage: string; } -const CardholderNameContainer = ({ cardholderName, handleChange, errorMessage }: CardholderNameContainerProps) => { +const CardholderNameContainer = ({ + cardholderName, + handleChange, + updateErrorMessage, + errorMessage, +}: CardholderNameContainerProps) => { return ( <>

카드 소유자 이름을 입력해주세요

{errorMessage} From f490168815528e5ce508da3fba643007161d7445 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:29:54 +0900 Subject: [PATCH 16/65] =?UTF-8?q?feat(CardExpiryDateContainer):=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardExpiryDateContainer.tsx | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/components/CardExpiryDateContainer.tsx diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx new file mode 100644 index 0000000000..fe5c7423e4 --- /dev/null +++ b/src/components/CardExpiryDateContainer.tsx @@ -0,0 +1,63 @@ +import styled from 'styled-components'; +import Input from './common/Input'; + +type MM = string; +type YY = string; + +interface CardExpiryDateContainerProps { + expiryDate: { month: MM; year: YY }; + changeHandler: { + month: (e: React.ChangeEvent) => void; + year: (e: React.ChangeEvent) => void; + }; + errorMessage: { + month: string; + year: string; + }; + errorMessageUpdater: { + month: () => void; + year: () => void; + }; +} + +const CardExpiryDateContainer = ({ + expiryDate, + changeHandler, + errorMessageUpdater, + errorMessage, +}: CardExpiryDateContainerProps) => { + return ( + <> +

카드 유효기간을 입력해 주세요

+

월/년도(MMYY)를 순서대로 입력해 주세요.

+ + + + {errorMessage.month} + {errorMessage.year} + + ); +}; + +const ErrorText = styled.p` + color: red; + font-size: 14px; +`; + +export default CardExpiryDateContainer; From c3f2a225c34b8b482f8383c501d41498a9d40687 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:30:34 +0900 Subject: [PATCH 17/65] =?UTF-8?q?refactor(Input):=20rest=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/common/Input.tsx | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index 889d0890ce..0368499320 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -1,30 +1,15 @@ -import React from 'react'; import styled from 'styled-components'; interface InputProps { - id: string; - value: string; - handleChange: (e: React.ChangeEvent) => void; - isError: boolean; - placeholder: string; - width?: string; + [key: string]: any; + isError?: boolean; } -const Input = ({ id, value, handleChange, isError, placeholder, width = '100%' }: InputProps) => { - return ( - - ); +const Input = ({ isError = false, ...rest }: InputProps) => { + return ; }; interface StyledInputProps { - width: string; isError: boolean; } From 06d34322d5838c972b6fd8c1bee7a8e67b04d740 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:42:12 +0900 Subject: [PATCH 18/65] =?UTF-8?q?refactor(inquiry):=20inquire=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/inquiry/index.ts | 5 +++++ src/inquiry/inquireCardholderName.ts | 14 ++++++++++++++ src/inquiry/inquireExpiryMonth.ts | 16 ++++++++++++++++ src/inquiry/inquireExpiryYear.ts | 16 ++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 src/inquiry/index.ts create mode 100644 src/inquiry/inquireCardholderName.ts create mode 100644 src/inquiry/inquireExpiryMonth.ts create mode 100644 src/inquiry/inquireExpiryYear.ts diff --git a/src/inquiry/index.ts b/src/inquiry/index.ts new file mode 100644 index 0000000000..b778e5338e --- /dev/null +++ b/src/inquiry/index.ts @@ -0,0 +1,5 @@ +import inquireCardholderName from './inquireCardholderName'; +import inquireExpiryMonth from './inquireExpiryMonth'; +import inquireExpiryYear from './inquireExpiryYear'; + +export { inquireCardholderName, inquireExpiryMonth, inquireExpiryYear }; diff --git a/src/inquiry/inquireCardholderName.ts b/src/inquiry/inquireCardholderName.ts new file mode 100644 index 0000000000..6df53fe10a --- /dev/null +++ b/src/inquiry/inquireCardholderName.ts @@ -0,0 +1,14 @@ +const inquireCardholderName = (cardholderName: string) => { + const isValidLength = cardholderName.length < 101; + if (!isValidLength) { + return '카드 소유자 이름은 100자 이하로 입력해주세요'; + } + + const isEnglish = /^[a-zA-Z]*$/.test(cardholderName); + if (!isEnglish) { + return '카드 소유자 이름은 영어로 입력해주세요'; + } + return ''; +}; + +export default inquireCardholderName; diff --git a/src/inquiry/inquireExpiryMonth.ts b/src/inquiry/inquireExpiryMonth.ts new file mode 100644 index 0000000000..0db843fee3 --- /dev/null +++ b/src/inquiry/inquireExpiryMonth.ts @@ -0,0 +1,16 @@ +const inquireExpiryMonth = (expiryMonth: string) => { + const isValidLength = expiryMonth.length == 0 || expiryMonth.length == 2; + const isValidMonth = /^(0[1-9]|1[0-2])$/.test(expiryMonth); + + if (!isValidLength) { + return '월(月) : 2자리로 입력해주세요'; + } + + if (!isValidMonth) { + return '월(月) : 01월부터 12월 중 하나로 입력해주세요'; + } + + return ''; +}; + +export default inquireExpiryMonth; diff --git a/src/inquiry/inquireExpiryYear.ts b/src/inquiry/inquireExpiryYear.ts new file mode 100644 index 0000000000..0a29ab3013 --- /dev/null +++ b/src/inquiry/inquireExpiryYear.ts @@ -0,0 +1,16 @@ +const inquireExpiryYear = (expiryYear: string) => { + const isValidLength = expiryYear.length < 3; + const isValidYear = Number(expiryYear) > 23 && Number(expiryYear) < 41; + + if (!isValidLength) { + return '년도(年) : 2자리로 입력해주세요'; + } + + if (!isValidYear) { + return '년도(年) : 24년도부터 40년도 중 하나로 입력해주세요'; + } + + return ''; +}; + +export default inquireExpiryYear; From 795451154bc719ddd37f67150f484566fa8cd4e6 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:43:06 +0900 Subject: [PATCH 19/65] =?UTF-8?q?feat(App):=20CardExpiryDateContainer=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/App.tsx | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index cbbfbb066c..8340050e32 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,34 +1,45 @@ import './App.css'; import useInput from './hooks/useInput'; import CardholderNameContainer from './components/CardholderNameContainer'; - -const inquireCardholderName = (cardholderName: string) => { - const isValidLength = cardholderName.length < 101; - if (!isValidLength) { - return '카드 소유자 이름은 100자 이하로 입력해주세요'; - } - - const isEnglish = /^[a-zA-Z]*$/.test(cardholderName); - if (!isEnglish) { - return '카드 소유자 이름은 영어로 입력해주세요'; - } - return ''; -}; +import CardExpiryDateContainer from './components/CardExpiryDateContainer'; +import { inquireCardholderName, inquireExpiryMonth, inquireExpiryYear } from './inquiry'; const App = () => { const { value: cardholderName, handleChange: handleChangeCardholderName, + updateErrorMessage: updateCardholderNameErrorMessage, errorMessage: cardholderNameErrorMessage, } = useInput('', inquireCardholderName); + const { + value: expiryMonth, + handleChange: handleChangeExpiryMonth, + updateErrorMessage: updateExpiryMonthErrorMessage, + errorMessage: expiryMonthErrorMessage, + } = useInput('', inquireExpiryMonth); + + const { + value: expiryYear, + handleChange: handleChangeExpiryYear, + updateErrorMessage: updateExpiryYearErrorMessage, + errorMessage: expiryYearErrorMessage, + } = useInput('', inquireExpiryYear); + return ( <> + ); }; From 1535fdd4b1fa82ebb30d02b532162b97f50ff335 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 15:43:23 +0900 Subject: [PATCH 20/65] =?UTF-8?q?chore(eslint):=20any=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=20warn=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- .eslintrc.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 6b8931c929..120af0b84b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -16,5 +16,6 @@ module.exports = { "warn", { allowConstantExport: true }, ], + "@typescript-eslint/no-explicit-any": "warn" }, }; From 157b17eb22cdc9b12c99e36901f71a87299bfedb Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:24:57 +0900 Subject: [PATCH 21/65] =?UTF-8?q?refactor(inquire):=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EA=B8=B0=EA=B0=84=20=EC=9B=94=EA=B3=BC=20=EB=85=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/inquiry/inquireExpiryMonth.ts | 2 +- src/inquiry/inquireExpiryYear.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/inquiry/inquireExpiryMonth.ts b/src/inquiry/inquireExpiryMonth.ts index 0db843fee3..a8a82f2773 100644 --- a/src/inquiry/inquireExpiryMonth.ts +++ b/src/inquiry/inquireExpiryMonth.ts @@ -1,5 +1,5 @@ const inquireExpiryMonth = (expiryMonth: string) => { - const isValidLength = expiryMonth.length == 0 || expiryMonth.length == 2; + const isValidLength = expiryMonth.length === 0 || expiryMonth.length === 2; const isValidMonth = /^(0[1-9]|1[0-2])$/.test(expiryMonth); if (!isValidLength) { diff --git a/src/inquiry/inquireExpiryYear.ts b/src/inquiry/inquireExpiryYear.ts index 0a29ab3013..5f4de512fb 100644 --- a/src/inquiry/inquireExpiryYear.ts +++ b/src/inquiry/inquireExpiryYear.ts @@ -1,5 +1,5 @@ const inquireExpiryYear = (expiryYear: string) => { - const isValidLength = expiryYear.length < 3; + const isValidLength = expiryYear.length === 0 || expiryYear.length === 2; const isValidYear = Number(expiryYear) > 23 && Number(expiryYear) < 41; if (!isValidLength) { From cc9658f08af78a7206cea210409cefa13a6096fb Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:25:31 +0900 Subject: [PATCH 22/65] =?UTF-8?q?feat(inquireCardNumber):=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=B2=88=ED=98=B8=20inquire=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/inquiry/inquireCardNumber.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/inquiry/inquireCardNumber.ts diff --git a/src/inquiry/inquireCardNumber.ts b/src/inquiry/inquireCardNumber.ts new file mode 100644 index 0000000000..9613367d90 --- /dev/null +++ b/src/inquiry/inquireCardNumber.ts @@ -0,0 +1,16 @@ +const inquireCardNumber = (cardNumber: string) => { + const isValidLength = cardNumber.length === 0 || cardNumber.length === 4; + const isValidCardNumber = /^0{1,4}|[1-9]\d{0,3}$/.test(cardNumber); + + if (!isValidLength) { + return '카드 번호는 4자리로 입력해주세요'; + } + + if (!isValidCardNumber) { + return '카드 번호는 0000 ~ 9999 사이의 숫자로 입력해주세요'; + } + + return ''; +}; + +export default inquireCardNumber; From 89ca4c43700c3c6abe1590c7fff2a7a042fe3fb6 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:26:14 +0900 Subject: [PATCH 23/65] =?UTF-8?q?chore(inquireCardHolderName):=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=9E=85=EB=A0=A5=EA=B0=92=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung Co-authored-by: Parkhanyoung --- src/inquiry/index.ts | 3 ++- src/inquiry/inquireCardholderName.ts | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/inquiry/index.ts b/src/inquiry/index.ts index b778e5338e..16e3d25881 100644 --- a/src/inquiry/index.ts +++ b/src/inquiry/index.ts @@ -1,5 +1,6 @@ import inquireCardholderName from './inquireCardholderName'; import inquireExpiryMonth from './inquireExpiryMonth'; import inquireExpiryYear from './inquireExpiryYear'; +import inquireCardNumber from './inquireCardNumber'; -export { inquireCardholderName, inquireExpiryMonth, inquireExpiryYear }; +export { inquireCardholderName, inquireExpiryMonth, inquireExpiryYear, inquireCardNumber }; diff --git a/src/inquiry/inquireCardholderName.ts b/src/inquiry/inquireCardholderName.ts index 6df53fe10a..cbad94564b 100644 --- a/src/inquiry/inquireCardholderName.ts +++ b/src/inquiry/inquireCardholderName.ts @@ -1,9 +1,4 @@ const inquireCardholderName = (cardholderName: string) => { - const isValidLength = cardholderName.length < 101; - if (!isValidLength) { - return '카드 소유자 이름은 100자 이하로 입력해주세요'; - } - const isEnglish = /^[a-zA-Z]*$/.test(cardholderName); if (!isEnglish) { return '카드 소유자 이름은 영어로 입력해주세요'; From 219b8d07100a3f51bc524e9d9d4300c27c590199 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:27:52 +0900 Subject: [PATCH 24/65] =?UTF-8?q?chore(useInput):=20inquireValidity=20opti?= =?UTF-8?q?onal=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/hooks/useInput.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts index a75440b885..fc29cb1b6c 100644 --- a/src/hooks/useInput.ts +++ b/src/hooks/useInput.ts @@ -2,7 +2,7 @@ import { useState } from 'react'; type ErrorMessage = string; -const useInput = (initialValue = '', inquireValidity: (value: string) => ErrorMessage) => { +const useInput = (initialValue = '', inquireValidity?: (value: string) => ErrorMessage) => { const [value, setValue] = useState(initialValue); const [errorMessage, setErrorMessage] = useState(''); @@ -11,7 +11,9 @@ const useInput = (initialValue = '', inquireValidity: (value: string) => ErrorMe }; const updateErrorMessage = () => { - setErrorMessage(inquireValidity(value)); + if (inquireValidity) { + setErrorMessage(inquireValidity(value)); + } }; return { From a1fe1fc6267ed3f9f6cf2130913650e25452e771 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:28:37 +0900 Subject: [PATCH 25/65] =?UTF-8?q?feat(useInputs):=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/hooks/useInputs.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/hooks/useInputs.ts diff --git a/src/hooks/useInputs.ts b/src/hooks/useInputs.ts new file mode 100644 index 0000000000..81f956b4ca --- /dev/null +++ b/src/hooks/useInputs.ts @@ -0,0 +1,40 @@ +import { useState } from 'react'; + +type ErrorMessage = string; + +const useInputs = >( + initialValue: T, + inquireValidity: (value: string) => ErrorMessage, +) => { + const [value, setValue] = useState(initialValue); + const [errorMessage, setErrorMessage] = useState({ + first: '', + second: '', + third: '', + fourth: '', + }); + + const generateChangeHandler = (targetKey: string) => (e: React.ChangeEvent) => { + setValue({ + ...value, + [targetKey]: e.target.value, + }); + }; + + const generateErrorMessageUpdater = (key: string) => () => { + setErrorMessage({ + ...errorMessage, + [key]: inquireValidity(value[key]), + }); + }; + + return { + value, + setValue, + generateChangeHandler, + generateErrorMessageUpdater, + errorMessage, + }; +}; + +export default useInputs; From 2c13b28468651f0170ee8ab10eb477ab6d107870 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:29:08 +0900 Subject: [PATCH 26/65] =?UTF-8?q?feat(CardNumbersContainer):=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardNumbersContainer.tsx | 56 +++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/components/CardNumbersContainer.tsx diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersContainer.tsx new file mode 100644 index 0000000000..e673892b5c --- /dev/null +++ b/src/components/CardNumbersContainer.tsx @@ -0,0 +1,56 @@ +import Input from './common/Input'; +import { ErrorText } from '../styles/common'; + +export interface CardNumbersContainerProps { + cardNumbers: { + first: string; + second: string; + third: string; + fourth: string; + }; + generateChangeHandler: (targetKey: string) => (e: React.ChangeEvent) => void; + errorMessage: { + first: string; + second: string; + third: string; + fourth: string; + }; + generateErrorMessageUpdater: (targetKey: string) => () => void; +} + +export default function CardNumberContainer({ + cardNumbers, + generateChangeHandler, + errorMessage, + generateErrorMessageUpdater, +}: CardNumbersContainerProps) { + const arr = ['first', 'second', 'third', 'fourth'] as const; + return ( + <> +

결제할 카드 번호를 입력해 주세요

+

본인 명의의 카드만 결제 가능합니다.

+ + {arr.map(key => { + const PASSWORD_INPUT_KEYS = ['third', 'fourth']; + const type = PASSWORD_INPUT_KEYS.includes(key) ? 'password' : 'text'; + + return ( + + ); + })} + {arr.map(key => ( + {errorMessage[key]} + ))} + + ); +} From 6c5efbec56cce218e1d64fdca5453448da854bf0 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:29:35 +0900 Subject: [PATCH 27/65] =?UTF-8?q?refactor(common):=20ErrorText=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardExpiryDateContainer.tsx | 7 +------ src/components/CardholderNameContainer.tsx | 8 ++------ src/styles/common.ts | 6 ++++++ 3 files changed, 9 insertions(+), 12 deletions(-) create mode 100644 src/styles/common.ts diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index fe5c7423e4..feb0df06c6 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -1,5 +1,5 @@ -import styled from 'styled-components'; import Input from './common/Input'; +import { ErrorText } from '../styles/common'; type MM = string; type YY = string; @@ -55,9 +55,4 @@ const CardExpiryDateContainer = ({ ); }; -const ErrorText = styled.p` - color: red; - font-size: 14px; -`; - export default CardExpiryDateContainer; diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index 477bfff332..c792723959 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -1,5 +1,5 @@ -import styled from 'styled-components'; import Input from './common/Input'; +import { ErrorText } from '../styles/common'; interface CardholderNameContainerProps { cardholderName: string; @@ -26,15 +26,11 @@ const CardholderNameContainer = ({ onBlur={updateErrorMessage} placeholder="카드 소유자 이름을 입력해주세요" width="100%" + maxLength={100} /> {errorMessage} ); }; -const ErrorText = styled.p` - color: red; - font-size: 14px; -`; - export default CardholderNameContainer; diff --git a/src/styles/common.ts b/src/styles/common.ts new file mode 100644 index 0000000000..fc4877d6ed --- /dev/null +++ b/src/styles/common.ts @@ -0,0 +1,6 @@ +import styled from 'styled-components'; + +export const ErrorText = styled.p` + color: red; + font-size: 14px; +`; From 8c8ca0e09b35029dcb5b2226765683094a61cc93 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:30:11 +0900 Subject: [PATCH 28/65] =?UTF-8?q?feat(App):=20CardNumberContainer=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/App.tsx | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8340050e32..130474dbb8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,27 @@ import './App.css'; import useInput from './hooks/useInput'; +import useInputs from './hooks/useInputs'; import CardholderNameContainer from './components/CardholderNameContainer'; import CardExpiryDateContainer from './components/CardExpiryDateContainer'; -import { inquireCardholderName, inquireExpiryMonth, inquireExpiryYear } from './inquiry'; +import { inquireCardNumber, inquireCardholderName, inquireExpiryMonth, inquireExpiryYear } from './inquiry'; +import CardNumberContainer from './components/CardNumbersContainer'; const App = () => { + const { + value: cardNumbers, + generateChangeHandler: generateCardNumbersChangeHandler, + generateErrorMessageUpdater: generateCardNumberErrorMessageUpdater, + errorMessage: cardNumbersErrorMessage, + } = useInputs( + { + first: '', + second: '', + third: '', + fourth: '', + }, + inquireCardNumber, + ); + const { value: cardholderName, handleChange: handleChangeCardholderName, @@ -28,11 +45,11 @@ const App = () => { return ( <> - { errorMessageUpdater={{ month: updateExpiryMonthErrorMessage, year: updateExpiryYearErrorMessage }} errorMessage={{ month: expiryMonthErrorMessage, year: expiryYearErrorMessage }} /> + ); }; From cb5e35eb5a7ad6f5ae63c521567ae74f24e99e79 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Wed, 17 Apr 2024 19:30:48 +0900 Subject: [PATCH 29/65] =?UTF-8?q?docs(REQUIREMENTS):=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- docs/REQUIREMENTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index a7cd049029..f87c813c31 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -4,15 +4,15 @@ 2. 개발자 관점에서 컴포넌트 기준으로 분류 # 기능 요구 사항 분석 -- [ ] 카드 번호를 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - - [ ] 4자리 숫자만 입력할 수 있다. - - [ ] 카드 번호 뒤의 8자리는 `*` 로 표시한다. +- [x] 카드 번호를 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) + - [x] 4자리 숫자만 입력할 수 있다. + - [x] 카드 번호 뒤의 8자리는 `*` 로 표시한다. - [ ] 입력한 카드 번호에 맞는 브랜드 로고를 UI에 표시한다. (해당하는 경우) - [ ] Visa: 4로 시작하는 16자리 숫자 - [ ] MasterCard: 51~55로 시작하는 16자리 숫자 -- [ ] 카드 유효기간을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - - [ ] 월(1월~12월)과 년도(2024년~2040년)를 범위 내에서만 입력할 수 있다. - - [ ] 숫자만 입력할 수 있다. +- [x] 카드 유효기간을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) + - [x] 월(1월~12월)과 년도(2024년~2040년)를 범위 내에서만 입력할 수 있다. + - [x] 숫자만 입력할 수 있다. - [x] 카드 소유자 이름을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - [x] 영문자 대문자만 입력할 수 있다. - [x] 이름은 100자 내로 입력할 수 있다. From 864cf0bdf8382fb2fbd895beb9b5bb83dd0adb3f Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:28:37 +0900 Subject: [PATCH 30/65] =?UTF-8?q?design(GlobalStyles):=20reset=20css=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- package-lock.json | 20 +++++++++++++++++++- package.json | 5 +++-- src/main.tsx | 7 ++++++- src/styles/GlobalStyles.tsx | 32 ++++++++++++++++++++++++++++++++ src/styles/common.ts | 15 ++++++++++++++- 5 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/styles/GlobalStyles.tsx diff --git a/package-lock.json b/package-lock.json index 274a70ca69..e1dfe928b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "styled-components": "^6.1.8" + "styled-components": "^6.1.8", + "styled-reset": "^4.5.2" }, "devDependencies": { "@chromatic-com/storybook": "^1.3.3", @@ -11483,6 +11484,17 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/styled-reset": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.2.tgz", + "integrity": "sha512-dbAaaVEhweBs2FGfqGBdW6oMcMK8238C2X5KCxBhUQJX92m/QyUfzRADOXhdXiXNkIPELtMCd72YY9eCdORfIw==", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0" + } + }, "node_modules/stylis": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", @@ -20631,6 +20643,12 @@ } } }, + "styled-reset": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.2.tgz", + "integrity": "sha512-dbAaaVEhweBs2FGfqGBdW6oMcMK8238C2X5KCxBhUQJX92m/QyUfzRADOXhdXiXNkIPELtMCd72YY9eCdORfIw==", + "requires": {} + }, "stylis": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", diff --git a/package.json b/package.json index c1a136f945..4cb93c7c21 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", @@ -14,7 +14,8 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "styled-components": "^6.1.8" + "styled-components": "^6.1.8", + "styled-reset": "^4.5.2" }, "devDependencies": { "@chromatic-com/storybook": "^1.3.3", diff --git a/src/main.tsx b/src/main.tsx index 5958b0bbae..6f60c26119 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,9 +2,14 @@ import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './index.css'; import { StrictMode } from 'react'; +import GlobalStyles from './styles/GlobalStyles.tsx'; +import { GlobalLayout } from './styles/common.ts'; ReactDOM.createRoot(document.getElementById('root')!).render( - + + + + , ); diff --git a/src/styles/GlobalStyles.tsx b/src/styles/GlobalStyles.tsx new file mode 100644 index 0000000000..0141ea945f --- /dev/null +++ b/src/styles/GlobalStyles.tsx @@ -0,0 +1,32 @@ +import { createGlobalStyle } from 'styled-components'; +import reset from 'styled-reset'; + +const GlobalStyles = createGlobalStyle` + ${reset} + + a{ + text-decoration: none; + color: inherit; + } + *{ + box-sizing: border-box; + } + input, textarea { + -moz-user-select: auto; + -webkit-user-select: auto; + -ms-user-select: auto; + user-select: auto; + } + input:focus { + outline: none; + } + + button { + border: none; + background: none; + padding: 0; + cursor: pointer; + } +`; + +export default GlobalStyles; diff --git a/src/styles/common.ts b/src/styles/common.ts index fc4877d6ed..495a2ca145 100644 --- a/src/styles/common.ts +++ b/src/styles/common.ts @@ -1,6 +1,19 @@ import styled from 'styled-components'; +const SCREEN_WIDTH = 480; + +export const ErrorWrapper = styled.div` + height: 44px; +`; + export const ErrorText = styled.p` color: red; - font-size: 14px; + font-size: 12px; + margin-top: 8px; +`; + +export const GlobalLayout = styled.div` + width: ${SCREEN_WIDTH}px; + height: 100vh; + margin: 0 auto; `; From 16380c006f99f5d2829e1ffab6f20ab28afe91c3 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:30:15 +0900 Subject: [PATCH 31/65] =?UTF-8?q?design(Input):=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/common/Input.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index 0368499320..ae7b94692e 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -15,15 +15,16 @@ interface StyledInputProps { const StyledInput = styled.input` width: ${props => props.width}; - padding: 12px 10px; + padding: 10px 7px; border: 1.2px solid ${props => (props.isError ? '#ff3d3d' : '#acacac')}; border-radius: 5px; - font-size: 16px; + font-size: 15px; &::placeholder { color: #acacac; } + &:focus { outline: none; } From 8ae34f371fa9bb7fc0a3cc93a1afd3a7e315fb0c Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:32:11 +0900 Subject: [PATCH 32/65] =?UTF-8?q?feat(useInputs):=20errorStatus=EC=99=80?= =?UTF-8?q?=20errorMessage=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/hooks/useInputs.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/hooks/useInputs.ts b/src/hooks/useInputs.ts index 81f956b4ca..70daac8dab 100644 --- a/src/hooks/useInputs.ts +++ b/src/hooks/useInputs.ts @@ -2,17 +2,24 @@ import { useState } from 'react'; type ErrorMessage = string; +const convertArrayIntoObject = (keys: string[]) => { + const obj: Record = {}; + + keys.forEach(key => { + obj[key] = false; + }); + + return obj; +}; + const useInputs = >( initialValue: T, inquireValidity: (value: string) => ErrorMessage, ) => { const [value, setValue] = useState(initialValue); - const [errorMessage, setErrorMessage] = useState({ - first: '', - second: '', - third: '', - fourth: '', - }); + const initialErrorStatus = convertArrayIntoObject(Object.keys(initialValue)); + const [errorStatus, setErrorStatus] = useState(initialErrorStatus); + const [errorMessage, setErrorMessage] = useState(''); const generateChangeHandler = (targetKey: string) => (e: React.ChangeEvent) => { setValue({ @@ -22,10 +29,14 @@ const useInputs = >( }; const generateErrorMessageUpdater = (key: string) => () => { - setErrorMessage({ - ...errorMessage, - [key]: inquireValidity(value[key]), + const foundErrorMessage = inquireValidity(value[key]); + + setErrorStatus({ + ...errorStatus, + [key]: Boolean(foundErrorMessage), }); + + setErrorMessage(foundErrorMessage); }; return { @@ -33,6 +44,7 @@ const useInputs = >( setValue, generateChangeHandler, generateErrorMessageUpdater, + errorStatus, errorMessage, }; }; From aee4de72ae13058d92abdc2be01659bf4054a4fc Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:35:32 +0900 Subject: [PATCH 33/65] =?UTF-8?q?feat:=20RegistrationLayout=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardExpiryDateContainer.tsx | 61 +++++++++------- src/components/CardNumbersContainer.tsx | 76 ++++++++++---------- src/components/CardholderNameContainer.tsx | 41 ++++++----- src/components/common/RegistrationLayout.tsx | 59 +++++++++++++++ 4 files changed, 155 insertions(+), 82 deletions(-) create mode 100644 src/components/common/RegistrationLayout.tsx diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index feb0df06c6..a2b7993e47 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -1,5 +1,6 @@ import Input from './common/Input'; -import { ErrorText } from '../styles/common'; +import { ErrorWrapper, ErrorText } from '../styles/common'; +import RegistrationLayout from './common/RegistrationLayout'; type MM = string; type YY = string; @@ -27,31 +28,39 @@ const CardExpiryDateContainer = ({ errorMessage, }: CardExpiryDateContainerProps) => { return ( - <> -

카드 유효기간을 입력해 주세요

-

월/년도(MMYY)를 순서대로 입력해 주세요.

- - - - {errorMessage.month} - {errorMessage.year} - +
+ + + + + + {errorMessage.month} + {errorMessage.year} + +
); }; diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersContainer.tsx index e673892b5c..649cf3a1b2 100644 --- a/src/components/CardNumbersContainer.tsx +++ b/src/components/CardNumbersContainer.tsx @@ -1,20 +1,14 @@ import Input from './common/Input'; -import { ErrorText } from '../styles/common'; +import { ErrorWrapper, ErrorText } from '../styles/common'; +import RegistrationLayout from './common/RegistrationLayout'; + +type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; export interface CardNumbersContainerProps { - cardNumbers: { - first: string; - second: string; - third: string; - fourth: string; - }; + cardNumbers: Record; generateChangeHandler: (targetKey: string) => (e: React.ChangeEvent) => void; - errorMessage: { - first: string; - second: string; - third: string; - fourth: string; - }; + errorMessage: string; + errorStatus: Record; generateErrorMessageUpdater: (targetKey: string) => () => void; } @@ -22,35 +16,41 @@ export default function CardNumberContainer({ cardNumbers, generateChangeHandler, errorMessage, + errorStatus, generateErrorMessageUpdater, }: CardNumbersContainerProps) { const arr = ['first', 'second', 'third', 'fourth'] as const; return ( - <> -

결제할 카드 번호를 입력해 주세요

-

본인 명의의 카드만 결제 가능합니다.

- - {arr.map(key => { - const PASSWORD_INPUT_KEYS = ['third', 'fourth']; - const type = PASSWORD_INPUT_KEYS.includes(key) ? 'password' : 'text'; +
+ + {arr.map(key => { + const PASSWORD_INPUT_KEYS = ['third', 'fourth']; + const type = PASSWORD_INPUT_KEYS.includes(key) ? 'password' : 'text'; - return ( - - ); - })} - {arr.map(key => ( - {errorMessage[key]} - ))} - + return ( + + ); + })} + + + {errorMessage} + +
); } diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index c792723959..3fc7841824 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -1,35 +1,40 @@ import Input from './common/Input'; -import { ErrorText } from '../styles/common'; +import { ErrorWrapper, ErrorText } from '../styles/common'; +import RegistrationLayout from './common/RegistrationLayout'; interface CardholderNameContainerProps { cardholderName: string; - handleChange: (e: React.ChangeEvent) => void; + setCardholderName: React.Dispatch>; updateErrorMessage: () => void; errorMessage: string; } const CardholderNameContainer = ({ cardholderName, - handleChange, + setCardholderName, updateErrorMessage, errorMessage, }: CardholderNameContainerProps) => { + const handleChange = (e: React.ChangeEvent) => setCardholderName(e.target.value.toUpperCase()); + return ( - <> -

카드 소유자 이름을 입력해주세요

- - - {errorMessage} - +
+ + + + + {errorMessage} + +
); }; diff --git a/src/components/common/RegistrationLayout.tsx b/src/components/common/RegistrationLayout.tsx new file mode 100644 index 0000000000..63fb7067af --- /dev/null +++ b/src/components/common/RegistrationLayout.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import styled from 'styled-components'; + +export interface RegistrationLayoutProps { + title: string; + subtitle?: string; + labelText: string; + labelFor: string; + children: React.ReactNode; +} + +export default function RegistrationLayout({ + title, + subtitle, + labelText, + labelFor, + children, +}: RegistrationLayoutProps) { + return ( + + {title} + {subtitle} + + {children} + + ); +} + +const Container = styled.div` + margin-top: 10px; +`; + +const Title = styled.h1` + font-size: 24px; + font-weight: 700; + margin: 0; +`; + +export const Subtitle = styled.h2` + font-size: 13px; + font-weight: 300; + margin: 10px 0 12px 0; + color: #8b95a1; +`; + +export const Label = styled.label` + font-size: 15.5px; + font-weight: 400; + display: block; + margin-top: 24px; + color: #0a0d13; +`; + +export const InputWrapper = styled.div` + display: flex; + width: 440px; + justify-content: space-between; + margin-top: 8px; +`; From 38a05bd06c0c1d24e89b5f9c0f8d6712573766bd Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:36:31 +0900 Subject: [PATCH 34/65] =?UTF-8?q?feat:=20CardPreview=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/App.tsx | 23 +++++- src/assets/images/mastercard.png | Bin 0 -> 1526 bytes src/assets/images/visa.png | Bin 0 -> 1723 bytes src/components/CardPreview.tsx | 137 +++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 src/assets/images/mastercard.png create mode 100644 src/assets/images/visa.png create mode 100644 src/components/CardPreview.tsx diff --git a/src/App.tsx b/src/App.tsx index 130474dbb8..3a50441e00 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,8 @@ import CardholderNameContainer from './components/CardholderNameContainer'; import CardExpiryDateContainer from './components/CardExpiryDateContainer'; import { inquireCardNumber, inquireCardholderName, inquireExpiryMonth, inquireExpiryYear } from './inquiry'; import CardNumberContainer from './components/CardNumbersContainer'; +import CardPreview from './components/CardPreview'; +import styled from 'styled-components'; const App = () => { const { @@ -12,6 +14,7 @@ const App = () => { generateChangeHandler: generateCardNumbersChangeHandler, generateErrorMessageUpdater: generateCardNumberErrorMessageUpdater, errorMessage: cardNumbersErrorMessage, + errorStatus: cardNumbersErrorStatus, } = useInputs( { first: '', @@ -24,7 +27,7 @@ const App = () => { const { value: cardholderName, - handleChange: handleChangeCardholderName, + setValue: setCardholderName, updateErrorMessage: updateCardholderNameErrorMessage, errorMessage: cardholderNameErrorMessage, } = useInput('', inquireCardholderName); @@ -44,12 +47,18 @@ const App = () => { } = useInput('', inquireExpiryYear); return ( - <> + + { /> - + ); }; +const AppLayout = styled.div` + display: flex; + flex-direction: column; + align-items: center; +`; + export default App; diff --git a/src/assets/images/mastercard.png b/src/assets/images/mastercard.png new file mode 100644 index 0000000000000000000000000000000000000000..0998910971f4ceb7c799bfe2702a0c602893dac8 GIT binary patch literal 1526 zcmVEG^w0jJh28aPg?_wwGdpj* z8XRDGc{x@tm&c@(qj;DEpQBh1Ld>G8bRv<+xhFMuXK`_n1WrXFkyvYMD}+KJ2nK`T zQ%R{*0+a}AYism7ho^4z_xI1zXO7H0kuzOgUC`d%4nFm^992&deEatVRQCHdW2eXI2?|dra3M&O&jq=su7Jw>DP#`Z95qV z1i;5T)LA1*K|is*gO7LIQI7y0Hhh_k_%azewVp@jR`&Nryc;;Mi@E)MEgFZD=0s%L zg#uU$Kf!MLE|fA4D@3~J$v07I9E{OnFh;I|7Kqt6M z#raXtE{uR?9Dh$$G<=k_dwYAK8N(xzxO4js>`Z>)>^FjudLJSapB{=@k|?vcLH;u7 z>^FkAlmdNVf<0>Kc^xN-nkUno{T8-v+^iaf^+M6vGX4>SQ7R!}dF!HqnxhxLNzhS`mBw9I)by2%g*m4p?MTC$lwv6F3o3ipuh#^ZM`j*#@v;;mAPZ z@JbaHnOnC$2X4Xx){&p-Szw1o392Z|q^LBf?*b7qwx>l?K zJND{*@QRwu6<(2q-dg{)G*se7;!w#e%8|*9-ch@WyHH2OPL}dM8S;bJrUw63lw{VL4fld9HXySF_)(ZRR+1SQ7yxd{ z8}-YyqreW8I=rGJlj=6ySkF&eruP6*ZsviZcYqVy#OQoh`_Hm*l2~%Dex%~txgqe1 z^5bO7*vG)lc2=)>jopMoqc}SRDk#e|us*}xtCvxcn=hPUDsa`7BfGmd;>YJ2wd8@_1906*hepTs7Qvnq{gT zVabDzNthNp1EL!v4@EW_$?iH$pp_ZLm6wydD2$|O(QcGD)bxd~ry}#eiYwD!)6R%5 zlM!DgBfd;VNM^w_P4Mv!bwC7tt9M~{cNcuTL;lcXhQ8JNpjd3eI+Onq^aXy)veLpZ zjLGfo?cCn$5quhiyzRZKBO`ixdI}h#*YIS)7up#4e4b=F{^%_-`!lagOG`;?@-y^` ziMF;jdQ}4W97am%vK~#7F?VL|Y zTt^(of4hlp{gYJ@o1kdagMZKj4^hb46k1JDC}@h{rJ)I>g`@}DV*eaU4`~mDhSKzq zKuU?BDbx}g0|i^CNlM!dXb+OQtZNS%rDFVpt`&^`JKv{c=FOWIch@<1m=BnB-si~=% zX=!QX^Z6(#DTxerR#sMsiAdAa(|8?Whu$eIE$zZNOUx}HZ{_6VP*zqJ8De8*X2$wq zoPwa9a5!AZO!jVec6K5AAwzu3&d$=*)YM#Za&i&V(R+ruvk@{u;K?Z|DVfX5%Wr!< z9#4}Ia(ASsr{kr`yRorRwSWJ9G9(U?HKWRlf0@6NA#r%s<0XSOjF=iRVrs;QsSzWl zMvRymF=A@Oh^Y}HrbdjI8ZlyO#E7Ypn750@#^)&5JNDEz8Gau%yjT*~HS+WP6sSI| zJQt|Rr~KSZ+p+oo9@F<-4=5CQM4`b+iq1b~{XWCmWl%-gLDqjMRhB)g>>r=)?)k@Y zytVlV=^$n?GY+sD&dZ>m?+j7*od}Kn8&$Tu@WDxX^K`ZH+{Md(P$v^YH0Y%2fqckx1h~3u^b>4nJeZRb=?EjUs-&yTsFbQrG z5DziCxu1y{s4ie))mt}f4U~y1^Yx8h*J}j(9@^gH^R3NCmFGZZ>E-}oQS~j~SYpaT zIzMyJrSDOtkZye^Lc%cz9Wh)x7}3V-ZY-IC&u^Cf;*TNR(ZdMwNm)a%8tb;*o(xbAo@3I?h*=9U?a{ zF_@rOk-g%R_fI<73}|rmU5g2;;E94G26PM>SylvbRBe_YjcO&Y?Uf9vW4C6b0!}-26M1f#exB=CY7i^Ft_J| zsLCNR>8UdRpNe~l2W@}Kah%;;ch8eV(|tFhu*w35d&xCkg`zr;fvW~;4pW`v+DJ^% zg(d6rh}ANf+?|T|#ya=Cg`LnQW+Pk8eEiw3+wWCtchNGeHq3hDb_Ka=H)A{%*^aAb8Wtp% zN#c$V+Mx+YQ%`C5r{dryu3_? z#KCeB;9I@@OG`^+NF3m%oKaO!)nNJSu?H0{W#~58;8T;GyC@KdyNTONCqWLz!4_uZJ*EA0ng^g{|DBR&XnH` R#G3#B002ovPDHLkV1j6RJz4+& literal 0 HcmV?d00001 diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx new file mode 100644 index 0000000000..e37e71b309 --- /dev/null +++ b/src/components/CardPreview.tsx @@ -0,0 +1,137 @@ +import styled from 'styled-components'; +import MasterCard from '../../src/assets/images/mastercard.png'; +import VisaCard from './src/assets/images/visa.png'; + +type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; + +interface CardPreviewProps { + cardNumbers: Record; + expiryDate: { month: string; year: string }; + cardholderName: string; +} + +const CardPreview = ({ cardNumbers, expiryDate, cardholderName }: CardPreviewProps) => { + const isVisa = cardNumbers.first[0] === '4'; + const firstTwoDigits = Number(cardNumbers.first.slice(0, 2)); + const isMaster = firstTwoDigits > 50 && firstTwoDigits < 56; + + return ( + + + + + {isVisa && } + {isMaster && } + + + + + {cardNumbers.first} + {cardNumbers.second} + {'·'.repeat(cardNumbers.third.length)} + {'·'.repeat(cardNumbers.fourth.length)} + + + +

{expiryDate.month}

+

{expiryDate.month && '/'}

+

{expiryDate.year}

+
+ + + {cardholderName} + +
+
+ ); +}; + +const CardPreviewLayout = styled.div` + display: flex; + flex-direction: column; + width: 320px; + height: 200px; + margin: 60px 0 50px 0; + + background-color: #333333; + padding: 16px 20px 20px 20px; + border-radius: 8px; + + color: white; + box-shadow: 3px 3px 5px 0px #00000040; +`; + +const HeaderWrapper = styled.div` + display: flex; + justify-content: space-between; + height: 35px; +`; + +const CardMagnetic = styled.div` + background-color: #ddcd78; + width: 50px; + border-radius: 5px; +`; + +const BrandImageWrapper = styled.div` + width: 50px; + border-radius: 5px; +`; + +const StyledImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +`; + +const BodyWrapper = styled.section` + display: flex; + flex-direction: column; + gap: 20px; + padding-left: 10px; + letter-spacing: 2; +`; + +const CardNumbersWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 20px; + margin-top: 20px; + gap: 20px; +`; + +const CardNumberText = styled.p` + display: flex; + flex-basis: 25%; + height: 20px; + font-size: 20px; +`; + +const CardSecretNumber = styled.p` + display: flex; + flex-basis: 25%; + font-size: 28px; +`; + +const ExpiryDateWrapper = styled.div` + display: flex; + align-items: center; + font-size: 20px; + height: 20px; +`; + +const CardholderNameWrapper = styled.div` + display: flex; + align-items: center; + flex-basis: 20%; + font-size: 20px; +`; + +const CardholderNameText = styled.p` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export default CardPreview; From 2aff5901334acc65d4e8fc29bfa9b5854136a65b Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:37:18 +0900 Subject: [PATCH 35/65] =?UTF-8?q?feat(inquireCardholderName):=20=EA=B3=B5?= =?UTF-8?q?=EB=B0=B1=20=EA=B4=80=EB=A0=A8=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/inquiry/inquireCardholderName.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/inquiry/inquireCardholderName.ts b/src/inquiry/inquireCardholderName.ts index cbad94564b..595e424312 100644 --- a/src/inquiry/inquireCardholderName.ts +++ b/src/inquiry/inquireCardholderName.ts @@ -1,8 +1,14 @@ const inquireCardholderName = (cardholderName: string) => { - const isEnglish = /^[a-zA-Z]*$/.test(cardholderName); + const isEnglish = /^[a-zA-Z ]+$/.test(cardholderName); if (!isEnglish) { - return '카드 소유자 이름은 영어로 입력해주세요'; + return '카드 소유자 이름을 영어로만 입력해주세요'; } + + const containSideBlankOrMultipleBlanks = !/^(?:\S(?:\s\S*)?)?\S$/.test(cardholderName); + if (containSideBlankOrMultipleBlanks) { + return '양 끝에 공백이 포함되면 안 되며, 사이 공백의 길이는 최대 1입니다.'; + } + return ''; }; From 2569f7a495a7d8a7711962deb5911f9cdd7bca42 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:39:03 +0900 Subject: [PATCH 36/65] =?UTF-8?q?docs(REQUIREMENTS):=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- docs/REQUIREMENTS.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index f87c813c31..3f1820f719 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -7,25 +7,24 @@ - [x] 카드 번호를 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - [x] 4자리 숫자만 입력할 수 있다. - [x] 카드 번호 뒤의 8자리는 `*` 로 표시한다. -- [ ] 입력한 카드 번호에 맞는 브랜드 로고를 UI에 표시한다. (해당하는 경우) - - [ ] Visa: 4로 시작하는 16자리 숫자 - - [ ] MasterCard: 51~55로 시작하는 16자리 숫자 +- [x] 입력한 카드 번호에 맞는 브랜드 로고를 UI에 표시한다. (해당하는 경우) + - [x] Visa: 4로 시작하는 16자리 숫자 + - [x] MasterCard: 51~55로 시작하는 16자리 숫자 - [x] 카드 유효기간을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - [x] 월(1월~12월)과 년도(2024년~2040년)를 범위 내에서만 입력할 수 있다. - [x] 숫자만 입력할 수 있다. - [x] 카드 소유자 이름을 입력한다. (입력이 유효하지 않은 경우 에러 메세지 출력) - [x] 영문자 대문자만 입력할 수 있다. - [x] 이름은 100자 내로 입력할 수 있다. - - [ ] 소문자로 입력해도 대문자로 자동 변환된다. (optional) -- [ ] 실시간으로 입력하는 카드 정보를 보여준다. + - [x] 소문자로 입력해도 대문자로 자동 변환된다. (optional) +- [x] 실시간으로 입력하는 카드 정보를 보여준다. # 컴포넌트 설계 - 공통 컴포넌트 - Input - - Container + - RegistrationLayout - 컴포넌트 - - CardRegistration (카드 번호, 유효기간, 소유자 이름을 1개의 input state로 관리) - CardPreview (카드 프리뷰) - CardNumberContainer (카드 번호) - CardExpiryDateContainer (카드 유효기간) From e8d68089d61e106d8105e057417128290f5b2fa8 Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 13:39:55 +0900 Subject: [PATCH 37/65] =?UTF-8?q?fix(CardPreview):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index e37e71b309..edae33cd02 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; import MasterCard from '../../src/assets/images/mastercard.png'; -import VisaCard from './src/assets/images/visa.png'; +import VisaCard from '../../src/assets/images/visa.png'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; From fcf4acdaff414d726f556e44081e52b20400f0fc Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 15:16:35 +0900 Subject: [PATCH 38/65] =?UTF-8?q?test(Input.stories):=20Input=20story=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/stories/Input.stories.tsx | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/stories/Input.stories.tsx diff --git a/src/stories/Input.stories.tsx b/src/stories/Input.stories.tsx new file mode 100644 index 0000000000..cd9b41c084 --- /dev/null +++ b/src/stories/Input.stories.tsx @@ -0,0 +1,25 @@ +import { Meta, StoryObj } from '@storybook/react'; +import Input from '../components/common/Input'; + +const meta = { + title: 'Input', + component: Input, + argTypes: { + type: { + options: { text: 'text', number: 'number' }, + control: { type: 'select' }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + isError: false, + width: '100%', + placeholder: '입력해주세요', + }, +}; From f8368dc390efa3b9a5ddd75230586b0879b2debe Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 15:28:00 +0900 Subject: [PATCH 39/65] =?UTF-8?q?test(CardPreview.stories):=20CardPreview?= =?UTF-8?q?=20story=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- .storybook/main.ts | 2 +- .storybook/{preview.ts => preview.tsx} | 12 ++++- src/stories/App.stories.tsx | 7 +-- src/stories/CardPreview.stories.tsx | 71 ++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 5 deletions(-) rename .storybook/{preview.ts => preview.tsx} (53%) create mode 100644 src/stories/CardPreview.stories.tsx diff --git a/.storybook/main.ts b/.storybook/main.ts index 69ac0074d7..2c6ee4bf25 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -14,7 +14,7 @@ const config: StorybookConfig = { options: {}, }, docs: { - autodocs: "tag", + autodocs: true, }, }; export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.tsx similarity index 53% rename from .storybook/preview.ts rename to .storybook/preview.tsx index 37914b18f2..fe276914fd 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.tsx @@ -1,11 +1,21 @@ import type { Preview } from "@storybook/react"; +import React from "react"; +import GlobalStyles from '../src/styles/GlobalStyles'; + +export const decorators = [ + (Story) => ( + <> + + + + ), +]; const preview: Preview = { parameters: { controls: { matchers: { color: /(background|color)$/i, - date: /Date$/i, }, }, }, diff --git a/src/stories/App.stories.tsx b/src/stories/App.stories.tsx index 7c5f8946bc..d290f56332 100644 --- a/src/stories/App.stories.tsx +++ b/src/stories/App.stories.tsx @@ -1,9 +1,10 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import App from "../App"; +import type { Meta, StoryObj } from '@storybook/react'; +import App from '../App'; const meta = { - title: "App", + title: 'App', component: App, + tags: ['autodocs'], } satisfies Meta; export default meta; diff --git a/src/stories/CardPreview.stories.tsx b/src/stories/CardPreview.stories.tsx new file mode 100644 index 0000000000..c916192360 --- /dev/null +++ b/src/stories/CardPreview.stories.tsx @@ -0,0 +1,71 @@ +import { Meta, StoryObj } from '@storybook/react'; +import CardPreview from '../components/CardPreview'; + +const meta = { + title: 'CardPreview', + component: CardPreview, + argTypes: { + cardNumbers: { + description: '• Visa: 4로 시작하는 16자리 숫자
• MasterCard: 51~55로 시작하는 16자리 숫자', + options: { + default: { + first: '1234', + second: '1234', + third: '1234', + fourth: '1234', + }, + 'visa card (4로 시작)': { + first: '4444', + second: '4444', + third: '4444', + fourth: '4444', + }, + 'master card (51~55로 시작)': { + first: '5555', + second: '5555', + third: '5555', + fourth: '5555', + }, + }, + control: { type: 'select' }, + }, + + expiryDate: { + options: { + '2024년 4월': { + month: '04', + year: '24', + }, + '2025년 11월': { + month: '11', + year: '25', + }, + '2040년 12월': { + month: '12', + year: '40', + }, + }, + control: { type: 'select' }, + }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + cardNumbers: { + first: '1234', + second: '1234', + third: '1234', + fourth: '1234', + }, + expiryDate: { + month: '11', + year: '25', + }, + cardholderName: 'John Doe', + }, +}; From 8dfc0fefaa9aa36df3e31a9c961d8b055e47180a Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 15:28:29 +0900 Subject: [PATCH 40/65] =?UTF-8?q?fix(CardPreview):=20=EC=9E=98=EB=AA=BB=20?= =?UTF-8?q?=EB=A7=A4=EC=B9=AD=EB=90=9C=20=EC=B9=B4=EB=93=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/components/CardPreview.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index edae33cd02..c25e52d7b7 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -20,8 +20,8 @@ const CardPreview = ({ cardNumbers, expiryDate, cardholderName }: CardPreviewPro - {isVisa && } - {isMaster && } + {isVisa && } + {isMaster && } @@ -124,7 +124,6 @@ const ExpiryDateWrapper = styled.div` const CardholderNameWrapper = styled.div` display: flex; align-items: center; - flex-basis: 20%; font-size: 20px; `; From f4fcfc2172dbebd2dc21034fcff15a8fda2188ad Mon Sep 17 00:00:00 2001 From: rbgksqkr Date: Thu, 18 Apr 2024 15:37:37 +0900 Subject: [PATCH 41/65] =?UTF-8?q?design(CardPreview):=20margin=20=EB=B0=8F?= =?UTF-8?q?=20padding=20=EC=A0=81=EC=9A=A9=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Parkhanyoung --- src/App.tsx | 45 ++++++++++++++++++++-------------- src/components/CardPreview.tsx | 3 +-- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 3a50441e00..0e589d1463 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,25 +53,27 @@ const App = () => { expiryDate={{ month: expiryMonth, year: expiryYear }} cardholderName={cardholderName} /> - - - + + + + + ); }; @@ -80,6 +82,11 @@ const AppLayout = styled.div` display: flex; flex-direction: column; align-items: center; + padding-top: 60px; +`; + +const CardInfoWrapper = styled.section` + margin-top: 50px; `; export default App; diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index c25e52d7b7..8783ae9828 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -51,10 +51,9 @@ const CardPreviewLayout = styled.div` flex-direction: column; width: 320px; height: 200px; - margin: 60px 0 50px 0; background-color: #333333; - padding: 16px 20px 20px 20px; + padding: 16px 20px 20px; border-radius: 8px; color: white; From be31813c24bf46bfea878928112f3432c037f3b7 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Thu, 18 Apr 2024 16:59:19 +0900 Subject: [PATCH 42/65] =?UTF-8?q?fix(Input):=20isError=20prop=EC=9D=84=20t?= =?UTF-8?q?ransient=20prop=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Input.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index ae7b94692e..cbd421dbc5 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -6,18 +6,18 @@ interface InputProps { } const Input = ({ isError = false, ...rest }: InputProps) => { - return ; + return ; }; interface StyledInputProps { - isError: boolean; + $isError: boolean; } const StyledInput = styled.input` width: ${props => props.width}; padding: 10px 7px; - border: 1.2px solid ${props => (props.isError ? '#ff3d3d' : '#acacac')}; + border: 1.2px solid ${props => (props.$isError ? '#ff3d3d' : '#acacac')}; border-radius: 5px; font-size: 15px; From d9bc2bfea7996033678219cd572071ba6536e1c2 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sat, 20 Apr 2024 19:52:27 +0900 Subject: [PATCH 43/65] =?UTF-8?q?refactor(Input):=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=95=88=EC=A0=95=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=B4=20Inp?= =?UTF-8?q?utProps=EA=B0=80InputHTMLAttributes=EB=A5=BC=20=EC=83=81?= =?UTF-8?q?=EC=86=8D=EB=B0=9B=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.cjs | 22 +++++++++------------- src/components/common/Input.tsx | 3 +-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 120af0b84b..0bd17478b4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,20 +2,16 @@ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ - "plugin:prettier/recommended", - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "plugin:react-hooks/recommended", - "plugin:storybook/recommended", + 'plugin:prettier/recommended', + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + 'plugin:storybook/recommended', ], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], rules: { - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], - "@typescript-eslint/no-explicit-any": "warn" + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], }, }; diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index cbd421dbc5..0b7d5ab85b 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -1,7 +1,6 @@ import styled from 'styled-components'; -interface InputProps { - [key: string]: any; +interface InputProps extends React.InputHTMLAttributes { isError?: boolean; } From 72040669906536791abb0042ec1db24b2204733b Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 11:42:54 +0900 Subject: [PATCH 44/65] =?UTF-8?q?refactor:=20useInput/useInputs=EB=A5=BC?= =?UTF-8?q?=20useValidation/useValidations=EB=A1=9C=20=EB=8C=80=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 71 ++++++++-------------- src/components/CardExpiryDateContainer.tsx | 39 ++++++------ src/components/CardNumbersContainer.tsx | 24 ++++---- src/components/CardholderNameContainer.tsx | 18 +++--- src/hooks/useInput.ts | 28 --------- src/hooks/useInputs.ts | 52 ---------------- src/hooks/useValidation.ts | 19 ++++++ src/hooks/useValidations.ts | 30 +++++++++ src/inquiry/index.d.ts | 4 ++ src/inquiry/inquireCardNumber.ts | 13 ++-- src/inquiry/inquireCardholderName.ts | 17 ++++-- src/inquiry/inquireExpiryMonth.ts | 10 +-- src/inquiry/inquireExpiryYear.ts | 10 +-- src/utils/convertKeysIntoObject.ts | 11 ++++ 14 files changed, 160 insertions(+), 186 deletions(-) delete mode 100644 src/hooks/useInput.ts delete mode 100644 src/hooks/useInputs.ts create mode 100644 src/hooks/useValidation.ts create mode 100644 src/hooks/useValidations.ts create mode 100644 src/inquiry/index.d.ts create mode 100644 src/utils/convertKeysIntoObject.ts diff --git a/src/App.tsx b/src/App.tsx index 0e589d1463..566ece90d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,50 +1,31 @@ import './App.css'; -import useInput from './hooks/useInput'; -import useInputs from './hooks/useInputs'; import CardholderNameContainer from './components/CardholderNameContainer'; import CardExpiryDateContainer from './components/CardExpiryDateContainer'; import { inquireCardNumber, inquireCardholderName, inquireExpiryMonth, inquireExpiryYear } from './inquiry'; import CardNumberContainer from './components/CardNumbersContainer'; import CardPreview from './components/CardPreview'; import styled from 'styled-components'; +import { useState } from 'react'; +import useValidation from './hooks/useValidation'; +import useValidations from './hooks/useValidations'; const App = () => { - const { - value: cardNumbers, - generateChangeHandler: generateCardNumbersChangeHandler, - generateErrorMessageUpdater: generateCardNumberErrorMessageUpdater, - errorMessage: cardNumbersErrorMessage, - errorStatus: cardNumbersErrorStatus, - } = useInputs( - { - first: '', - second: '', - third: '', - fourth: '', - }, - inquireCardNumber, - ); + const [cardNumbers, setCardNumbers] = useState({ + first: '', + second: '', + third: '', + fourth: '', + }); + const cardNumbersValidation = useValidations(cardNumbers, inquireCardNumber); - const { - value: cardholderName, - setValue: setCardholderName, - updateErrorMessage: updateCardholderNameErrorMessage, - errorMessage: cardholderNameErrorMessage, - } = useInput('', inquireCardholderName); + const [cardholderName, setCardholderName] = useState(''); + const cardholderNameValidation = useValidation(cardholderName, inquireCardholderName); - const { - value: expiryMonth, - handleChange: handleChangeExpiryMonth, - updateErrorMessage: updateExpiryMonthErrorMessage, - errorMessage: expiryMonthErrorMessage, - } = useInput('', inquireExpiryMonth); + const [expiryMonth, setExpiryMonth] = useState(''); + const expiryMonthValidation = useValidation(expiryMonth, inquireExpiryMonth); - const { - value: expiryYear, - handleChange: handleChangeExpiryYear, - updateErrorMessage: updateExpiryYearErrorMessage, - errorMessage: expiryYearErrorMessage, - } = useInput('', inquireExpiryYear); + const [expiryYear, setExpiryYear] = useState(''); + const expiryYearValidation = useValidation(expiryYear, inquireExpiryYear); return ( @@ -54,24 +35,20 @@ const App = () => { cardholderName={cardholderName} /> - + diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index a2b7993e47..fe4b0ea7ef 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -1,21 +1,19 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; import RegistrationLayout from './common/RegistrationLayout'; +import { IErrorStatus } from '../inquiry/index.d'; type MM = string; type YY = string; interface CardExpiryDateContainerProps { expiryDate: { month: MM; year: YY }; - changeHandler: { - month: (e: React.ChangeEvent) => void; - year: (e: React.ChangeEvent) => void; + expiryDateSetter: { month: React.Dispatch>; year: React.Dispatch> }; + errorStatus: { + month: IErrorStatus; + year: IErrorStatus; }; - errorMessage: { - month: string; - year: string; - }; - errorMessageUpdater: { + errorStatusUpdater: { month: () => void; year: () => void; }; @@ -23,10 +21,13 @@ interface CardExpiryDateContainerProps { const CardExpiryDateContainer = ({ expiryDate, - changeHandler, - errorMessageUpdater, - errorMessage, + expiryDateSetter, + errorStatus, + errorStatusUpdater, }: CardExpiryDateContainerProps) => { + const onMonthChange = (e: React.ChangeEvent) => expiryDateSetter.month(e.target.value); + const onYearChange = (e: React.ChangeEvent) => expiryDateSetter.year(e.target.value); + return (
- {errorMessage.month} - {errorMessage.year} + {errorStatus.month.errorMessage} + {errorStatus.year.errorMessage}
); diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersContainer.tsx index 649cf3a1b2..bff134a923 100644 --- a/src/components/CardNumbersContainer.tsx +++ b/src/components/CardNumbersContainer.tsx @@ -6,20 +6,22 @@ type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; export interface CardNumbersContainerProps { cardNumbers: Record; - generateChangeHandler: (targetKey: string) => (e: React.ChangeEvent) => void; - errorMessage: string; - errorStatus: Record; - generateErrorMessageUpdater: (targetKey: string) => () => void; + setCardNumbers: React.Dispatch>>; + errorStatus: { isError: Record; errorMessage: string }; + updateErrorStatus: (key: CardNumberKey) => void; } export default function CardNumberContainer({ cardNumbers, - generateChangeHandler, - errorMessage, + setCardNumbers, errorStatus, - generateErrorMessageUpdater, + updateErrorStatus, }: CardNumbersContainerProps) { const arr = ['first', 'second', 'third', 'fourth'] as const; + const generateOnChange = (key: CardNumberKey) => (e: React.ChangeEvent) => { + setCardNumbers({ ...cardNumbers, [key]: e.target.value }); + }; + return (
updateErrorStatus(key)} placeholder="1234" maxLength={4} type={type} @@ -49,7 +51,7 @@ export default function CardNumberContainer({ })} - {errorMessage} + {errorStatus.errorMessage}
); diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index 3fc7841824..9a023fab7c 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -5,34 +5,34 @@ import RegistrationLayout from './common/RegistrationLayout'; interface CardholderNameContainerProps { cardholderName: string; setCardholderName: React.Dispatch>; - updateErrorMessage: () => void; - errorMessage: string; + errorStatus: { errorMessage: string; isError: boolean }; + updateErrorStatus: () => void; } const CardholderNameContainer = ({ cardholderName, setCardholderName, - updateErrorMessage, - errorMessage, + errorStatus, + updateErrorStatus, }: CardholderNameContainerProps) => { - const handleChange = (e: React.ChangeEvent) => setCardholderName(e.target.value.toUpperCase()); + const onChange = (e: React.ChangeEvent) => setCardholderName(e.target.value.toUpperCase()); return (
- {errorMessage} + {errorStatus.errorMessage}
); diff --git a/src/hooks/useInput.ts b/src/hooks/useInput.ts deleted file mode 100644 index fc29cb1b6c..0000000000 --- a/src/hooks/useInput.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useState } from 'react'; - -type ErrorMessage = string; - -const useInput = (initialValue = '', inquireValidity?: (value: string) => ErrorMessage) => { - const [value, setValue] = useState(initialValue); - const [errorMessage, setErrorMessage] = useState(''); - - const handleChange = (e: React.ChangeEvent) => { - setValue(() => e.target.value); - }; - - const updateErrorMessage = () => { - if (inquireValidity) { - setErrorMessage(inquireValidity(value)); - } - }; - - return { - value, - setValue, - handleChange, - updateErrorMessage, - errorMessage, - }; -}; - -export default useInput; diff --git a/src/hooks/useInputs.ts b/src/hooks/useInputs.ts deleted file mode 100644 index 70daac8dab..0000000000 --- a/src/hooks/useInputs.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useState } from 'react'; - -type ErrorMessage = string; - -const convertArrayIntoObject = (keys: string[]) => { - const obj: Record = {}; - - keys.forEach(key => { - obj[key] = false; - }); - - return obj; -}; - -const useInputs = >( - initialValue: T, - inquireValidity: (value: string) => ErrorMessage, -) => { - const [value, setValue] = useState(initialValue); - const initialErrorStatus = convertArrayIntoObject(Object.keys(initialValue)); - const [errorStatus, setErrorStatus] = useState(initialErrorStatus); - const [errorMessage, setErrorMessage] = useState(''); - - const generateChangeHandler = (targetKey: string) => (e: React.ChangeEvent) => { - setValue({ - ...value, - [targetKey]: e.target.value, - }); - }; - - const generateErrorMessageUpdater = (key: string) => () => { - const foundErrorMessage = inquireValidity(value[key]); - - setErrorStatus({ - ...errorStatus, - [key]: Boolean(foundErrorMessage), - }); - - setErrorMessage(foundErrorMessage); - }; - - return { - value, - setValue, - generateChangeHandler, - generateErrorMessageUpdater, - errorStatus, - errorMessage, - }; -}; - -export default useInputs; diff --git a/src/hooks/useValidation.ts b/src/hooks/useValidation.ts new file mode 100644 index 0000000000..79ec6815ac --- /dev/null +++ b/src/hooks/useValidation.ts @@ -0,0 +1,19 @@ +import { useCallback, useState } from 'react'; +import { IErrorStatus } from '../inquiry/index.d'; + +type TValidate = (value: string) => IErrorStatus; + +const useValidation = (value: string, validate: TValidate) => { + const [errorStatus, setErrorStatus] = useState({ + errorMessage: '', + isError: false, + }); + + const updateErrorStatus = useCallback(() => { + setErrorStatus(validate(value)); + }, [value, validate]); + + return { errorStatus, updateErrorStatus }; +}; + +export default useValidation; diff --git a/src/hooks/useValidations.ts b/src/hooks/useValidations.ts new file mode 100644 index 0000000000..2c0470a30e --- /dev/null +++ b/src/hooks/useValidations.ts @@ -0,0 +1,30 @@ +import { useCallback, useState } from 'react'; +import { IErrorStatus } from '../inquiry/index.d'; +import convertKeysIntoObject from '../utils/convertKeysIntoObject'; + +type TValidate = (value: string) => IErrorStatus; + +const useValidations = >(value: T, validate: TValidate) => { + const initialErrorStatus = { + isError: convertKeysIntoObject(Object.keys(value), false), + errorMessage: '', + }; + + const [errorStatus, setErrorStatus] = useState(initialErrorStatus); + + const updateErrorStatus = useCallback( + (key: keyof T) => { + const { isError, errorMessage } = validate(value[key]); + + setErrorStatus({ + isError: { ...errorStatus.isError, [key]: isError }, + errorMessage, + }); + }, + [value, validate, errorStatus], + ); + + return { errorStatus, updateErrorStatus }; +}; + +export default useValidations; diff --git a/src/inquiry/index.d.ts b/src/inquiry/index.d.ts new file mode 100644 index 0000000000..a2d6efa047 --- /dev/null +++ b/src/inquiry/index.d.ts @@ -0,0 +1,4 @@ +export interface IErrorStatus { + errorMessage: string; + isError: boolean; +} diff --git a/src/inquiry/inquireCardNumber.ts b/src/inquiry/inquireCardNumber.ts index 9613367d90..96b79be155 100644 --- a/src/inquiry/inquireCardNumber.ts +++ b/src/inquiry/inquireCardNumber.ts @@ -1,16 +1,17 @@ -const inquireCardNumber = (cardNumber: string) => { - const isValidLength = cardNumber.length === 0 || cardNumber.length === 4; - const isValidCardNumber = /^0{1,4}|[1-9]\d{0,3}$/.test(cardNumber); +import { IErrorStatus } from './index.d'; +const inquireCardNumber = (cardNumber: string): IErrorStatus => { + const isValidLength = cardNumber.length === 0 || cardNumber.length === 4; if (!isValidLength) { - return '카드 번호는 4자리로 입력해주세요'; + return { isError: true, errorMessage: '카드 번호는 4자리로 입력해주세요' }; } + const isValidCardNumber = /^0{1,4}|[1-9]\d{0,3}$/.test(cardNumber); if (!isValidCardNumber) { - return '카드 번호는 0000 ~ 9999 사이의 숫자로 입력해주세요'; + return { isError: true, errorMessage: '카드 번호는 0000 ~ 9999 사이의 숫자로 입력해주세요' }; } - return ''; + return { isError: false, errorMessage: '' }; }; export default inquireCardNumber; diff --git a/src/inquiry/inquireCardholderName.ts b/src/inquiry/inquireCardholderName.ts index 595e424312..3b4920d7a9 100644 --- a/src/inquiry/inquireCardholderName.ts +++ b/src/inquiry/inquireCardholderName.ts @@ -1,15 +1,20 @@ -const inquireCardholderName = (cardholderName: string) => { +import { IErrorStatus } from './index.d'; + +const inquireCardholderName = (cardholderName: string): IErrorStatus => { const isEnglish = /^[a-zA-Z ]+$/.test(cardholderName); if (!isEnglish) { - return '카드 소유자 이름을 영어로만 입력해주세요'; + return { isError: true, errorMessage: '카드 소유자 이름을 영어로만 입력해주세요' }; + } + + if (cardholderName.trim() !== cardholderName) { + return { isError: true, errorMessage: '양 끝에 공백이 포함될 수 없습니다.' }; } - const containSideBlankOrMultipleBlanks = !/^(?:\S(?:\s\S*)?)?\S$/.test(cardholderName); - if (containSideBlankOrMultipleBlanks) { - return '양 끝에 공백이 포함되면 안 되며, 사이 공백의 길이는 최대 1입니다.'; + if (cardholderName.includes(' ')) { + return { isError: true, errorMessage: '사이 공백은 최대 한 칸 입력할 수 있습니다.' }; } - return ''; + return { isError: false, errorMessage: '' }; }; export default inquireCardholderName; diff --git a/src/inquiry/inquireExpiryMonth.ts b/src/inquiry/inquireExpiryMonth.ts index a8a82f2773..66b6f5fda0 100644 --- a/src/inquiry/inquireExpiryMonth.ts +++ b/src/inquiry/inquireExpiryMonth.ts @@ -1,16 +1,18 @@ -const inquireExpiryMonth = (expiryMonth: string) => { +import { IErrorStatus } from './index.d'; + +const inquireExpiryMonth = (expiryMonth: string): IErrorStatus => { const isValidLength = expiryMonth.length === 0 || expiryMonth.length === 2; const isValidMonth = /^(0[1-9]|1[0-2])$/.test(expiryMonth); if (!isValidLength) { - return '월(月) : 2자리로 입력해주세요'; + return { isError: true, errorMessage: '월(月) : 2자리로 입력해주세요' }; } if (!isValidMonth) { - return '월(月) : 01월부터 12월 중 하나로 입력해주세요'; + return { isError: true, errorMessage: '월(月) : 01월부터 12월 중 하나로 입력해주세요' }; } - return ''; + return { isError: false, errorMessage: '' }; }; export default inquireExpiryMonth; diff --git a/src/inquiry/inquireExpiryYear.ts b/src/inquiry/inquireExpiryYear.ts index 5f4de512fb..b46652d063 100644 --- a/src/inquiry/inquireExpiryYear.ts +++ b/src/inquiry/inquireExpiryYear.ts @@ -1,16 +1,18 @@ -const inquireExpiryYear = (expiryYear: string) => { +import { IErrorStatus } from './index.d'; + +const inquireExpiryYear = (expiryYear: string): IErrorStatus => { const isValidLength = expiryYear.length === 0 || expiryYear.length === 2; const isValidYear = Number(expiryYear) > 23 && Number(expiryYear) < 41; if (!isValidLength) { - return '년도(年) : 2자리로 입력해주세요'; + return { isError: true, errorMessage: '년도(年) : 2자리로 입력해주세요' }; } if (!isValidYear) { - return '년도(年) : 24년도부터 40년도 중 하나로 입력해주세요'; + return { isError: true, errorMessage: '년도(年) : 24년도부터 40년도 중 하나로 입력해주세요' }; } - return ''; + return { isError: false, errorMessage: '' }; }; export default inquireExpiryYear; diff --git a/src/utils/convertKeysIntoObject.ts b/src/utils/convertKeysIntoObject.ts new file mode 100644 index 0000000000..f33c01887a --- /dev/null +++ b/src/utils/convertKeysIntoObject.ts @@ -0,0 +1,11 @@ +const convertKeysIntoObject = (keys: string[], defaultValue: T) => { + const obj: Record = {}; + + keys.forEach(key => { + obj[key] = defaultValue; + }); + + return obj; +}; + +export default convertKeysIntoObject; From 0ec98e5a6f33d01bf62c673baeb5effb1e011f09 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 11:43:14 +0900 Subject: [PATCH 45/65] =?UTF-8?q?chore(gitignore):=20eol=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 51a7d98a37..1a72d4d657 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,4 @@ dist-ssr *.sln *.sw? -*storybook.log \ No newline at end of file +*storybook.log From 3719a25bfabc05fbed43c7ce618305eaa0822201 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 14:05:22 +0900 Subject: [PATCH 46/65] =?UTF-8?q?refactor:=20container=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=93=A4=EC=9D=98=20interface?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=EC=9D=BC=20/=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=EB=B2=88=ED=98=B8,=20=EC=9C=A0=ED=9A=A8=EA=B8=B0=EA=B0=84,=20?= =?UTF-8?q?=EC=86=8C=EC=9C=A0=EC=9E=90=EB=AA=85=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=ED=9B=85=EC=9D=84=20?= =?UTF-8?q?=EB=AC=B6=EC=96=B4=EC=A3=BC=EB=8A=94=20useCardInfo=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 52 +++++----------------- src/components/CardExpiryDateContainer.tsx | 45 +++++++++---------- src/components/CardNumbersContainer.tsx | 12 ++--- src/components/CardholderNameContainer.tsx | 15 +++---- src/hooks/useCardInfo.ts | 17 +++++++ src/hooks/useCardNumbers.ts | 22 +++++++++ src/hooks/useCardholderName.ts | 17 +++++++ src/hooks/useExpiryDate.ts | 26 +++++++++++ 8 files changed, 125 insertions(+), 81 deletions(-) create mode 100644 src/hooks/useCardInfo.ts create mode 100644 src/hooks/useCardNumbers.ts create mode 100644 src/hooks/useCardholderName.ts create mode 100644 src/hooks/useExpiryDate.ts diff --git a/src/App.tsx b/src/App.tsx index 566ece90d3..490f9f1217 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,55 +1,23 @@ import './App.css'; +import styled from 'styled-components'; + import CardholderNameContainer from './components/CardholderNameContainer'; import CardExpiryDateContainer from './components/CardExpiryDateContainer'; -import { inquireCardNumber, inquireCardholderName, inquireExpiryMonth, inquireExpiryYear } from './inquiry'; -import CardNumberContainer from './components/CardNumbersContainer'; +import CardNumbersContainer from './components/CardNumbersContainer'; import CardPreview from './components/CardPreview'; -import styled from 'styled-components'; -import { useState } from 'react'; -import useValidation from './hooks/useValidation'; -import useValidations from './hooks/useValidations'; -const App = () => { - const [cardNumbers, setCardNumbers] = useState({ - first: '', - second: '', - third: '', - fourth: '', - }); - const cardNumbersValidation = useValidations(cardNumbers, inquireCardNumber); +import useCardInfo from './hooks/useCardInfo'; - const [cardholderName, setCardholderName] = useState(''); - const cardholderNameValidation = useValidation(cardholderName, inquireCardholderName); - - const [expiryMonth, setExpiryMonth] = useState(''); - const expiryMonthValidation = useValidation(expiryMonth, inquireExpiryMonth); - - const [expiryYear, setExpiryYear] = useState(''); - const expiryYearValidation = useValidation(expiryYear, inquireExpiryYear); +const App = () => { + const { cardNumbers, expiryDate, cardholderName } = useCardInfo(); return ( - + - - - + + + ); diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index fe4b0ea7ef..9ff6d83165 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -6,27 +6,26 @@ import { IErrorStatus } from '../inquiry/index.d'; type MM = string; type YY = string; +type TErrorStatusUpdater = () => void; + interface CardExpiryDateContainerProps { - expiryDate: { month: MM; year: YY }; - expiryDateSetter: { month: React.Dispatch>; year: React.Dispatch> }; - errorStatus: { - month: IErrorStatus; - year: IErrorStatus; - }; - errorStatusUpdater: { - month: () => void; - year: () => void; + data: { month: MM; year: YY }; + dataSetter: { + month: React.Dispatch>; + year: React.Dispatch>; }; + errorStatus: { month: IErrorStatus; year: IErrorStatus }; + errorStatusUpdater: { month: TErrorStatusUpdater; year: TErrorStatusUpdater }; } const CardExpiryDateContainer = ({ - expiryDate, - expiryDateSetter, - errorStatus, - errorStatusUpdater, + data, + dataSetter: { month: setMonth, year: setYear }, + errorStatus: { month: monthErrorStatus, year: yearErrorStatus }, + errorStatusUpdater: { month: updateMonthErrorStatus, year: updateYearErrorStatus }, }: CardExpiryDateContainerProps) => { - const onMonthChange = (e: React.ChangeEvent) => expiryDateSetter.month(e.target.value); - const onYearChange = (e: React.ChangeEvent) => expiryDateSetter.year(e.target.value); + const onMonthChange = (e: React.ChangeEvent) => setMonth(e.target.value); + const onYearChange = (e: React.ChangeEvent) => setYear(e.target.value); return (
@@ -38,28 +37,28 @@ const CardExpiryDateContainer = ({ > - {errorStatus.month.errorMessage} - {errorStatus.year.errorMessage} + {monthErrorStatus.errorMessage} + {yearErrorStatus.errorMessage}
); diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersContainer.tsx index bff134a923..9b2fa59213 100644 --- a/src/components/CardNumbersContainer.tsx +++ b/src/components/CardNumbersContainer.tsx @@ -5,21 +5,21 @@ import RegistrationLayout from './common/RegistrationLayout'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; export interface CardNumbersContainerProps { - cardNumbers: Record; - setCardNumbers: React.Dispatch>>; + data: Record; + setData: React.Dispatch>>; errorStatus: { isError: Record; errorMessage: string }; updateErrorStatus: (key: CardNumberKey) => void; } export default function CardNumberContainer({ - cardNumbers, - setCardNumbers, + data, + setData, errorStatus, updateErrorStatus, }: CardNumbersContainerProps) { const arr = ['first', 'second', 'third', 'fourth'] as const; const generateOnChange = (key: CardNumberKey) => (e: React.ChangeEvent) => { - setCardNumbers({ ...cardNumbers, [key]: e.target.value }); + setData({ ...data, [key]: e.target.value }); }; return ( @@ -39,7 +39,7 @@ export default function CardNumberContainer({ key={key} id={`${key}-card-numbers-input`} isError={errorStatus.isError[key]} - value={cardNumbers[key]} + value={data[key]} onChange={generateOnChange(key)} onBlur={() => updateErrorStatus(key)} placeholder="1234" diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index 9a023fab7c..70a27d15c2 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -3,19 +3,14 @@ import { ErrorWrapper, ErrorText } from '../styles/common'; import RegistrationLayout from './common/RegistrationLayout'; interface CardholderNameContainerProps { - cardholderName: string; - setCardholderName: React.Dispatch>; + data: string; + setData: React.Dispatch>; errorStatus: { errorMessage: string; isError: boolean }; updateErrorStatus: () => void; } -const CardholderNameContainer = ({ - cardholderName, - setCardholderName, - errorStatus, - updateErrorStatus, -}: CardholderNameContainerProps) => { - const onChange = (e: React.ChangeEvent) => setCardholderName(e.target.value.toUpperCase()); +const CardholderNameContainer = ({ data, setData, errorStatus, updateErrorStatus }: CardholderNameContainerProps) => { + const onChange = (e: React.ChangeEvent) => setData(e.target.value.toUpperCase()); return (
@@ -23,7 +18,7 @@ const CardholderNameContainer = ({ { + const cardNumbersControl = useCardNumbers(); + const expiryDateControl = useExpiryDate(); + const cardholderNameControl = useCardHolderName(); + + return { + cardNumbers: cardNumbersControl, + expiryDate: expiryDateControl, + cardholderName: cardholderNameControl, + }; +}; + +export default useCardInfo; diff --git a/src/hooks/useCardNumbers.ts b/src/hooks/useCardNumbers.ts new file mode 100644 index 0000000000..c1dea8d03f --- /dev/null +++ b/src/hooks/useCardNumbers.ts @@ -0,0 +1,22 @@ +import { useState } from 'react'; +import useValidations from './useValidations'; +import { inquireCardNumber } from '../inquiry'; + +const useCardNumbers = () => { + const [cardNumbers, setCardNumbers] = useState({ + first: '', + second: '', + third: '', + fourth: '', + }); + const { errorStatus, updateErrorStatus } = useValidations(cardNumbers, inquireCardNumber); + + return { + data: cardNumbers, + setData: setCardNumbers, + errorStatus, + updateErrorStatus, + }; +}; + +export default useCardNumbers; diff --git a/src/hooks/useCardholderName.ts b/src/hooks/useCardholderName.ts new file mode 100644 index 0000000000..7f285897be --- /dev/null +++ b/src/hooks/useCardholderName.ts @@ -0,0 +1,17 @@ +import { useState } from 'react'; +import useValidation from './useValidation'; +import { inquireCardholderName } from '../inquiry'; + +const useCardHolderName = () => { + const [cardholderName, setCardholderName] = useState(''); + const { errorStatus, updateErrorStatus } = useValidation(cardholderName, inquireCardholderName); + + return { + data: cardholderName, + setData: setCardholderName, + errorStatus, + updateErrorStatus, + }; +}; + +export default useCardHolderName; diff --git a/src/hooks/useExpiryDate.ts b/src/hooks/useExpiryDate.ts new file mode 100644 index 0000000000..6432e70ee3 --- /dev/null +++ b/src/hooks/useExpiryDate.ts @@ -0,0 +1,26 @@ +import { useState } from 'react'; +import useValidation from './useValidation'; +import { inquireExpiryMonth, inquireExpiryYear } from '../inquiry'; + +const useExpiryDate = () => { + const [expiryMonth, setExpiryMonth] = useState(''); + const { errorStatus: expiryMonthErrorStatus, updateErrorStatus: updateExpiryMonthErrorStatus } = useValidation( + expiryMonth, + inquireExpiryMonth, + ); + + const [expiryYear, setExpiryYear] = useState(''); + const { errorStatus: expiryYearErrorStatus, updateErrorStatus: updateExpiryYearErrorStatus } = useValidation( + expiryYear, + inquireExpiryYear, + ); + + return { + data: { month: expiryMonth, year: expiryYear }, + dataSetter: { month: setExpiryMonth, year: setExpiryYear }, + errorStatus: { month: expiryMonthErrorStatus, year: expiryYearErrorStatus }, + errorStatusUpdater: { month: updateExpiryMonthErrorStatus, year: updateExpiryYearErrorStatus }, + }; +}; + +export default useExpiryDate; From 3e58dc358f6a0975b7c1314c1ab8e2ee8c1541ca Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 14:06:19 +0900 Subject: [PATCH 47/65] =?UTF-8?q?chore:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20cs?= =?UTF-8?q?s=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 0 src/index.css | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/App.css delete mode 100644 src/index.css diff --git a/src/App.css b/src/App.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/index.css b/src/index.css deleted file mode 100644 index e69de29bb2..0000000000 From 044882b80ea81f6ddf4394a1c37e36f40c58b8b1 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 14:33:30 +0900 Subject: [PATCH 48/65] =?UTF-8?q?refactor:=20=EC=9C=A0=ED=9A=A8=EC=84=B1?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=EC=9D=98=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95(inquire=20->=20validate?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardExpiryDateContainer.tsx | 2 +- src/components/{CardPreview.tsx => CardPreview/index.tsx} | 0 src/hooks/useCardNumbers.ts | 4 ++-- src/hooks/useCardholderName.ts | 4 ++-- src/hooks/useExpiryDate.ts | 6 +++--- src/hooks/useValidation.ts | 2 +- src/hooks/useValidations.ts | 2 +- src/inquiry/index.ts | 6 ------ src/{inquiry => validators}/index.d.ts | 0 src/validators/index.ts | 6 ++++++ .../validateCardNumber.ts} | 4 ++-- .../validateCardholderName.ts} | 4 ++-- .../validateExpiryMonth.ts} | 4 ++-- .../validateExpiryYear.ts} | 0 14 files changed, 22 insertions(+), 22 deletions(-) rename src/components/{CardPreview.tsx => CardPreview/index.tsx} (100%) delete mode 100644 src/inquiry/index.ts rename src/{inquiry => validators}/index.d.ts (100%) create mode 100644 src/validators/index.ts rename src/{inquiry/inquireCardNumber.ts => validators/validateCardNumber.ts} (83%) rename src/{inquiry/inquireCardholderName.ts => validators/validateCardholderName.ts} (83%) rename src/{inquiry/inquireExpiryMonth.ts => validators/validateExpiryMonth.ts} (82%) rename src/{inquiry/inquireExpiryYear.ts => validators/validateExpiryYear.ts} (100%) diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index 9ff6d83165..995a76b7de 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -1,7 +1,7 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; import RegistrationLayout from './common/RegistrationLayout'; -import { IErrorStatus } from '../inquiry/index.d'; +import { IErrorStatus } from '../validators/index.d'; type MM = string; type YY = string; diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview/index.tsx similarity index 100% rename from src/components/CardPreview.tsx rename to src/components/CardPreview/index.tsx diff --git a/src/hooks/useCardNumbers.ts b/src/hooks/useCardNumbers.ts index c1dea8d03f..d182b3219a 100644 --- a/src/hooks/useCardNumbers.ts +++ b/src/hooks/useCardNumbers.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import useValidations from './useValidations'; -import { inquireCardNumber } from '../inquiry'; +import { validateCardNumber } from '../validators'; const useCardNumbers = () => { const [cardNumbers, setCardNumbers] = useState({ @@ -9,7 +9,7 @@ const useCardNumbers = () => { third: '', fourth: '', }); - const { errorStatus, updateErrorStatus } = useValidations(cardNumbers, inquireCardNumber); + const { errorStatus, updateErrorStatus } = useValidations(cardNumbers, validateCardNumber); return { data: cardNumbers, diff --git a/src/hooks/useCardholderName.ts b/src/hooks/useCardholderName.ts index 7f285897be..d4d7de324c 100644 --- a/src/hooks/useCardholderName.ts +++ b/src/hooks/useCardholderName.ts @@ -1,10 +1,10 @@ import { useState } from 'react'; import useValidation from './useValidation'; -import { inquireCardholderName } from '../inquiry'; +import { validateCardholderName } from '../validators'; const useCardHolderName = () => { const [cardholderName, setCardholderName] = useState(''); - const { errorStatus, updateErrorStatus } = useValidation(cardholderName, inquireCardholderName); + const { errorStatus, updateErrorStatus } = useValidation(cardholderName, validateCardholderName); return { data: cardholderName, diff --git a/src/hooks/useExpiryDate.ts b/src/hooks/useExpiryDate.ts index 6432e70ee3..39f7504fbf 100644 --- a/src/hooks/useExpiryDate.ts +++ b/src/hooks/useExpiryDate.ts @@ -1,18 +1,18 @@ import { useState } from 'react'; import useValidation from './useValidation'; -import { inquireExpiryMonth, inquireExpiryYear } from '../inquiry'; +import { validateExpiryMonth, validateExpiryYear } from '../validators'; const useExpiryDate = () => { const [expiryMonth, setExpiryMonth] = useState(''); const { errorStatus: expiryMonthErrorStatus, updateErrorStatus: updateExpiryMonthErrorStatus } = useValidation( expiryMonth, - inquireExpiryMonth, + validateExpiryMonth, ); const [expiryYear, setExpiryYear] = useState(''); const { errorStatus: expiryYearErrorStatus, updateErrorStatus: updateExpiryYearErrorStatus } = useValidation( expiryYear, - inquireExpiryYear, + validateExpiryYear, ); return { diff --git a/src/hooks/useValidation.ts b/src/hooks/useValidation.ts index 79ec6815ac..55e93269a6 100644 --- a/src/hooks/useValidation.ts +++ b/src/hooks/useValidation.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import { IErrorStatus } from '../inquiry/index.d'; +import { IErrorStatus } from '../validators/index.d'; type TValidate = (value: string) => IErrorStatus; diff --git a/src/hooks/useValidations.ts b/src/hooks/useValidations.ts index 2c0470a30e..ae5c9d047e 100644 --- a/src/hooks/useValidations.ts +++ b/src/hooks/useValidations.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react'; -import { IErrorStatus } from '../inquiry/index.d'; +import { IErrorStatus } from '../validators/index.d'; import convertKeysIntoObject from '../utils/convertKeysIntoObject'; type TValidate = (value: string) => IErrorStatus; diff --git a/src/inquiry/index.ts b/src/inquiry/index.ts deleted file mode 100644 index 16e3d25881..0000000000 --- a/src/inquiry/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import inquireCardholderName from './inquireCardholderName'; -import inquireExpiryMonth from './inquireExpiryMonth'; -import inquireExpiryYear from './inquireExpiryYear'; -import inquireCardNumber from './inquireCardNumber'; - -export { inquireCardholderName, inquireExpiryMonth, inquireExpiryYear, inquireCardNumber }; diff --git a/src/inquiry/index.d.ts b/src/validators/index.d.ts similarity index 100% rename from src/inquiry/index.d.ts rename to src/validators/index.d.ts diff --git a/src/validators/index.ts b/src/validators/index.ts new file mode 100644 index 0000000000..c8f164b6a0 --- /dev/null +++ b/src/validators/index.ts @@ -0,0 +1,6 @@ +import validateCardholderName from './validateCardholderName'; +import validateExpiryMonth from './validateExpiryMonth'; +import validateExpiryYear from './validateExpiryYear'; +import validateCardNumber from './validateCardNumber'; + +export { validateCardholderName, validateExpiryMonth, validateExpiryYear, validateCardNumber }; diff --git a/src/inquiry/inquireCardNumber.ts b/src/validators/validateCardNumber.ts similarity index 83% rename from src/inquiry/inquireCardNumber.ts rename to src/validators/validateCardNumber.ts index 96b79be155..ddc576422f 100644 --- a/src/inquiry/inquireCardNumber.ts +++ b/src/validators/validateCardNumber.ts @@ -1,6 +1,6 @@ import { IErrorStatus } from './index.d'; -const inquireCardNumber = (cardNumber: string): IErrorStatus => { +const validateCardNumber = (cardNumber: string): IErrorStatus => { const isValidLength = cardNumber.length === 0 || cardNumber.length === 4; if (!isValidLength) { return { isError: true, errorMessage: '카드 번호는 4자리로 입력해주세요' }; @@ -14,4 +14,4 @@ const inquireCardNumber = (cardNumber: string): IErrorStatus => { return { isError: false, errorMessage: '' }; }; -export default inquireCardNumber; +export default validateCardNumber; diff --git a/src/inquiry/inquireCardholderName.ts b/src/validators/validateCardholderName.ts similarity index 83% rename from src/inquiry/inquireCardholderName.ts rename to src/validators/validateCardholderName.ts index 3b4920d7a9..947f2062cf 100644 --- a/src/inquiry/inquireCardholderName.ts +++ b/src/validators/validateCardholderName.ts @@ -1,6 +1,6 @@ import { IErrorStatus } from './index.d'; -const inquireCardholderName = (cardholderName: string): IErrorStatus => { +const validateCardholderName = (cardholderName: string): IErrorStatus => { const isEnglish = /^[a-zA-Z ]+$/.test(cardholderName); if (!isEnglish) { return { isError: true, errorMessage: '카드 소유자 이름을 영어로만 입력해주세요' }; @@ -17,4 +17,4 @@ const inquireCardholderName = (cardholderName: string): IErrorStatus => { return { isError: false, errorMessage: '' }; }; -export default inquireCardholderName; +export default validateCardholderName; diff --git a/src/inquiry/inquireExpiryMonth.ts b/src/validators/validateExpiryMonth.ts similarity index 82% rename from src/inquiry/inquireExpiryMonth.ts rename to src/validators/validateExpiryMonth.ts index 66b6f5fda0..f883d61c90 100644 --- a/src/inquiry/inquireExpiryMonth.ts +++ b/src/validators/validateExpiryMonth.ts @@ -1,6 +1,6 @@ import { IErrorStatus } from './index.d'; -const inquireExpiryMonth = (expiryMonth: string): IErrorStatus => { +const validateExpiryMonth = (expiryMonth: string): IErrorStatus => { const isValidLength = expiryMonth.length === 0 || expiryMonth.length === 2; const isValidMonth = /^(0[1-9]|1[0-2])$/.test(expiryMonth); @@ -15,4 +15,4 @@ const inquireExpiryMonth = (expiryMonth: string): IErrorStatus => { return { isError: false, errorMessage: '' }; }; -export default inquireExpiryMonth; +export default validateExpiryMonth; diff --git a/src/inquiry/inquireExpiryYear.ts b/src/validators/validateExpiryYear.ts similarity index 100% rename from src/inquiry/inquireExpiryYear.ts rename to src/validators/validateExpiryYear.ts From 4a29bc5062eaa754a7cd32809ceb2a0757afd9b0 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 14:49:17 +0900 Subject: [PATCH 49/65] =?UTF-8?q?refactor(CardPreview):=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=B8=8C=EB=9E=9C=EB=93=9C=20=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=EB=A5=BC=20=EB=B3=84=EB=8F=84=EC=9D=98=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardPreview/CardBrandLogo.tsx | 53 ++++++++++++++++++++ src/components/CardPreview/index.tsx | 23 ++------- 2 files changed, 56 insertions(+), 20 deletions(-) create mode 100644 src/components/CardPreview/CardBrandLogo.tsx diff --git a/src/components/CardPreview/CardBrandLogo.tsx b/src/components/CardPreview/CardBrandLogo.tsx new file mode 100644 index 0000000000..390d39db71 --- /dev/null +++ b/src/components/CardPreview/CardBrandLogo.tsx @@ -0,0 +1,53 @@ +import styled from 'styled-components'; +import MasterCardLogo from '../../../src/assets/images/mastercard.png'; +import VisaCardLogo from '../../../src/assets/images/visa.png'; + +interface ICardBrandLogoProps { + firstTwoDigits: string; +} + +type LogoImageSrc = string; + +const VISA_FIRST_DIGIT = '4'; +const MIN_MASTER_FIRST_TWO_DIGITS = 51; +const MAX_MASTER_FIRST_TWO_DIGITS = 55; + +const getCardBrandLogo = (firstTwoDigits: string): LogoImageSrc | null => { + if (firstTwoDigits[0] === VISA_FIRST_DIGIT) { + return VisaCardLogo; + } else if ( + Number(firstTwoDigits) >= MIN_MASTER_FIRST_TWO_DIGITS && + Number(firstTwoDigits) <= MAX_MASTER_FIRST_TWO_DIGITS + ) { + return MasterCardLogo; + } + + return null; +}; + +const CardBrandLogo = ({ firstTwoDigits }: ICardBrandLogoProps) => { + const logoSrc = getCardBrandLogo(firstTwoDigits); + + if (!logoSrc) { + return null; + } + + return ( + + + + ); +}; + +const BrandLogoContainer = styled.div` + width: 50px; + border-radius: 5px; +`; + +const StyledImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +`; + +export default CardBrandLogo; diff --git a/src/components/CardPreview/index.tsx b/src/components/CardPreview/index.tsx index 8783ae9828..c99f9d0474 100644 --- a/src/components/CardPreview/index.tsx +++ b/src/components/CardPreview/index.tsx @@ -1,6 +1,5 @@ import styled from 'styled-components'; -import MasterCard from '../../src/assets/images/mastercard.png'; -import VisaCard from '../../src/assets/images/visa.png'; +import CardBrandLogo from './CardBrandLogo'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; @@ -11,18 +10,13 @@ interface CardPreviewProps { } const CardPreview = ({ cardNumbers, expiryDate, cardholderName }: CardPreviewProps) => { - const isVisa = cardNumbers.first[0] === '4'; - const firstTwoDigits = Number(cardNumbers.first.slice(0, 2)); - const isMaster = firstTwoDigits > 50 && firstTwoDigits < 56; + const firstTwoDigits = cardNumbers.first.slice(0, 2); return ( - - {isVisa && } - {isMaster && } - + @@ -72,17 +66,6 @@ const CardMagnetic = styled.div` border-radius: 5px; `; -const BrandImageWrapper = styled.div` - width: 50px; - border-radius: 5px; -`; - -const StyledImage = styled.img` - width: 100%; - height: 100%; - object-fit: cover; -`; - const BodyWrapper = styled.section` display: flex; flex-direction: column; From 31497e4faef6dc4eee778d6cee3b7ae3c3080050 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 15:26:17 +0900 Subject: [PATCH 50/65] =?UTF-8?q?refactor(CardPreview):=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EC=88=AB=EC=9E=90=EB=A5=BC=20=EB=B3=84=EB=8F=84?= =?UTF-8?q?=EC=9D=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CardPreview/CardNumberContainer.tsx | 42 +++++++++++++++++++ src/components/CardPreview/index.tsx | 24 +++-------- 2 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 src/components/CardPreview/CardNumberContainer.tsx diff --git a/src/components/CardPreview/CardNumberContainer.tsx b/src/components/CardPreview/CardNumberContainer.tsx new file mode 100644 index 0000000000..f6e404a7d4 --- /dev/null +++ b/src/components/CardPreview/CardNumberContainer.tsx @@ -0,0 +1,42 @@ +import styled from 'styled-components'; + +type TCardNumberType = 'normal' | 'password'; + +export interface ICardNumberContainerProps { + data: string; + type?: TCardNumberType; +} + +const PASSWORD_CHAR = '*' as const; +const TYPE = { + normal: 'normal', + password: 'password', +} as const; + +const getDisplayingCardNumber = (cardNumber: string, type: TCardNumberType) => { + switch (type) { + case TYPE.normal: + return cardNumber; + + case TYPE.password: + return PASSWORD_CHAR.repeat(cardNumber.length); + + default: + throw Error('올바른 유형의 카드 번호 타입이 아닙니다.'); + } +}; + +const CardNumber = ({ data = '', type = TYPE.normal }: ICardNumberContainerProps) => { + const displayingCardNumber = getDisplayingCardNumber(data, type); + + return {displayingCardNumber}; +}; + +const CardNumberContainer = styled.p` + display: flex; + flex-basis: 25%; + height: 20px; + font-size: 20px; +`; + +export default CardNumber; diff --git a/src/components/CardPreview/index.tsx b/src/components/CardPreview/index.tsx index c99f9d0474..eea5af0088 100644 --- a/src/components/CardPreview/index.tsx +++ b/src/components/CardPreview/index.tsx @@ -1,5 +1,6 @@ import styled from 'styled-components'; import CardBrandLogo from './CardBrandLogo'; +import CardNumber from './CardNumberContainer'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; @@ -20,15 +21,15 @@ const CardPreview = ({ cardNumbers, expiryDate, cardholderName }: CardPreviewPro - {cardNumbers.first} - {cardNumbers.second} - {'·'.repeat(cardNumbers.third.length)} - {'·'.repeat(cardNumbers.fourth.length)} + + + +

{expiryDate.month}

-

{expiryDate.month && '/'}

+

{(expiryDate.month || expiryDate.year) && '/'}

{expiryDate.year}

@@ -83,19 +84,6 @@ const CardNumbersWrapper = styled.div` gap: 20px; `; -const CardNumberText = styled.p` - display: flex; - flex-basis: 25%; - height: 20px; - font-size: 20px; -`; - -const CardSecretNumber = styled.p` - display: flex; - flex-basis: 25%; - font-size: 28px; -`; - const ExpiryDateWrapper = styled.div` display: flex; align-items: center; From 786ea1a0f1b1a93656f8b636faeaf4586a176699 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 15:26:55 +0900 Subject: [PATCH 51/65] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20css=20import=20=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 1 - src/main.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 490f9f1217..a29030aa79 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,3 @@ -import './App.css'; import styled from 'styled-components'; import CardholderNameContainer from './components/CardholderNameContainer'; diff --git a/src/main.tsx b/src/main.tsx index 6f60c26119..e242d3cc07 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,5 @@ import ReactDOM from 'react-dom/client'; import App from './App.tsx'; -import './index.css'; import { StrictMode } from 'react'; import GlobalStyles from './styles/GlobalStyles.tsx'; import { GlobalLayout } from './styles/common.ts'; From 45b603f290680fb7b6fcf60564e8976163e3c86e Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 15:29:45 +0900 Subject: [PATCH 52/65] =?UTF-8?q?feat(CardExpiryDate,=20CardhokderName):?= =?UTF-8?q?=20placeholder=20=EB=AC=B8=EA=B5=AC=EB=A5=BC=20=EC=98=88?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=85=EB=A0=A5=EC=9C=BC=EB=A1=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardExpiryDateContainer.tsx | 4 ++-- src/components/CardholderNameContainer.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index 995a76b7de..eea5637ff9 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -41,7 +41,7 @@ const CardExpiryDateContainer = ({ value={data.month} onChange={onMonthChange} onBlur={updateMonthErrorStatus} - placeholder="MM" + placeholder="01" maxLength={2} width="48%" /> @@ -51,7 +51,7 @@ const CardExpiryDateContainer = ({ value={data.year} onChange={onYearChange} onBlur={updateYearErrorStatus} - placeholder="YY" + placeholder="24" maxLength={2} width="48%" /> diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index 70a27d15c2..c8e428d38c 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -21,7 +21,7 @@ const CardholderNameContainer = ({ data, setData, errorStatus, updateErrorStatus value={data} onChange={onChange} onBlur={updateErrorStatus} - placeholder="카드 소유자 이름을 입력해주세요" + placeholder="JOHN DOE" width="100%" maxLength={100} /> From ed85991553fd383c0edcd9877eb3dedb2067b6a9 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 15:58:54 +0900 Subject: [PATCH 53/65] =?UTF-8?q?refactor(CardNumbersContainer):=20?= =?UTF-8?q?=EC=83=81=EC=88=98=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardNumbersContainer.tsx | 14 ++++++++++---- src/utils/getObjectKeys.ts | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 src/utils/getObjectKeys.ts diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersContainer.tsx index 9b2fa59213..f10d362368 100644 --- a/src/components/CardNumbersContainer.tsx +++ b/src/components/CardNumbersContainer.tsx @@ -1,6 +1,7 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; import RegistrationLayout from './common/RegistrationLayout'; +import getObjectKeys from '../utils/getObjectKeys'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; @@ -11,13 +12,19 @@ export interface CardNumbersContainerProps { updateErrorStatus: (key: CardNumberKey) => void; } +const PASSWORD_INPUT_KEYS = ['third', 'fourth']; +const INPUT_TYPE = { + text: 'text', + password: 'password', +}; + export default function CardNumberContainer({ data, setData, errorStatus, updateErrorStatus, }: CardNumbersContainerProps) { - const arr = ['first', 'second', 'third', 'fourth'] as const; + const cardNumbersKeys = getObjectKeys(data); const generateOnChange = (key: CardNumberKey) => (e: React.ChangeEvent) => { setData({ ...data, [key]: e.target.value }); }; @@ -30,9 +37,8 @@ export default function CardNumberContainer({ labelText="카드 번호" labelFor="first-card-numbers-input" > - {arr.map(key => { - const PASSWORD_INPUT_KEYS = ['third', 'fourth']; - const type = PASSWORD_INPUT_KEYS.includes(key) ? 'password' : 'text'; + {cardNumbersKeys.map(key => { + const type = PASSWORD_INPUT_KEYS.includes(key) ? INPUT_TYPE.password : INPUT_TYPE.text; return ( (object: T): (keyof T)[] => { + return Object.keys(object) as (keyof T)[]; +}; + +export default getObjectKeys; From dfe9df6b89d8c6f9ac332bdf66b0eabfb64642aa Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 16:09:07 +0900 Subject: [PATCH 54/65] =?UTF-8?q?refactor:=20RegisrationLayout=EC=9D=98=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=EC=9D=84=20InputField=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardExpiryDateContainer.tsx | 6 +++--- src/components/CardNumbersContainer.tsx | 6 +++--- src/components/CardholderNameContainer.tsx | 6 +++--- .../{RegistrationLayout.tsx => InputSection.tsx} | 10 ++-------- 4 files changed, 11 insertions(+), 17 deletions(-) rename src/components/common/{RegistrationLayout.tsx => InputSection.tsx} (83%) diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index eea5637ff9..e02274f800 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -1,6 +1,6 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; -import RegistrationLayout from './common/RegistrationLayout'; +import InputSection from './common/InputSection'; import { IErrorStatus } from '../validators/index.d'; type MM = string; @@ -29,7 +29,7 @@ const CardExpiryDateContainer = ({ return (
- - + {monthErrorStatus.errorMessage} {yearErrorStatus.errorMessage} diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersContainer.tsx index f10d362368..44a934c38f 100644 --- a/src/components/CardNumbersContainer.tsx +++ b/src/components/CardNumbersContainer.tsx @@ -1,6 +1,6 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; -import RegistrationLayout from './common/RegistrationLayout'; +import InputSection from './common/InputSection'; import getObjectKeys from '../utils/getObjectKeys'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; @@ -31,7 +31,7 @@ export default function CardNumberContainer({ return (
- ); })} - + {errorStatus.errorMessage} diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index c8e428d38c..435dbef9db 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -1,6 +1,6 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; -import RegistrationLayout from './common/RegistrationLayout'; +import InputSection from './common/InputSection'; interface CardholderNameContainerProps { data: string; @@ -14,7 +14,7 @@ const CardholderNameContainer = ({ data, setData, errorStatus, updateErrorStatus return (
- + - + {errorStatus.errorMessage} diff --git a/src/components/common/RegistrationLayout.tsx b/src/components/common/InputSection.tsx similarity index 83% rename from src/components/common/RegistrationLayout.tsx rename to src/components/common/InputSection.tsx index 63fb7067af..6eb36aeeb3 100644 --- a/src/components/common/RegistrationLayout.tsx +++ b/src/components/common/InputSection.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import styled from 'styled-components'; -export interface RegistrationLayoutProps { +export interface InputSectionProps { title: string; subtitle?: string; labelText: string; @@ -9,13 +9,7 @@ export interface RegistrationLayoutProps { children: React.ReactNode; } -export default function RegistrationLayout({ - title, - subtitle, - labelText, - labelFor, - children, -}: RegistrationLayoutProps) { +export default function InputSection({ title, subtitle, labelText, labelFor, children }: InputSectionProps) { return ( {title} From 5a26bd92ab1cc92f21c5d497fabf7d6fdeb49221 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 16:12:08 +0900 Subject: [PATCH 55/65] =?UTF-8?q?fix(validateCardNumber):=20=EC=B9=B4?= =?UTF-8?q?=EB=93=9C=20=EB=B2=88=ED=98=B8=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=88=AB=EC=9E=90=204=EC=9E=90?= =?UTF-8?q?=EB=A6=AC=EB=A7=8C=20=ED=86=B5=EA=B3=BC=EC=8B=9C=ED=82=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validators/validateCardNumber.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/validateCardNumber.ts b/src/validators/validateCardNumber.ts index ddc576422f..1105b79d90 100644 --- a/src/validators/validateCardNumber.ts +++ b/src/validators/validateCardNumber.ts @@ -6,7 +6,7 @@ const validateCardNumber = (cardNumber: string): IErrorStatus => { return { isError: true, errorMessage: '카드 번호는 4자리로 입력해주세요' }; } - const isValidCardNumber = /^0{1,4}|[1-9]\d{0,3}$/.test(cardNumber); + const isValidCardNumber = /^\d{4}$/.test(cardNumber); if (!isValidCardNumber) { return { isError: true, errorMessage: '카드 번호는 0000 ~ 9999 사이의 숫자로 입력해주세요' }; } From 7c51312b9c20df233ff5fdfbed4d0d28c2ff4529 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 16:34:39 +0900 Subject: [PATCH 56/65] =?UTF-8?q?refactor:=20=EC=9C=A0=ED=9A=A8=EA=B8=B0?= =?UTF-8?q?=EA=B0=84=20=EC=9B=94(=E6=9C=88)=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=B0=BD=20blur=20=EC=8B=9C,=20=ED=95=9C=20=EC=9E=90=EB=A6=AC?= =?UTF-8?q?=EC=9D=BC=20=EA=B2=BD=EC=9A=B0=20=EB=91=90=20=EC=9E=90=EB=A6=AC?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=90=EB=8F=99=20=EB=B3=80=EA=B2=BD=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20-=20=20useValidation/useValidations=EC=9D=98=20upda?= =?UTF-8?q?teErrorStatus=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=8C=80=EC=83=81=20=EA=B0=92=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B8=EC=9E=90=EB=A1=9C=EB=8F=84=20=EB=B0=9B=EC=9D=84=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardExpiryDateContainer.tsx | 14 +++++++++++--- src/components/CardholderNameContainer.tsx | 2 +- src/hooks/useValidation.ts | 9 ++++++--- src/hooks/useValidations.ts | 4 ++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateContainer.tsx index e02274f800..b454fff475 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateContainer.tsx @@ -6,7 +6,7 @@ import { IErrorStatus } from '../validators/index.d'; type MM = string; type YY = string; -type TErrorStatusUpdater = () => void; +type TErrorStatusUpdater = (value?: string) => void; interface CardExpiryDateContainerProps { data: { month: MM; year: YY }; @@ -27,6 +27,14 @@ const CardExpiryDateContainer = ({ const onMonthChange = (e: React.ChangeEvent) => setMonth(e.target.value); const onYearChange = (e: React.ChangeEvent) => setYear(e.target.value); + const onMonthBlur = (e: React.FocusEvent) => { + const shouldConvertToTwoDigits = e.target.value.length === 1 && e.target.value !== '0'; + const month = shouldConvertToTwoDigits ? `0${e.target.value}` : e.target.value; + + setMonth(month); + updateMonthErrorStatus(month); + }; + return (
updateYearErrorStatus()} placeholder="24" maxLength={2} width="48%" diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameContainer.tsx index 435dbef9db..0a45472443 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameContainer.tsx @@ -20,7 +20,7 @@ const CardholderNameContainer = ({ data, setData, errorStatus, updateErrorStatus isError={errorStatus.isError} value={data} onChange={onChange} - onBlur={updateErrorStatus} + onBlur={() => updateErrorStatus()} placeholder="JOHN DOE" width="100%" maxLength={100} diff --git a/src/hooks/useValidation.ts b/src/hooks/useValidation.ts index 55e93269a6..4ae5a8a27e 100644 --- a/src/hooks/useValidation.ts +++ b/src/hooks/useValidation.ts @@ -9,9 +9,12 @@ const useValidation = (value: string, validate: TValidate) => { isError: false, }); - const updateErrorStatus = useCallback(() => { - setErrorStatus(validate(value)); - }, [value, validate]); + const updateErrorStatus = useCallback( + (targetValue: string = value) => { + setErrorStatus(validate(targetValue)); + }, + [value, validate], + ); return { errorStatus, updateErrorStatus }; }; diff --git a/src/hooks/useValidations.ts b/src/hooks/useValidations.ts index ae5c9d047e..3083a805bb 100644 --- a/src/hooks/useValidations.ts +++ b/src/hooks/useValidations.ts @@ -13,8 +13,8 @@ const useValidations = >(value: T, validate: TV const [errorStatus, setErrorStatus] = useState(initialErrorStatus); const updateErrorStatus = useCallback( - (key: keyof T) => { - const { isError, errorMessage } = validate(value[key]); + (key: keyof T, targetValue = value[key]) => { + const { isError, errorMessage } = validate(targetValue); setErrorStatus({ isError: { ...errorStatus.isError, [key]: isError }, From d9f3bb43e677b3608a68ca1d325c0c038979a719 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 16:38:38 +0900 Subject: [PATCH 57/65] =?UTF-8?q?refactor(App):=20XXXContainer=20=ED=98=95?= =?UTF-8?q?=ED=83=9C=EC=9D=98=20=EB=84=A4=EC=9D=B4=EB=B0=8D=EC=9D=84=20XXX?= =?UTF-8?q?InputContainer=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 12 ++++++------ ...ontainer.tsx => CardExpiryDateInputContainer.tsx} | 8 ++++---- ...rsContainer.tsx => CardNumbersInputContainer.tsx} | 4 ++-- ...ontainer.tsx => CardholderNameInputContainer.tsx} | 11 ++++++++--- 4 files changed, 20 insertions(+), 15 deletions(-) rename src/components/{CardExpiryDateContainer.tsx => CardExpiryDateInputContainer.tsx} (93%) rename src/components/{CardNumbersContainer.tsx => CardNumbersInputContainer.tsx} (95%) rename src/components/{CardholderNameContainer.tsx => CardholderNameInputContainer.tsx} (81%) diff --git a/src/App.tsx b/src/App.tsx index a29030aa79..a3168e5930 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,8 @@ import styled from 'styled-components'; -import CardholderNameContainer from './components/CardholderNameContainer'; -import CardExpiryDateContainer from './components/CardExpiryDateContainer'; -import CardNumbersContainer from './components/CardNumbersContainer'; +import CardholderNameInputContainer from './components/CardholderNameInputContainer'; +import CardExpiryDateInputContainer from './components/CardExpiryDateInputContainer'; +import CardNumbersInputContainer from './components/CardNumbersInputContainer'; import CardPreview from './components/CardPreview'; import useCardInfo from './hooks/useCardInfo'; @@ -14,9 +14,9 @@ const App = () => { - - - + + + ); diff --git a/src/components/CardExpiryDateContainer.tsx b/src/components/CardExpiryDateInputContainer.tsx similarity index 93% rename from src/components/CardExpiryDateContainer.tsx rename to src/components/CardExpiryDateInputContainer.tsx index b454fff475..923a16556a 100644 --- a/src/components/CardExpiryDateContainer.tsx +++ b/src/components/CardExpiryDateInputContainer.tsx @@ -8,7 +8,7 @@ type YY = string; type TErrorStatusUpdater = (value?: string) => void; -interface CardExpiryDateContainerProps { +interface CardExpiryDateInputContainerProps { data: { month: MM; year: YY }; dataSetter: { month: React.Dispatch>; @@ -18,12 +18,12 @@ interface CardExpiryDateContainerProps { errorStatusUpdater: { month: TErrorStatusUpdater; year: TErrorStatusUpdater }; } -const CardExpiryDateContainer = ({ +const CardExpiryDateInputContainer = ({ data, dataSetter: { month: setMonth, year: setYear }, errorStatus: { month: monthErrorStatus, year: yearErrorStatus }, errorStatusUpdater: { month: updateMonthErrorStatus, year: updateYearErrorStatus }, -}: CardExpiryDateContainerProps) => { +}: CardExpiryDateInputContainerProps) => { const onMonthChange = (e: React.ChangeEvent) => setMonth(e.target.value); const onYearChange = (e: React.ChangeEvent) => setYear(e.target.value); @@ -72,4 +72,4 @@ const CardExpiryDateContainer = ({ ); }; -export default CardExpiryDateContainer; +export default CardExpiryDateInputContainer; diff --git a/src/components/CardNumbersContainer.tsx b/src/components/CardNumbersInputContainer.tsx similarity index 95% rename from src/components/CardNumbersContainer.tsx rename to src/components/CardNumbersInputContainer.tsx index 44a934c38f..110bb86b04 100644 --- a/src/components/CardNumbersContainer.tsx +++ b/src/components/CardNumbersInputContainer.tsx @@ -5,7 +5,7 @@ import getObjectKeys from '../utils/getObjectKeys'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; -export interface CardNumbersContainerProps { +export interface CardNumbersInputContainerProps { data: Record; setData: React.Dispatch>>; errorStatus: { isError: Record; errorMessage: string }; @@ -23,7 +23,7 @@ export default function CardNumberContainer({ setData, errorStatus, updateErrorStatus, -}: CardNumbersContainerProps) { +}: CardNumbersInputContainerProps) { const cardNumbersKeys = getObjectKeys(data); const generateOnChange = (key: CardNumberKey) => (e: React.ChangeEvent) => { setData({ ...data, [key]: e.target.value }); diff --git a/src/components/CardholderNameContainer.tsx b/src/components/CardholderNameInputContainer.tsx similarity index 81% rename from src/components/CardholderNameContainer.tsx rename to src/components/CardholderNameInputContainer.tsx index 0a45472443..50035ef926 100644 --- a/src/components/CardholderNameContainer.tsx +++ b/src/components/CardholderNameInputContainer.tsx @@ -2,14 +2,19 @@ import Input from './common/Input'; import { ErrorWrapper, ErrorText } from '../styles/common'; import InputSection from './common/InputSection'; -interface CardholderNameContainerProps { +interface CardholderNameInputContainerProps { data: string; setData: React.Dispatch>; errorStatus: { errorMessage: string; isError: boolean }; updateErrorStatus: () => void; } -const CardholderNameContainer = ({ data, setData, errorStatus, updateErrorStatus }: CardholderNameContainerProps) => { +const CardholderNameInputContainer = ({ + data, + setData, + errorStatus, + updateErrorStatus, +}: CardholderNameInputContainerProps) => { const onChange = (e: React.ChangeEvent) => setData(e.target.value.toUpperCase()); return ( @@ -33,4 +38,4 @@ const CardholderNameContainer = ({ data, setData, errorStatus, updateErrorStatus ); }; -export default CardholderNameContainer; +export default CardholderNameInputContainer; From 97e56874e16c71595c6e5dc0e3f0914f0f8af807 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 16:45:03 +0900 Subject: [PATCH 58/65] =?UTF-8?q?refactor(CardBrandLogo):=20=EA=B0=80?= =?UTF-8?q?=EB=8F=85=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardPreview/CardBrandLogo.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/CardPreview/CardBrandLogo.tsx b/src/components/CardPreview/CardBrandLogo.tsx index 390d39db71..28fc96a762 100644 --- a/src/components/CardPreview/CardBrandLogo.tsx +++ b/src/components/CardPreview/CardBrandLogo.tsx @@ -26,15 +26,15 @@ const getCardBrandLogo = (firstTwoDigits: string): LogoImageSrc | null => { }; const CardBrandLogo = ({ firstTwoDigits }: ICardBrandLogoProps) => { - const logoSrc = getCardBrandLogo(firstTwoDigits); + const matchedLogo = getCardBrandLogo(firstTwoDigits); - if (!logoSrc) { + if (!matchedLogo) { return null; } return ( - + ); }; From 93e73f600b5e013602d4d4bfa90e255aef3fa171 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 16:54:42 +0900 Subject: [PATCH 59/65] =?UTF-8?q?refactor(CardNumberContainer):=20?= =?UTF-8?q?=EB=B3=B8=EB=9E=98=20=EC=9D=98=EB=AF=B8=EC=97=90=20=EB=8D=94=20?= =?UTF-8?q?=EB=B6=80=ED=95=A9=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20typ?= =?UTF-8?q?e=EB=AA=85=20password=EB=A5=BC=20secret=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardPreview/CardNumberContainer.tsx | 6 +++--- src/components/CardPreview/index.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/CardPreview/CardNumberContainer.tsx b/src/components/CardPreview/CardNumberContainer.tsx index f6e404a7d4..780e09d000 100644 --- a/src/components/CardPreview/CardNumberContainer.tsx +++ b/src/components/CardPreview/CardNumberContainer.tsx @@ -1,6 +1,6 @@ import styled from 'styled-components'; -type TCardNumberType = 'normal' | 'password'; +type TCardNumberType = 'normal' | 'secret'; export interface ICardNumberContainerProps { data: string; @@ -10,7 +10,7 @@ export interface ICardNumberContainerProps { const PASSWORD_CHAR = '*' as const; const TYPE = { normal: 'normal', - password: 'password', + secret: 'secret', } as const; const getDisplayingCardNumber = (cardNumber: string, type: TCardNumberType) => { @@ -18,7 +18,7 @@ const getDisplayingCardNumber = (cardNumber: string, type: TCardNumberType) => { case TYPE.normal: return cardNumber; - case TYPE.password: + case TYPE.secret: return PASSWORD_CHAR.repeat(cardNumber.length); default: diff --git a/src/components/CardPreview/index.tsx b/src/components/CardPreview/index.tsx index eea5af0088..5c3f6bd2a5 100644 --- a/src/components/CardPreview/index.tsx +++ b/src/components/CardPreview/index.tsx @@ -23,8 +23,8 @@ const CardPreview = ({ cardNumbers, expiryDate, cardholderName }: CardPreviewPro - - + + From d79f9912dfbde94faddfe8f2773bd5d6c8438e3a Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 17:03:02 +0900 Subject: [PATCH 60/65] =?UTF-8?q?refactor(CardPreview):=20ExpiryDate?= =?UTF-8?q?=EB=A5=BC=20=EB=B3=84=EB=8F=84=EC=9D=98=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CardPreview/ExpiryDate.tsx | 26 +++++++++++++++++++++++ src/components/CardPreview/index.tsx | 16 ++------------ 2 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 src/components/CardPreview/ExpiryDate.tsx diff --git a/src/components/CardPreview/ExpiryDate.tsx b/src/components/CardPreview/ExpiryDate.tsx new file mode 100644 index 0000000000..f449806370 --- /dev/null +++ b/src/components/CardPreview/ExpiryDate.tsx @@ -0,0 +1,26 @@ +import styled from 'styled-components'; + +export interface IExpiryDateProps { + expiryDate: { month: string; year: string }; +} + +const DATE_SEPARATOR = '/'; + +const ExpiryDate = ({ expiryDate: { month, year } }: IExpiryDateProps) => { + return ( + +
{month}
+
{(month || year) && DATE_SEPARATOR}
+
{year}
+
+ ); +}; + +const ExpiryDateContainer = styled.div` + display: flex; + align-items: center; + font-size: 20px; + height: 20px; +`; + +export default ExpiryDate; diff --git a/src/components/CardPreview/index.tsx b/src/components/CardPreview/index.tsx index 5c3f6bd2a5..ee2ab4abc2 100644 --- a/src/components/CardPreview/index.tsx +++ b/src/components/CardPreview/index.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components'; import CardBrandLogo from './CardBrandLogo'; import CardNumber from './CardNumberContainer'; +import ExpiryDate from './ExpiryDate'; type CardNumberKey = 'first' | 'second' | 'third' | 'fourth'; @@ -26,13 +27,7 @@ const CardPreview = ({ cardNumbers, expiryDate, cardholderName }: CardPreviewPro - - -

{expiryDate.month}

-

{(expiryDate.month || expiryDate.year) && '/'}

-

{expiryDate.year}

-
- + {cardholderName} @@ -84,13 +79,6 @@ const CardNumbersWrapper = styled.div` gap: 20px; `; -const ExpiryDateWrapper = styled.div` - display: flex; - align-items: center; - font-size: 20px; - height: 20px; -`; - const CardholderNameWrapper = styled.div` display: flex; align-items: center; From b55be32fe5b4e543a755a80e7b877c2dc500d52b Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 17:07:36 +0900 Subject: [PATCH 61/65] =?UTF-8?q?refactor(Input):=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EB=90=98=EB=8A=94=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A5=BC=20=EB=B3=80=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Input.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index 0b7d5ab85b..3eb55c1acc 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -12,16 +12,17 @@ interface StyledInputProps { $isError: boolean; } +const LIGHT_GREY = '#acacac'; const StyledInput = styled.input` width: ${props => props.width}; padding: 10px 7px; - border: 1.2px solid ${props => (props.$isError ? '#ff3d3d' : '#acacac')}; + border: 1.2px solid ${props => (props.$isError ? '#ff3d3d' : LIGHT_GREY)}; border-radius: 5px; font-size: 15px; &::placeholder { - color: #acacac; + color: ${LIGHT_GREY}; } &:focus { From c6bcc66c83a9153f5aab72caf4a2fd09c6fc95b7 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 17:16:21 +0900 Subject: [PATCH 62/65] =?UTF-8?q?refactor(App):=20=EC=8A=A4=ED=83=80?= =?UTF-8?q?=EC=9D=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20CardInfoWrap?= =?UTF-8?q?per=20->=20CardInfoInputWrapper=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index a3168e5930..c04e744707 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,11 +13,11 @@ const App = () => { return ( - + - + ); }; @@ -29,7 +29,7 @@ const AppLayout = styled.div` padding-top: 60px; `; -const CardInfoWrapper = styled.section` +const CardInfoInputWrapper = styled.section` margin-top: 50px; `; From 78905d98f3108c1f8e6e7d774765f2c1f6be2f4c Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 17:21:31 +0900 Subject: [PATCH 63/65] =?UTF-8?q?refactor(convertKeysIntoObject):=20forEac?= =?UTF-8?q?h=EA=B0=80=20=EC=95=84=EB=8B=8C=20reduce=EB=A5=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EB=8A=94=20=EB=B0=A9=EC=8B=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/convertKeysIntoObject.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/convertKeysIntoObject.ts b/src/utils/convertKeysIntoObject.ts index f33c01887a..295136cbaf 100644 --- a/src/utils/convertKeysIntoObject.ts +++ b/src/utils/convertKeysIntoObject.ts @@ -1,11 +1,11 @@ -const convertKeysIntoObject = (keys: string[], defaultValue: T) => { - const obj: Record = {}; - - keys.forEach(key => { - obj[key] = defaultValue; - }); - - return obj; +const convertKeysIntoObject = (keys: string[], defaultValue: T): Record => { + return keys.reduce( + (resultObj, key) => { + resultObj[key] = defaultValue; + return resultObj; + }, + {} as Record, + ); }; export default convertKeysIntoObject; From 15659de199385b51ccde0bc3a343c7cb95ae1a09 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 21:03:46 +0900 Subject: [PATCH 64/65] =?UTF-8?q?refactor(useValidation,=20useValidations)?= =?UTF-8?q?:=20=EC=B2=AB=20=EB=B2=88=EC=A7=B8=20=20=EC=9D=B8=EC=9E=90=20?= =?UTF-8?q?=EB=AA=85=EC=9D=84=20value=EC=97=90=EC=84=9C=20state=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useValidation.ts | 6 +++--- src/hooks/useValidations.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hooks/useValidation.ts b/src/hooks/useValidation.ts index 4ae5a8a27e..29e88fa0a4 100644 --- a/src/hooks/useValidation.ts +++ b/src/hooks/useValidation.ts @@ -3,17 +3,17 @@ import { IErrorStatus } from '../validators/index.d'; type TValidate = (value: string) => IErrorStatus; -const useValidation = (value: string, validate: TValidate) => { +const useValidation = (state: string, validate: TValidate) => { const [errorStatus, setErrorStatus] = useState({ errorMessage: '', isError: false, }); const updateErrorStatus = useCallback( - (targetValue: string = value) => { + (targetValue: string = state) => { setErrorStatus(validate(targetValue)); }, - [value, validate], + [state, validate], ); return { errorStatus, updateErrorStatus }; diff --git a/src/hooks/useValidations.ts b/src/hooks/useValidations.ts index 3083a805bb..481ce8871c 100644 --- a/src/hooks/useValidations.ts +++ b/src/hooks/useValidations.ts @@ -4,16 +4,16 @@ import convertKeysIntoObject from '../utils/convertKeysIntoObject'; type TValidate = (value: string) => IErrorStatus; -const useValidations = >(value: T, validate: TValidate) => { +const useValidations = >(state: T, validate: TValidate) => { const initialErrorStatus = { - isError: convertKeysIntoObject(Object.keys(value), false), + isError: convertKeysIntoObject(Object.keys(state), false), errorMessage: '', }; const [errorStatus, setErrorStatus] = useState(initialErrorStatus); const updateErrorStatus = useCallback( - (key: keyof T, targetValue = value[key]) => { + (key: keyof T, targetValue = state[key]) => { const { isError, errorMessage } = validate(targetValue); setErrorStatus({ @@ -21,7 +21,7 @@ const useValidations = >(value: T, validate: TV errorMessage, }); }, - [value, validate, errorStatus], + [state, validate, errorStatus], ); return { errorStatus, updateErrorStatus }; From e0b929a79e4a3a36903eb7b178129e84b099cb58 Mon Sep 17 00:00:00 2001 From: hanyoung Date: Sun, 21 Apr 2024 21:33:39 +0900 Subject: [PATCH 65/65] =?UTF-8?q?refactor(validator):=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EA=B4=80=EB=A0=A8=20=EC=83=81?= =?UTF-8?q?=EC=88=98=EB=A5=BC=20=EB=B3=80=EC=88=98=EB=A1=9C=20=EC=84=A0?= =?UTF-8?q?=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validators/validateCardNumber.ts | 4 +++- src/validators/validateCardholderName.ts | 4 +++- src/validators/validateExpiryMonth.ts | 4 +++- src/validators/validateExpiryYear.ts | 8 ++++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/validators/validateCardNumber.ts b/src/validators/validateCardNumber.ts index 1105b79d90..db15dd080a 100644 --- a/src/validators/validateCardNumber.ts +++ b/src/validators/validateCardNumber.ts @@ -1,7 +1,9 @@ import { IErrorStatus } from './index.d'; +const CARD_NUMBER_LENGTH = 4; + const validateCardNumber = (cardNumber: string): IErrorStatus => { - const isValidLength = cardNumber.length === 0 || cardNumber.length === 4; + const isValidLength = cardNumber.length === CARD_NUMBER_LENGTH; if (!isValidLength) { return { isError: true, errorMessage: '카드 번호는 4자리로 입력해주세요' }; } diff --git a/src/validators/validateCardholderName.ts b/src/validators/validateCardholderName.ts index 947f2062cf..9d4592ee15 100644 --- a/src/validators/validateCardholderName.ts +++ b/src/validators/validateCardholderName.ts @@ -1,5 +1,7 @@ import { IErrorStatus } from './index.d'; +const TWO_BLANKS = ' '; + const validateCardholderName = (cardholderName: string): IErrorStatus => { const isEnglish = /^[a-zA-Z ]+$/.test(cardholderName); if (!isEnglish) { @@ -10,7 +12,7 @@ const validateCardholderName = (cardholderName: string): IErrorStatus => { return { isError: true, errorMessage: '양 끝에 공백이 포함될 수 없습니다.' }; } - if (cardholderName.includes(' ')) { + if (cardholderName.includes(TWO_BLANKS)) { return { isError: true, errorMessage: '사이 공백은 최대 한 칸 입력할 수 있습니다.' }; } diff --git a/src/validators/validateExpiryMonth.ts b/src/validators/validateExpiryMonth.ts index f883d61c90..fc024724a2 100644 --- a/src/validators/validateExpiryMonth.ts +++ b/src/validators/validateExpiryMonth.ts @@ -1,7 +1,9 @@ import { IErrorStatus } from './index.d'; +const EXPIRY_MONTH_LENGTH = 2; + const validateExpiryMonth = (expiryMonth: string): IErrorStatus => { - const isValidLength = expiryMonth.length === 0 || expiryMonth.length === 2; + const isValidLength = expiryMonth.length === EXPIRY_MONTH_LENGTH; const isValidMonth = /^(0[1-9]|1[0-2])$/.test(expiryMonth); if (!isValidLength) { diff --git a/src/validators/validateExpiryYear.ts b/src/validators/validateExpiryYear.ts index b46652d063..0340f7afac 100644 --- a/src/validators/validateExpiryYear.ts +++ b/src/validators/validateExpiryYear.ts @@ -1,8 +1,12 @@ import { IErrorStatus } from './index.d'; +const EXPIRY_YEAR_LENGTH = 2; +const MIN_YEAR = 24; +const MAX_YEAR = 40; + const inquireExpiryYear = (expiryYear: string): IErrorStatus => { - const isValidLength = expiryYear.length === 0 || expiryYear.length === 2; - const isValidYear = Number(expiryYear) > 23 && Number(expiryYear) < 41; + const isValidLength = expiryYear.length === EXPIRY_YEAR_LENGTH; + const isValidYear = Number(expiryYear) >= MIN_YEAR && Number(expiryYear) <= MAX_YEAR; if (!isValidLength) { return { isError: true, errorMessage: '년도(年) : 2자리로 입력해주세요' };