From 952613a624b5fd4131c33b720cdacc3c5e2cf135 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 16:03:58 +0900 Subject: [PATCH 01/24] Setting : add jsdom --- package.json | 1 + pnpm-lock.yaml | 393 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 356 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index d3f7e3de..5dde1499 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "eslint": "^9.12.0", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.12", + "jsdom": "^26.0.0", "typescript": "^5.6.3", "vite": "^5.4.9", "vitest": "^2.1.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f1d525d..b1286148 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,9 +51,9 @@ importers: eslint-plugin-react-refresh: specifier: ^0.4.12 version: 0.4.12(eslint@9.12.0) - prettier: - specifier: ^3.3.3 - version: 3.3.3 + jsdom: + specifier: ^26.0.0 + version: 26.0.0 typescript: specifier: ^5.6.3 version: 5.6.3 @@ -62,16 +62,16 @@ importers: version: 5.4.9 vitest: specifier: ^2.1.3 - version: 2.1.3(@vitest/ui@2.1.3) - zustand: - specifier: ^5.0.0 - version: 5.0.0(@types/react@18.3.11)(react@18.3.1) + version: 2.1.3(@vitest/ui@2.1.3)(jsdom@26.0.0) packages: '@adobe/css-tools@4.4.0': resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + '@asamuzakjp/css-color@2.8.2': + resolution: {integrity: sha512-RtWv9jFN2/bLExuZgFFZ0I3pWWeezAHGgrmjqGGWclATl1aDe3yhCUaI0Ilkp6OCk9zX7+FjvDasEX8Q9Rxc5w==} + '@babel/code-frame@7.25.7': resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} engines: {node: '>=6.9.0'} @@ -88,6 +88,34 @@ packages: resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} engines: {node: '>=6.9.0'} + '@csstools/color-helpers@5.0.1': + resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.1': + resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.7': + resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} @@ -603,6 +631,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -636,6 +668,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -690,6 +725,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -700,9 +739,17 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssstyle@4.2.1: + resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + debug@4.3.7: resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} engines: {node: '>=6.0'} @@ -712,6 +759,9 @@ packages: supports-color: optional: true + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -719,6 +769,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -729,6 +783,10 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -844,6 +902,10 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -872,6 +934,22 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -900,6 +978,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -910,6 +991,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@26.0.0: + resolution: {integrity: sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -943,6 +1033,10 @@ packages: loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lru-cache@11.0.2: + resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} + engines: {node: 20 || >=22} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -958,6 +1052,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -984,6 +1086,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1000,6 +1105,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1034,11 +1142,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} - engines: {node: '>=14'} - hasBin: true - pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -1082,9 +1185,19 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -1134,6 +1247,9 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1159,6 +1275,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.71: + resolution: {integrity: sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==} + + tldts@6.1.71: + resolution: {integrity: sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -1167,6 +1290,14 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@5.1.0: + resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -1246,6 +1377,26 @@ packages: jsdom: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} + engines: {node: '>=18'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1260,32 +1411,41 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zustand@5.0.0: - resolution: {integrity: sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==} - engines: {node: '>=12.20.0'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: + bufferutil: optional: true - use-sync-external-store: + utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + snapshots: '@adobe/css-tools@4.4.0': {} + '@asamuzakjp/css-color@2.8.2': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + lru-cache: 11.0.2 + '@babel/code-frame@7.25.7': dependencies: '@babel/highlight': 7.25.7 @@ -1304,6 +1464,26 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@csstools/color-helpers@5.0.1': {} + + '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.1 + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + '@esbuild/aix-ppc64@0.21.5': optional: true @@ -1722,7 +1902,7 @@ snapshots: sirv: 2.0.4 tinyglobby: 0.2.9 tinyrainbow: 1.2.0 - vitest: 2.1.3(@vitest/ui@2.1.3) + vitest: 2.1.3(@vitest/ui@2.1.3)(jsdom@26.0.0) '@vitest/utils@2.1.3': dependencies: @@ -1736,6 +1916,8 @@ snapshots: acorn@8.13.0: {} + agent-base@7.1.3: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -1765,6 +1947,8 @@ snapshots: assertion-error@2.0.1: {} + asynckit@0.4.0: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -1822,6 +2006,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} cross-spawn@7.0.3: @@ -1832,22 +2020,38 @@ snapshots: css.escape@1.5.1: {} + cssstyle@4.2.1: + dependencies: + '@asamuzakjp/css-color': 2.8.2 + rrweb-cssom: 0.8.0 + csstype@3.1.3: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + debug@4.3.7: dependencies: ms: 2.1.3 + decimal.js@10.4.3: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + dequal@2.0.3: {} dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} + entities@4.5.0: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -2001,6 +2205,12 @@ snapshots: flatted@3.3.1: {} + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true @@ -2020,6 +2230,28 @@ snapshots: has-flag@4.0.0: {} + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.0: @@ -2039,6 +2271,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} js-tokens@4.0.0: {} @@ -2047,6 +2281,34 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@26.0.0: + dependencies: + cssstyle: 4.2.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.16 + parse5: 7.2.1 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.1.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -2076,6 +2338,8 @@ snapshots: loupe@3.1.2: {} + lru-cache@11.0.2: {} + lz-string@1.5.0: {} magic-string@0.30.12: @@ -2089,6 +2353,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + min-indent@1.0.1: {} minimatch@3.1.2: @@ -2107,6 +2377,8 @@ snapshots: natural-compare@1.4.0: {} + nwsapi@2.2.16: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -2128,6 +2400,10 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -2150,8 +2426,6 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.3.3: {} - pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -2207,10 +2481,18 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -2251,6 +2533,8 @@ snapshots: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + text-table@0.2.0: {} tinybench@2.9.0: {} @@ -2268,12 +2552,26 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@6.1.71: {} + + tldts@6.1.71: + dependencies: + tldts-core: 6.1.71 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 totalist@3.0.1: {} + tough-cookie@5.1.0: + dependencies: + tldts: 6.1.71 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: typescript: 5.6.3 @@ -2313,7 +2611,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - vitest@2.1.3(@vitest/ui@2.1.3): + vitest@2.1.3(@vitest/ui@2.1.3)(jsdom@26.0.0): dependencies: '@vitest/expect': 2.1.3 '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.9) @@ -2336,6 +2634,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@vitest/ui': 2.1.3(vitest@2.1.3) + jsdom: 26.0.0 transitivePeerDependencies: - less - lightningcss @@ -2347,6 +2646,23 @@ snapshots: - supports-color - terser + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.1.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -2358,9 +2674,10 @@ snapshots: word-wrap@1.2.5: {} - yocto-queue@0.1.0: {} + ws@8.18.0: {} - zustand@5.0.0(@types/react@18.3.11)(react@18.3.1): - optionalDependencies: - '@types/react': 18.3.11 - react: 18.3.1 + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yocto-queue@0.1.0: {} From 302465be0858a0199b0ed1bdb67dd6d7281ace44 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 16:04:33 +0900 Subject: [PATCH 02/24] =?UTF-8?q?Feat=20:=20useProducts=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/App.tsx | 53 +++++++++++++++-------------- src/refactoring/hooks/useProduct.ts | 22 ++++++++++-- 2 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/refactoring/App.tsx b/src/refactoring/App.tsx index 31307173..e64cecb2 100644 --- a/src/refactoring/App.tsx +++ b/src/refactoring/App.tsx @@ -1,46 +1,49 @@ -import { useState } from 'react'; -import { CartPage } from './components/CartPage.tsx'; -import { AdminPage } from './components/AdminPage.tsx'; -import { Coupon, Product } from '../types.ts'; +import { useState } from "react"; +import { CartPage } from "./components/CartPage.tsx"; +import { AdminPage } from "./components/AdminPage.tsx"; +import { Coupon, Product } from "../types.ts"; import { useCoupons, useProducts } from "./hooks"; const initialProducts: Product[] = [ { - id: 'p1', - name: '상품1', + id: "p1", + name: "상품1", price: 10000, stock: 20, - discounts: [{ quantity: 10, rate: 0.1 }, { quantity: 20, rate: 0.2 }] + discounts: [ + { quantity: 10, rate: 0.1 }, + { quantity: 20, rate: 0.2 }, + ], }, { - id: 'p2', - name: '상품2', + id: "p2", + name: "상품2", price: 20000, stock: 20, - discounts: [{ quantity: 10, rate: 0.15 }] + discounts: [{ quantity: 10, rate: 0.15 }], }, { - id: 'p3', - name: '상품3', + id: "p3", + name: "상품3", price: 30000, stock: 20, - discounts: [{ quantity: 10, rate: 0.2 }] - } + discounts: [{ quantity: 10, rate: 0.2 }], + }, ]; const initialCoupons: Coupon[] = [ { - name: '5000원 할인 쿠폰', - code: 'AMOUNT5000', - discountType: 'amount', - discountValue: 5000 + name: "5000원 할인 쿠폰", + code: "AMOUNT5000", + discountType: "amount", + discountValue: 5000, }, { - name: '10% 할인 쿠폰', - code: 'PERCENT10', - discountType: 'percentage', - discountValue: 10 - } + name: "10% 할인 쿠폰", + code: "PERCENT10", + discountType: "percentage", + discountValue: 10, + }, ]; const App = () => { @@ -57,7 +60,7 @@ const App = () => { onClick={() => setIsAdmin(!isAdmin)} className="bg-white text-blue-600 px-4 py-2 rounded hover:bg-blue-100" > - {isAdmin ? '장바구니 페이지로' : '관리자 페이지로'} + {isAdmin ? "장바구니 페이지로" : "관리자 페이지로"} @@ -71,7 +74,7 @@ const App = () => { onCouponAdd={addCoupon} /> ) : ( - + )} diff --git a/src/refactoring/hooks/useProduct.ts b/src/refactoring/hooks/useProduct.ts index 028bacb6..1d5b16ff 100644 --- a/src/refactoring/hooks/useProduct.ts +++ b/src/refactoring/hooks/useProduct.ts @@ -1,6 +1,22 @@ -import { useState } from 'react'; -import { Product } from '../../types.ts'; +import { useState } from "react"; +import { Product } from "../../types.ts"; export const useProducts = (initialProducts: Product[]) => { - return { products: [], updateProduct: () => undefined, addProduct: () => undefined }; + const [products, setProducts] = useState(initialProducts); + + const updateProduct = (updatedProduct: Product) => { + setProducts((prevProducts) => + prevProducts.map((p) => (p.id === updatedProduct.id ? updatedProduct : p)) + ); + }; + + const addProduct = (newProduct: Product) => { + setProducts((prevProducts) => [...prevProducts, newProduct]); + }; + + return { + products, + updateProduct, + addProduct, + }; }; From 5d4a939d492c498c9c2010d1ae88912838cb1272 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 21:17:25 +0900 Subject: [PATCH 03/24] =?UTF-8?q?Feat=20:=20useCoupon=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/hooks/useCoupon.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/refactoring/hooks/useCoupon.ts b/src/refactoring/hooks/useCoupon.ts index dc1b9078..c6add64d 100644 --- a/src/refactoring/hooks/useCoupon.ts +++ b/src/refactoring/hooks/useCoupon.ts @@ -2,5 +2,10 @@ import { Coupon } from "../../types.ts"; import { useState } from "react"; export const useCoupons = (initialCoupons: Coupon[]) => { - return { coupons: [], addCoupon: () => undefined }; + const [coupons, setCoupons] = useState(initialCoupons); + + const addCoupon = (newCoupon: Coupon) => { + setCoupons((prevCoupons) => [...prevCoupons, newCoupon]); + }; + return { coupons, addCoupon }; }; From 92b7d6812328bfc5029931a8cb05f0911c4ced2f Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 21:52:22 +0900 Subject: [PATCH 04/24] =?UTF-8?q?Refactor=20:=20=EC=83=98=ED=94=8C?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/App.tsx | 45 ++------------------------------ src/refactoring/mock/coupons.ts | 16 ++++++++++++ src/refactoring/mock/products.ts | 28 ++++++++++++++++++++ 3 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 src/refactoring/mock/coupons.ts create mode 100644 src/refactoring/mock/products.ts diff --git a/src/refactoring/App.tsx b/src/refactoring/App.tsx index e64cecb2..ce144755 100644 --- a/src/refactoring/App.tsx +++ b/src/refactoring/App.tsx @@ -1,50 +1,9 @@ import { useState } from "react"; import { CartPage } from "./components/CartPage.tsx"; import { AdminPage } from "./components/AdminPage.tsx"; -import { Coupon, Product } from "../types.ts"; import { useCoupons, useProducts } from "./hooks"; - -const initialProducts: Product[] = [ - { - id: "p1", - name: "상품1", - price: 10000, - stock: 20, - discounts: [ - { quantity: 10, rate: 0.1 }, - { quantity: 20, rate: 0.2 }, - ], - }, - { - id: "p2", - name: "상품2", - price: 20000, - stock: 20, - discounts: [{ quantity: 10, rate: 0.15 }], - }, - { - id: "p3", - name: "상품3", - price: 30000, - stock: 20, - discounts: [{ quantity: 10, rate: 0.2 }], - }, -]; - -const initialCoupons: Coupon[] = [ - { - name: "5000원 할인 쿠폰", - code: "AMOUNT5000", - discountType: "amount", - discountValue: 5000, - }, - { - name: "10% 할인 쿠폰", - code: "PERCENT10", - discountType: "percentage", - discountValue: 10, - }, -]; +import { initialProducts } from "./mock/products.ts"; +import { initialCoupons } from "./mock/coupons.ts"; const App = () => { const { products, updateProduct, addProduct } = useProducts(initialProducts); diff --git a/src/refactoring/mock/coupons.ts b/src/refactoring/mock/coupons.ts new file mode 100644 index 00000000..66c60bbe --- /dev/null +++ b/src/refactoring/mock/coupons.ts @@ -0,0 +1,16 @@ +import { Coupon } from "../../types"; + +export const initialCoupons: Coupon[] = [ + { + name: "5000원 할인 쿠폰", + code: "AMOUNT5000", + discountType: "amount", + discountValue: 5000, + }, + { + name: "10% 할인 쿠폰", + code: "PERCENT10", + discountType: "percentage", + discountValue: 10, + }, +]; diff --git a/src/refactoring/mock/products.ts b/src/refactoring/mock/products.ts new file mode 100644 index 00000000..0272d3d0 --- /dev/null +++ b/src/refactoring/mock/products.ts @@ -0,0 +1,28 @@ +import { Product } from "../../types"; + +export const initialProducts: Product[] = [ + { + id: "p1", + name: "상품1", + price: 10000, + stock: 20, + discounts: [ + { quantity: 10, rate: 0.1 }, + { quantity: 20, rate: 0.2 }, + ], + }, + { + id: "p2", + name: "상품2", + price: 20000, + stock: 20, + discounts: [{ quantity: 10, rate: 0.15 }], + }, + { + id: "p3", + name: "상품3", + price: 30000, + stock: 20, + discounts: [{ quantity: 10, rate: 0.2 }], + }, +]; From 0a51356416e6354259f51c9b6a53ec0770a1294f Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 22:29:10 +0900 Subject: [PATCH 05/24] Feat : addToCart --- src/refactoring/components/CartPage.tsx | 81 +++++++++++++++++-------- src/refactoring/hooks/useCart.ts | 24 +++++++- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/src/refactoring/components/CartPage.tsx b/src/refactoring/components/CartPage.tsx index bafe5ecb..6494a014 100644 --- a/src/refactoring/components/CartPage.tsx +++ b/src/refactoring/components/CartPage.tsx @@ -1,4 +1,4 @@ -import { CartItem, Coupon, Product } from '../../types.ts'; +import { CartItem, Coupon, Product } from "../../types.ts"; import { useCart } from "../hooks"; interface Props { @@ -14,7 +14,7 @@ export const CartPage = ({ products, coupons }: Props) => { updateQuantity, applyCoupon, calculateTotal, - selectedCoupon + selectedCoupon, } = useCart(); const getMaxDiscount = (discounts: { quantity: number; rate: number }[]) => { @@ -22,11 +22,12 @@ export const CartPage = ({ products, coupons }: Props) => { }; const getRemainingStock = (product: Product) => { - const cartItem = cart.find(item => item.product.id === product.id); + const cartItem = cart.find((item) => item.product.id === product.id); return product.stock - (cartItem?.quantity || 0); }; - const { totalBeforeDiscount, totalAfterDiscount, totalDiscount } = calculateTotal() + const { totalBeforeDiscount, totalAfterDiscount, totalDiscount } = + calculateTotal(); const getAppliedDiscount = (item: CartItem) => { const { discounts } = item.product; @@ -47,21 +48,33 @@ export const CartPage = ({ products, coupons }: Props) => {

상품 목록

- {products.map(product => { + {products.map((product) => { const remainingStock = getRemainingStock(product); return ( -
+
{product.name} - {product.price.toLocaleString()}원 + + {product.price.toLocaleString()}원 +
- 0 ? 'text-green-600' : 'text-red-600'}`}> + 0 ? "text-green-600" : "text-red-600" + }`} + > 재고: {remainingStock}개 {product.discounts.length > 0 && ( - 최대 {(getMaxDiscount(product.discounts) * 100).toFixed(0)}% 할인 + 최대{" "} + {(getMaxDiscount(product.discounts) * 100).toFixed(0)}% + 할인 )}
@@ -69,7 +82,8 @@ export const CartPage = ({ products, coupons }: Props) => {
    {product.discounts.map((discount, index) => (
  • - {discount.quantity}개 이상: {(discount.rate * 100).toFixed(0)}% 할인 + {discount.quantity}개 이상:{" "} + {(discount.rate * 100).toFixed(0)}% 할인
  • ))}
@@ -78,12 +92,12 @@ export const CartPage = ({ products, coupons }: Props) => { onClick={() => addToCart(product)} className={`w-full px-3 py-1 rounded ${ remainingStock > 0 - ? 'bg-blue-500 text-white hover:bg-blue-600' - : 'bg-gray-300 text-gray-500 cursor-not-allowed' + ? "bg-blue-500 text-white hover:bg-blue-600" + : "bg-gray-300 text-gray-500 cursor-not-allowed" }`} disabled={remainingStock <= 0} > - {remainingStock > 0 ? '장바구니에 추가' : '품절'} + {remainingStock > 0 ? "장바구니에 추가" : "품절"}
); @@ -94,31 +108,38 @@ export const CartPage = ({ products, coupons }: Props) => {

장바구니 내역

- {cart.map(item => { + {cart.map((item) => { const appliedDiscount = getAppliedDiscount(item); return ( -
+
{item.product.name} -
+
- {item.product.price}원 x {item.quantity} + {item.product.price}원 x {item.quantity} {appliedDiscount > 0 && ( - ({(appliedDiscount * 100).toFixed(0)}% 할인 적용) - + ({(appliedDiscount * 100).toFixed(0)}% 할인 적용) + )} - +
@@ -160,7 +187,9 @@ export const CartPage = ({ products, coupons }: Props) => {

주문 요약

상품 금액: {totalBeforeDiscount.toLocaleString()}원

-

할인 금액: {totalDiscount.toLocaleString()}원

+

+ 할인 금액: {totalDiscount.toLocaleString()}원 +

최종 결제 금액: {totalAfterDiscount.toLocaleString()}원

diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index 67bf9208..f8d4cd1f 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -7,7 +7,29 @@ export const useCart = () => { const [cart, setCart] = useState([]); const [selectedCoupon, setSelectedCoupon] = useState(null); - const addToCart = (product: Product) => {}; + const getRemainingStock = (product: Product) => { + const cartItem = cart.find((item) => item.product.id === product.id); + return product.stock - (cartItem?.quantity || 0); + }; + + const addToCart = (product: Product) => { + const remainingStock = getRemainingStock(product); + + if (remainingStock <= 0) return; + setCart((prevCart) => { + const existingItem = prevCart.find( + (item) => item.product.id === product.id + ); + if (existingItem) { + return prevCart.map((item) => + item.product.id === product.id + ? { ...item, quantity: Math.min(item.quantity + 1, product.stock) } + : item + ); + } + return [...prevCart, { product, quantity: 1 }]; + }); + }; const removeFromCart = (productId: string) => {}; From 09de9321139853fbc44013ef58aa1b42bc2c7872 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 22:30:23 +0900 Subject: [PATCH 06/24] Feat : removeFromCart --- src/refactoring/hooks/useCart.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index f8d4cd1f..0eae4571 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -31,7 +31,11 @@ export const useCart = () => { }); }; - const removeFromCart = (productId: string) => {}; + const removeFromCart = (productId: string) => { + setCart((prevCart) => + prevCart.filter((item) => item.product.id !== productId) + ); + }; const updateQuantity = (productId: string, newQuantity: number) => {}; From bd71225d9072d552f1a893eaf209167d9cd99ecb Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 22:34:26 +0900 Subject: [PATCH 07/24] Feat : updateQuantity --- src/refactoring/hooks/useCart.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index 0eae4571..cd339b74 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -37,7 +37,25 @@ export const useCart = () => { ); }; - const updateQuantity = (productId: string, newQuantity: number) => {}; + const updateQuantity = (productId: string, newQuantity: number) => { + setCart((prevCart) => + prevCart + .map((item) => { + if (item.product.id === productId) { + const maxQuantity = item.product.stock; + const updatedQuantity = Math.max( + 0, + Math.min(newQuantity, maxQuantity) + ); + return updatedQuantity > 0 + ? { ...item, quantity: updatedQuantity } + : null; + } + return item; + }) + .filter((item): item is CartItem => item !== null) + ); + }; const applyCoupon = (coupon: Coupon) => {}; From dea6e38e0fe4928db76689c401fcb67f132222ca Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Sun, 12 Jan 2025 23:17:32 +0900 Subject: [PATCH 08/24] =?UTF-8?q?Refactor=20:=20updateCartItemQuantity?= =?UTF-8?q?=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/refactoring/hooks/useCart.ts | 73 ++++++++++++++++++++++---------- src/refactoring/models/cart.ts | 15 ++++++- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index cd339b74..d651345d 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -38,32 +38,59 @@ export const useCart = () => { }; const updateQuantity = (productId: string, newQuantity: number) => { - setCart((prevCart) => - prevCart - .map((item) => { - if (item.product.id === productId) { - const maxQuantity = item.product.stock; - const updatedQuantity = Math.max( - 0, - Math.min(newQuantity, maxQuantity) - ); - return updatedQuantity > 0 - ? { ...item, quantity: updatedQuantity } - : null; - } - return item; - }) - .filter((item): item is CartItem => item !== null) - ); + setCart((prevCart) => { + const updatedCart = updateCartItemQuantity( + prevCart, + productId, + newQuantity + ); + return updatedCart; + }); + }; + + const applyCoupon = (coupon: Coupon) => { + setSelectedCoupon(coupon); }; - const applyCoupon = (coupon: Coupon) => {}; + const calculateTotal = () => { + let totalBeforeDiscount = 0; + let totalAfterDiscount = 0; + + cart.forEach((item) => { + const { price } = item.product; + const { quantity } = item; + totalBeforeDiscount += price * quantity; - const calculateTotal = () => ({ - totalBeforeDiscount: 0, - totalAfterDiscount: 0, - totalDiscount: 0, - }); + const discount = item.product.discounts.reduce((maxDiscount, d) => { + return quantity >= d.quantity && d.rate > maxDiscount + ? d.rate + : maxDiscount; + }, 0); + + totalAfterDiscount += price * quantity * (1 - discount); + }); + + let totalDiscount = totalBeforeDiscount - totalAfterDiscount; + + // 쿠폰 적용 + if (selectedCoupon) { + if (selectedCoupon.discountType === "amount") { + totalAfterDiscount = Math.max( + 0, + totalAfterDiscount - selectedCoupon.discountValue + ); + } else { + totalAfterDiscount *= 1 - selectedCoupon.discountValue / 100; + } + totalDiscount = totalBeforeDiscount - totalAfterDiscount; + } + + return { + totalBeforeDiscount: Math.round(totalBeforeDiscount), + totalAfterDiscount: Math.round(totalAfterDiscount), + totalDiscount: Math.round(totalDiscount), + }; + }; return { cart, diff --git a/src/refactoring/models/cart.ts b/src/refactoring/models/cart.ts index d8f4c2ea..25a64b9e 100644 --- a/src/refactoring/models/cart.ts +++ b/src/refactoring/models/cart.ts @@ -24,5 +24,18 @@ export const updateCartItemQuantity = ( productId: string, newQuantity: number ): CartItem[] => { - return []; + const result = cart + .map((item) => { + if (item.product.id === productId) { + const maxQuantity = item.product.stock; + const updatedQuantity = Math.max(0, Math.min(newQuantity, maxQuantity)); + return updatedQuantity > 0 + ? { ...item, quantity: updatedQuantity } + : null; + } + return item; + }) + .filter((item): item is CartItem => item !== null); + + return result; }; From 7ff20e0cd0ab012f93a84580f550c56132824180 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Mon, 13 Jan 2025 11:21:26 +0900 Subject: [PATCH 09/24] =?UTF-8?q?Refactor=20:=20calculateCart=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/hooks/useCart.ts | 38 +------------------------------- src/refactoring/models/cart.ts | 37 ++++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index d651345d..8e5e0fad 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -53,43 +53,7 @@ export const useCart = () => { }; const calculateTotal = () => { - let totalBeforeDiscount = 0; - let totalAfterDiscount = 0; - - cart.forEach((item) => { - const { price } = item.product; - const { quantity } = item; - totalBeforeDiscount += price * quantity; - - const discount = item.product.discounts.reduce((maxDiscount, d) => { - return quantity >= d.quantity && d.rate > maxDiscount - ? d.rate - : maxDiscount; - }, 0); - - totalAfterDiscount += price * quantity * (1 - discount); - }); - - let totalDiscount = totalBeforeDiscount - totalAfterDiscount; - - // 쿠폰 적용 - if (selectedCoupon) { - if (selectedCoupon.discountType === "amount") { - totalAfterDiscount = Math.max( - 0, - totalAfterDiscount - selectedCoupon.discountValue - ); - } else { - totalAfterDiscount *= 1 - selectedCoupon.discountValue / 100; - } - totalDiscount = totalBeforeDiscount - totalAfterDiscount; - } - - return { - totalBeforeDiscount: Math.round(totalBeforeDiscount), - totalAfterDiscount: Math.round(totalAfterDiscount), - totalDiscount: Math.round(totalDiscount), - }; + return calculateCartTotal(cart, selectedCoupon); }; return { diff --git a/src/refactoring/models/cart.ts b/src/refactoring/models/cart.ts index 25a64b9e..02d06abb 100644 --- a/src/refactoring/models/cart.ts +++ b/src/refactoring/models/cart.ts @@ -12,10 +12,41 @@ export const calculateCartTotal = ( cart: CartItem[], selectedCoupon: Coupon | null ) => { + let totalBeforeDiscount = 0; + let totalAfterDiscount = 0; + + cart.forEach((item) => { + const { price } = item.product; + const { quantity } = item; + totalBeforeDiscount += price * quantity; + + const discount = item.product.discounts.reduce((maxDiscount, d) => { + return quantity >= d.quantity && d.rate > maxDiscount + ? d.rate + : maxDiscount; + }, 0); + + totalAfterDiscount += price * quantity * (1 - discount); + }); + + let totalDiscount = totalBeforeDiscount - totalAfterDiscount; + + // 쿠폰 적용 + if (selectedCoupon) { + if (selectedCoupon.discountType === "amount") { + totalAfterDiscount = Math.max( + 0, + totalAfterDiscount - selectedCoupon.discountValue + ); + } else { + totalAfterDiscount *= 1 - selectedCoupon.discountValue / 100; + } + totalDiscount = totalBeforeDiscount - totalAfterDiscount; + } return { - totalBeforeDiscount: 0, - totalAfterDiscount: 0, - totalDiscount: 0, + totalBeforeDiscount: Math.round(totalBeforeDiscount), + totalAfterDiscount: Math.round(totalAfterDiscount), + totalDiscount: Math.round(totalDiscount), }; }; From 4827ffb13e1a5a411f37d628d3f8f3a495fa4a46 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Mon, 13 Jan 2025 12:10:11 +0900 Subject: [PATCH 10/24] =?UTF-8?q?Refactor=20:=20calculateCartTotal=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/models/cart.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/refactoring/models/cart.ts b/src/refactoring/models/cart.ts index 02d06abb..c1c1c7d4 100644 --- a/src/refactoring/models/cart.ts +++ b/src/refactoring/models/cart.ts @@ -1,11 +1,22 @@ import { CartItem, Coupon } from "../../types"; export const calculateItemTotal = (item: CartItem) => { - return 0; + const { price } = item.product; + const { quantity } = item; + const maxDiscount = getMaxApplicableDiscount(item); + + const totalBeforeDiscount = price * quantity; + const totalAfterDiscount = totalBeforeDiscount * (1 - maxDiscount); + + return totalAfterDiscount; }; export const getMaxApplicableDiscount = (item: CartItem) => { - return 0; + return item.product.discounts.reduce((maxDiscount, discount) => { + return item.quantity >= discount.quantity && discount.rate > maxDiscount + ? discount.rate + : maxDiscount; + }, 0); }; export const calculateCartTotal = ( @@ -19,14 +30,7 @@ export const calculateCartTotal = ( const { price } = item.product; const { quantity } = item; totalBeforeDiscount += price * quantity; - - const discount = item.product.discounts.reduce((maxDiscount, d) => { - return quantity >= d.quantity && d.rate > maxDiscount - ? d.rate - : maxDiscount; - }, 0); - - totalAfterDiscount += price * quantity * (1 - discount); + totalAfterDiscount += calculateItemTotal(item); }); let totalDiscount = totalBeforeDiscount - totalAfterDiscount; From 2506007074c1fcd3e217a5afa4a0de6db7cac981 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Tue, 14 Jan 2025 20:31:44 +0900 Subject: [PATCH 11/24] Feat : add localstorage --- src/refactoring/hooks/index.ts | 1 + src/refactoring/hooks/useCart.ts | 3 +++ src/refactoring/hooks/useLocalStorage.ts | 33 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/refactoring/hooks/useLocalStorage.ts diff --git a/src/refactoring/hooks/index.ts b/src/refactoring/hooks/index.ts index 0a396931..f3155b05 100644 --- a/src/refactoring/hooks/index.ts +++ b/src/refactoring/hooks/index.ts @@ -1,3 +1,4 @@ export * from "./useCart.ts"; export * from "./useCoupon.ts"; export * from "./useProduct.ts"; +export * from "./useLocalStorage.ts"; diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index 8e5e0fad..1408d10f 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -2,10 +2,12 @@ import { useState } from "react"; import { CartItem, Coupon, Product } from "../../types"; import { calculateCartTotal, updateCartItemQuantity } from "../models/cart"; +import { useLocalStorage } from "./useLocalStorage"; export const useCart = () => { const [cart, setCart] = useState([]); const [selectedCoupon, setSelectedCoupon] = useState(null); + const { addLocalStorage } = useLocalStorage(); const getRemainingStock = (product: Product) => { const cartItem = cart.find((item) => item.product.id === product.id); @@ -27,6 +29,7 @@ export const useCart = () => { : item ); } + addLocalStorage([...prevCart, { product, quantity: 1 }]); return [...prevCart, { product, quantity: 1 }]; }); }; diff --git a/src/refactoring/hooks/useLocalStorage.ts b/src/refactoring/hooks/useLocalStorage.ts new file mode 100644 index 00000000..c8c32542 --- /dev/null +++ b/src/refactoring/hooks/useLocalStorage.ts @@ -0,0 +1,33 @@ +/** + * @todo setLocalStorage의 방식 + * 1. addtocart -> 카트 추가할 때 로컬스트리지에 값을 추가한다 . + * 2. remove -> 카트 삭제될 때 로컬스토리지에 값을 제거한다. + * 3. update -> 카트 내부의 수량을 조절한다. + */ + +import { CartItem } from "../../types"; + +/** + * @description 로컬스토리지에서 프로덕트 데이터를 주고받는 커스텀 훅 + * @param cart + * @returns getLocalStorage / setLocalStorage + */ +export const useLocalStorage = () => { + const getLocalStorage = () => { + window.localStorage.getItem("productList"); + return 123; + }; + + const addLocalStorage = (cart: CartItem[]) => { + window.localStorage.setItem("cart", JSON.stringify(cart)); + }; + + const updateLocalStorage = () => { + return 1; + }; + return { + getLocalStorage, + addLocalStorage, + updateLocalStorage, + }; +}; From 436a5909bd6898fc86a048943ccac07610b06253 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Thu, 16 Jan 2025 22:44:06 +0900 Subject: [PATCH 12/24] Fix : ci --- .github/{workflows_ => workflows}/ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflows_ => workflows}/ci.yml (100%) diff --git a/.github/workflows_/ci.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows_/ci.yml rename to .github/workflows/ci.yml From 174654cfcbe39a5421c2f94f5741aadfe487bf20 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Thu, 16 Jan 2025 22:48:35 +0900 Subject: [PATCH 13/24] =?UTF-8?q?WIP=20:=20useLocalStorage=EB=A5=BC=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=B4=EC=95=BC=20=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/advanced/__tests__/advanced.test.tsx | 297 +++++++++++++---------- src/origin/components/AdminPage.tsx | 195 +++++++++++---- src/refactoring/components/CartPage.tsx | 2 +- src/refactoring/hooks/useCart.ts | 4 +- src/refactoring/hooks/useLocalStorage.ts | 24 +- 5 files changed, 334 insertions(+), 188 deletions(-) diff --git a/src/advanced/__tests__/advanced.test.tsx b/src/advanced/__tests__/advanced.test.tsx index ef8e6ae0..c0ed8dc0 100644 --- a/src/advanced/__tests__/advanced.test.tsx +++ b/src/advanced/__tests__/advanced.test.tsx @@ -1,65 +1,64 @@ import { useState } from "react"; -import { describe, expect, test } from 'vitest'; -import { act, fireEvent, render, screen, within } from '@testing-library/react'; -import { CartPage } from '../../refactoring/components/CartPage'; +import { describe, expect, test } from "vitest"; +import { act, fireEvent, render, screen, within } from "@testing-library/react"; +import { CartPage } from "../../refactoring/components/CartPage"; import { AdminPage } from "../../refactoring/components/AdminPage"; -import { Coupon, Product } from '../../types'; +import { Coupon, Product } from "../../types"; const mockProducts: Product[] = [ { - id: 'p1', - name: '상품1', + id: "p1", + name: "상품1", price: 10000, stock: 20, - discounts: [{ quantity: 10, rate: 0.1 }] + discounts: [{ quantity: 10, rate: 0.1 }], }, { - id: 'p2', - name: '상품2', + id: "p2", + name: "상품2", price: 20000, stock: 20, - discounts: [{ quantity: 10, rate: 0.15 }] + discounts: [{ quantity: 10, rate: 0.15 }], }, { - id: 'p3', - name: '상품3', + id: "p3", + name: "상품3", price: 30000, stock: 20, - discounts: [{ quantity: 10, rate: 0.2 }] - } + discounts: [{ quantity: 10, rate: 0.2 }], + }, ]; const mockCoupons: Coupon[] = [ { - name: '5000원 할인 쿠폰', - code: 'AMOUNT5000', - discountType: 'amount', - discountValue: 5000 + name: "5000원 할인 쿠폰", + code: "AMOUNT5000", + discountType: "amount", + discountValue: 5000, }, { - name: '10% 할인 쿠폰', - code: 'PERCENT10', - discountType: 'percentage', - discountValue: 10 - } + name: "10% 할인 쿠폰", + code: "PERCENT10", + discountType: "percentage", + discountValue: 10, + }, ]; const TestAdminPage = () => { const [products, setProducts] = useState(mockProducts); const [coupons, setCoupons] = useState(mockCoupons); - const handleProductUpdate = (updatedProduct: Product) => { - setProducts(prevProducts => - prevProducts.map(p => p.id === updatedProduct.id ? updatedProduct : p) + setProducts((prevProducts) => + prevProducts.map((p) => (p.id === updatedProduct.id ? updatedProduct : p)) ); }; const handleProductAdd = (newProduct: Product) => { - setProducts(prevProducts => [...prevProducts, newProduct]); + setProducts((prevProducts) => [...prevProducts, newProduct]); }; const handleCouponAdd = (newCoupon: Coupon) => { - setCoupons(prevCoupons => [...prevCoupons, newCoupon]); + setCoupons((prevCoupons) => [...prevCoupons, newCoupon]); }; return ( @@ -73,42 +72,41 @@ const TestAdminPage = () => { ); }; -describe('advanced > ', () => { - - describe('시나리오 테스트 > ', () => { - - test('장바구니 페이지 테스트 > ', async () => { - - render(); - const product1 = screen.getByTestId('product-p1'); - const product2 = screen.getByTestId('product-p2'); - const product3 = screen.getByTestId('product-p3'); - const addToCartButtonsAtProduct1 = within(product1).getByText('장바구니에 추가'); - const addToCartButtonsAtProduct2 = within(product2).getByText('장바구니에 추가'); - const addToCartButtonsAtProduct3 = within(product3).getByText('장바구니에 추가'); +describe("advanced > ", () => { + describe("시나리오 테스트 > ", () => { + test("장바구니 페이지 테스트 > ", async () => { + render(); + const product1 = screen.getByTestId("product-p1"); + const product2 = screen.getByTestId("product-p2"); + const product3 = screen.getByTestId("product-p3"); + const addToCartButtonsAtProduct1 = + within(product1).getByText("장바구니에 추가"); + const addToCartButtonsAtProduct2 = + within(product2).getByText("장바구니에 추가"); + const addToCartButtonsAtProduct3 = + within(product3).getByText("장바구니에 추가"); // 1. 상품 정보 표시 - expect(product1).toHaveTextContent('상품1'); - expect(product1).toHaveTextContent('10,000원'); - expect(product1).toHaveTextContent('재고: 20개'); - expect(product2).toHaveTextContent('상품2'); - expect(product2).toHaveTextContent('20,000원'); - expect(product2).toHaveTextContent('재고: 20개'); - expect(product3).toHaveTextContent('상품3'); - expect(product3).toHaveTextContent('30,000원'); - expect(product3).toHaveTextContent('재고: 20개'); - + expect(product1).toHaveTextContent("상품1"); + expect(product1).toHaveTextContent("10,000원"); + expect(product1).toHaveTextContent("재고: 20개"); + expect(product2).toHaveTextContent("상품2"); + expect(product2).toHaveTextContent("20,000원"); + expect(product2).toHaveTextContent("재고: 20개"); + expect(product3).toHaveTextContent("상품3"); + expect(product3).toHaveTextContent("30,000원"); + expect(product3).toHaveTextContent("재고: 20개"); // 2. 할인 정보 표시 - expect(screen.getByText('10개 이상: 10% 할인')).toBeInTheDocument(); + expect(screen.getByText("10개 이상: 10% 할인")).toBeInTheDocument(); // 3. 상품1 장바구니에 상품 추가 fireEvent.click(addToCartButtonsAtProduct1); // 상품1 추가 // 4. 할인율 계산 - expect(screen.getByText('상품 금액: 10,000원')).toBeInTheDocument(); - expect(screen.getByText('할인 금액: 0원')).toBeInTheDocument(); - expect(screen.getByText('최종 결제 금액: 10,000원')).toBeInTheDocument(); + expect(screen.getByText("상품 금액: 10,000원")).toBeInTheDocument(); + expect(screen.getByText("할인 금액: 0원")).toBeInTheDocument(); + expect(screen.getByText("최종 결제 금액: 10,000원")).toBeInTheDocument(); // 5. 상품 품절 상태로 만들기 for (let i = 0; i < 19; i++) { @@ -116,129 +114,160 @@ describe('advanced > ', () => { } // 6. 품절일 때 상품 추가 안 되는지 확인하기 - expect(product1).toHaveTextContent('재고: 0개'); + expect(product1).toHaveTextContent("재고: 0개"); fireEvent.click(addToCartButtonsAtProduct1); - expect(product1).toHaveTextContent('재고: 0개'); + expect(product1).toHaveTextContent("재고: 0개"); // 7. 할인율 계산 - expect(screen.getByText('상품 금액: 200,000원')).toBeInTheDocument(); - expect(screen.getByText('할인 금액: 20,000원')).toBeInTheDocument(); - expect(screen.getByText('최종 결제 금액: 180,000원')).toBeInTheDocument(); + expect(screen.getByText("상품 금액: 200,000원")).toBeInTheDocument(); + expect(screen.getByText("할인 금액: 20,000원")).toBeInTheDocument(); + expect(screen.getByText("최종 결제 금액: 180,000원")).toBeInTheDocument(); // 8. 상품을 각각 10개씩 추가하기 fireEvent.click(addToCartButtonsAtProduct2); // 상품2 추가 fireEvent.click(addToCartButtonsAtProduct3); // 상품3 추가 - const increaseButtons = screen.getAllByText('+'); + const increaseButtons = screen.getAllByText("+"); for (let i = 0; i < 9; i++) { fireEvent.click(increaseButtons[1]); // 상품2 fireEvent.click(increaseButtons[2]); // 상품3 } // 9. 할인율 계산 - expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument(); - expect(screen.getByText('할인 금액: 110,000원')).toBeInTheDocument(); - expect(screen.getByText('최종 결제 금액: 590,000원')).toBeInTheDocument(); + expect(screen.getByText("상품 금액: 700,000원")).toBeInTheDocument(); + expect(screen.getByText("할인 금액: 110,000원")).toBeInTheDocument(); + expect(screen.getByText("최종 결제 금액: 590,000원")).toBeInTheDocument(); // 10. 쿠폰 적용하기 - const couponSelect = screen.getByRole('combobox'); - fireEvent.change(couponSelect, { target: { value: '1' } }); // 10% 할인 쿠폰 선택 + const couponSelect = screen.getByRole("combobox"); + fireEvent.change(couponSelect, { target: { value: "1" } }); // 10% 할인 쿠폰 선택 // 11. 할인율 계산 - expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument(); - expect(screen.getByText('할인 금액: 169,000원')).toBeInTheDocument(); - expect(screen.getByText('최종 결제 금액: 531,000원')).toBeInTheDocument(); + expect(screen.getByText("상품 금액: 700,000원")).toBeInTheDocument(); + expect(screen.getByText("할인 금액: 169,000원")).toBeInTheDocument(); + expect(screen.getByText("최종 결제 금액: 531,000원")).toBeInTheDocument(); // 12. 다른 할인 쿠폰 적용하기 - fireEvent.change(couponSelect, { target: { value: '0' } }); // 5000원 할인 쿠폰 - expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument(); - expect(screen.getByText('할인 금액: 115,000원')).toBeInTheDocument(); - expect(screen.getByText('최종 결제 금액: 585,000원')).toBeInTheDocument(); + fireEvent.change(couponSelect, { target: { value: "0" } }); // 5000원 할인 쿠폰 + expect(screen.getByText("상품 금액: 700,000원")).toBeInTheDocument(); + expect(screen.getByText("할인 금액: 115,000원")).toBeInTheDocument(); + expect(screen.getByText("최종 결제 금액: 585,000원")).toBeInTheDocument(); }); - test('관리자 페이지 테스트 > ', async () => { - render(); - + test("관리자 페이지 테스트 > ", async () => { + render(); - const $product1 = screen.getByTestId('product-1'); + const $product1 = screen.getByTestId("product-1"); // 1. 새로운 상품 추가 - fireEvent.click(screen.getByText('새 상품 추가')); + fireEvent.click(screen.getByText("새 상품 추가")); - fireEvent.change(screen.getByLabelText('상품명'), { target: { value: '상품4' } }); - fireEvent.change(screen.getByLabelText('가격'), { target: { value: '15000' } }); - fireEvent.change(screen.getByLabelText('재고'), { target: { value: '30' } }); + fireEvent.change(screen.getByLabelText("상품명"), { + target: { value: "상품4" }, + }); + fireEvent.change(screen.getByLabelText("가격"), { + target: { value: "15000" }, + }); + fireEvent.change(screen.getByLabelText("재고"), { + target: { value: "30" }, + }); - fireEvent.click(screen.getByText('추가')); + fireEvent.click(screen.getByText("추가")); - const $product4 = screen.getByTestId('product-4'); + const $product4 = screen.getByTestId("product-4"); - expect($product4).toHaveTextContent('상품4'); - expect($product4).toHaveTextContent('15000원'); - expect($product4).toHaveTextContent('재고: 30'); + expect($product4).toHaveTextContent("상품4"); + expect($product4).toHaveTextContent("15000원"); + expect($product4).toHaveTextContent("재고: 30"); // 2. 상품 선택 및 수정 fireEvent.click($product1); - fireEvent.click(within($product1).getByTestId('toggle-button')); - fireEvent.click(within($product1).getByTestId('modify-button')); - + fireEvent.click(within($product1).getByTestId("toggle-button")); + fireEvent.click(within($product1).getByTestId("modify-button")); act(() => { - fireEvent.change(within($product1).getByDisplayValue('20'), { target: { value: '25' } }); - fireEvent.change(within($product1).getByDisplayValue('10000'), { target: { value: '12000' } }); - fireEvent.change(within($product1).getByDisplayValue('상품1'), { target: { value: '수정된 상품1' } }); - }) - - fireEvent.click(within($product1).getByText('수정 완료')); - - expect($product1).toHaveTextContent('수정된 상품1'); - expect($product1).toHaveTextContent('12000원'); - expect($product1).toHaveTextContent('재고: 25'); + fireEvent.change(within($product1).getByDisplayValue("20"), { + target: { value: "25" }, + }); + fireEvent.change(within($product1).getByDisplayValue("10000"), { + target: { value: "12000" }, + }); + fireEvent.change(within($product1).getByDisplayValue("상품1"), { + target: { value: "수정된 상품1" }, + }); + }); + + fireEvent.click(within($product1).getByText("수정 완료")); + + expect($product1).toHaveTextContent("수정된 상품1"); + expect($product1).toHaveTextContent("12000원"); + expect($product1).toHaveTextContent("재고: 25"); // 3. 상품 할인율 추가 및 삭제 fireEvent.click($product1); - fireEvent.click(within($product1).getByTestId('modify-button')); + fireEvent.click(within($product1).getByTestId("modify-button")); // 할인 추가 act(() => { - fireEvent.change(screen.getByPlaceholderText('수량'), { target: { value: '5' } }); - fireEvent.change(screen.getByPlaceholderText('할인율 (%)'), { target: { value: '5' } }); - }) - fireEvent.click(screen.getByText('할인 추가')); - - expect(screen.queryByText('5개 이상 구매 시 5% 할인')).toBeInTheDocument(); + fireEvent.change(screen.getByPlaceholderText("수량"), { + target: { value: "5" }, + }); + fireEvent.change(screen.getByPlaceholderText("할인율 (%)"), { + target: { value: "5" }, + }); + }); + fireEvent.click(screen.getByText("할인 추가")); + + expect( + screen.queryByText("5개 이상 구매 시 5% 할인") + ).toBeInTheDocument(); // 할인 삭제 - fireEvent.click(screen.getAllByText('삭제')[0]); - expect(screen.queryByText('10개 이상 구매 시 10% 할인')).not.toBeInTheDocument(); - expect(screen.queryByText('5개 이상 구매 시 5% 할인')).toBeInTheDocument(); - - fireEvent.click(screen.getAllByText('삭제')[0]); - expect(screen.queryByText('10개 이상 구매 시 10% 할인')).not.toBeInTheDocument(); - expect(screen.queryByText('5개 이상 구매 시 5% 할인')).not.toBeInTheDocument(); + fireEvent.click(screen.getAllByText("삭제")[0]); + expect( + screen.queryByText("10개 이상 구매 시 10% 할인") + ).not.toBeInTheDocument(); + expect( + screen.queryByText("5개 이상 구매 시 5% 할인") + ).toBeInTheDocument(); + + fireEvent.click(screen.getAllByText("삭제")[0]); + expect( + screen.queryByText("10개 이상 구매 시 10% 할인") + ).not.toBeInTheDocument(); + expect( + screen.queryByText("5개 이상 구매 시 5% 할인") + ).not.toBeInTheDocument(); // 4. 쿠폰 추가 - fireEvent.change(screen.getByPlaceholderText('쿠폰 이름'), { target: { value: '새 쿠폰' } }); - fireEvent.change(screen.getByPlaceholderText('쿠폰 코드'), { target: { value: 'NEW10' } }); - fireEvent.change(screen.getByRole('combobox'), { target: { value: 'percentage' } }); - fireEvent.change(screen.getByPlaceholderText('할인 값'), { target: { value: '10' } }); - - fireEvent.click(screen.getByText('쿠폰 추가')); - - const $newCoupon = screen.getByTestId('coupon-3'); - - expect($newCoupon).toHaveTextContent('새 쿠폰 (NEW10):10% 할인'); - }) - }) - - describe('자유롭게 작성해보세요.', () => { - test('새로운 유틸 함수를 만든 후에 테스트 코드를 작성해서 실행해보세요', () => { - expect(true).toBe(false); - }) + fireEvent.change(screen.getByPlaceholderText("쿠폰 이름"), { + target: { value: "새 쿠폰" }, + }); + fireEvent.change(screen.getByPlaceholderText("쿠폰 코드"), { + target: { value: "NEW10" }, + }); + fireEvent.change(screen.getByRole("combobox"), { + target: { value: "percentage" }, + }); + fireEvent.change(screen.getByPlaceholderText("할인 값"), { + target: { value: "10" }, + }); + + fireEvent.click(screen.getByText("쿠폰 추가")); + + const $newCoupon = screen.getByTestId("coupon-3"); + + expect($newCoupon).toHaveTextContent("새 쿠폰 (NEW10):10% 할인"); + }); + }); - test('새로운 hook 함수르 만든 후에 테스트 코드를 작성해서 실행해보세요', () => { + describe("커스텀훅 테스트", () => { + test("useLocalStorage test", () => { expect(true).toBe(false); - }) - }) -}) + }); + // test("새로운 hook 함수르 만든 후에 테스트 코드를 작성해서 실행해보세요", () => { + // expect(true).toBe(false); + // }); + }); +}); diff --git a/src/origin/components/AdminPage.tsx b/src/origin/components/AdminPage.tsx index c8f17db2..240c341a 100644 --- a/src/origin/components/AdminPage.tsx +++ b/src/origin/components/AdminPage.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react'; -import { Coupon, Discount, Product } from '../../types'; +import { useState } from "react"; +import { Coupon, Discount, Product } from "../../types"; interface Props { products: Product[]; @@ -9,26 +9,35 @@ interface Props { onCouponAdd: (newCoupon: Coupon) => void; } -export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, onCouponAdd }: Props) => { +export const AdminPage = ({ + products, + coupons, + onProductUpdate, + onProductAdd, + onCouponAdd, +}: Props) => { const [openProductIds, setOpenProductIds] = useState>(new Set()); const [editingProduct, setEditingProduct] = useState(null); - const [newDiscount, setNewDiscount] = useState({ quantity: 0, rate: 0 }); + const [newDiscount, setNewDiscount] = useState({ + quantity: 0, + rate: 0, + }); const [newCoupon, setNewCoupon] = useState({ - name: '', - code: '', - discountType: 'percentage', - discountValue: 0 + name: "", + code: "", + discountType: "percentage", + discountValue: 0, }); const [showNewProductForm, setShowNewProductForm] = useState(false); - const [newProduct, setNewProduct] = useState>({ - name: '', + const [newProduct, setNewProduct] = useState>({ + name: "", price: 0, stock: 0, - discounts: [] + discounts: [], }); const toggleProductAccordion = (productId: string) => { - setOpenProductIds(prev => { + setOpenProductIds((prev) => { const newSet = new Set(prev); if (newSet.has(productId)) { newSet.delete(productId); @@ -41,7 +50,7 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on // handleEditProduct 함수 수정 const handleEditProduct = (product: Product) => { - setEditingProduct({...product}); + setEditingProduct({ ...product }); }; // 새로운 핸들러 함수 추가 @@ -69,7 +78,7 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on }; const handleStockUpdate = (productId: string, newStock: number) => { - const updatedProduct = products.find(p => p.id === productId); + const updatedProduct = products.find((p) => p.id === productId); if (updatedProduct) { const newProduct = { ...updatedProduct, stock: newStock }; onProductUpdate(newProduct); @@ -78,11 +87,11 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on }; const handleAddDiscount = (productId: string) => { - const updatedProduct = products.find(p => p.id === productId); + const updatedProduct = products.find((p) => p.id === productId); if (updatedProduct && editingProduct) { const newProduct = { ...updatedProduct, - discounts: [...updatedProduct.discounts, newDiscount] + discounts: [...updatedProduct.discounts, newDiscount], }; onProductUpdate(newProduct); setEditingProduct(newProduct); @@ -91,11 +100,11 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on }; const handleRemoveDiscount = (productId: string, index: number) => { - const updatedProduct = products.find(p => p.id === productId); + const updatedProduct = products.find((p) => p.id === productId); if (updatedProduct) { const newProduct = { ...updatedProduct, - discounts: updatedProduct.discounts.filter((_, i) => i !== index) + discounts: updatedProduct.discounts.filter((_, i) => i !== index), }; onProductUpdate(newProduct); setEditingProduct(newProduct); @@ -105,10 +114,10 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on const handleAddCoupon = () => { onCouponAdd(newCoupon); setNewCoupon({ - name: '', - code: '', - discountType: 'percentage', - discountValue: 0 + name: "", + code: "", + discountType: "percentage", + discountValue: 0, }); }; @@ -116,10 +125,10 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on const productWithId = { ...newProduct, id: Date.now().toString() }; onProductAdd(productWithId); setNewProduct({ - name: '', + name: "", price: 0, stock: 0, - discounts: [] + discounts: [], }); setShowNewProductForm(false); }; @@ -134,38 +143,65 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on onClick={() => setShowNewProductForm(!showNewProductForm)} className="bg-green-500 text-white px-4 py-2 rounded mb-4 hover:bg-green-600" > - {showNewProductForm ? '취소' : '새 상품 추가'} + {showNewProductForm ? "취소" : "새 상품 추가"} {showNewProductForm && (

새 상품 추가

- + setNewProduct({ ...newProduct, name: e.target.value })} + onChange={(e) => + setNewProduct({ ...newProduct, name: e.target.value }) + } className="w-full p-2 border rounded" />
- + setNewProduct({ ...newProduct, price: parseInt(e.target.value) })} + onChange={(e) => + setNewProduct({ + ...newProduct, + price: parseInt(e.target.value), + }) + } className="w-full p-2 border rounded" />
- + setNewProduct({ ...newProduct, stock: parseInt(e.target.value) })} + onChange={(e) => + setNewProduct({ + ...newProduct, + stock: parseInt(e.target.value), + }) + } className="w-full p-2 border rounded" />
@@ -179,7 +215,11 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on )}
{products.map((product, index) => ( -
+
@@ -205,7 +250,12 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on handlePriceUpdate(product.id, parseInt(e.target.value))} + onChange={(e) => + handlePriceUpdate( + product.id, + parseInt(e.target.value) + ) + } className="w-full p-2 border rounded" />
@@ -214,18 +264,33 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on handleStockUpdate(product.id, parseInt(e.target.value))} + onChange={(e) => + handleStockUpdate( + product.id, + parseInt(e.target.value) + ) + } className="w-full p-2 border rounded" />
{/* 할인 정보 수정 부분 */}
-

할인 정보

+

+ 할인 정보 +

{editingProduct.discounts.map((discount, index) => ( -
- {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 +
+ + {discount.quantity}개 이상 구매 시{" "} + {discount.rate * 100}% 할인 + {showNewProductForm && (

새 상품 추가

- + setNewProduct({ ...newProduct, name: e.target.value })} + onChange={(e) => + setNewProduct({ ...newProduct, name: e.target.value }) + } className="w-full p-2 border rounded" />
- + setNewProduct({ ...newProduct, price: parseInt(e.target.value) })} + onChange={(e) => + setNewProduct({ + ...newProduct, + price: parseInt(e.target.value), + }) + } className="w-full p-2 border rounded" />
- + setNewProduct({ ...newProduct, stock: parseInt(e.target.value) })} + onChange={(e) => + setNewProduct({ + ...newProduct, + stock: parseInt(e.target.value), + }) + } className="w-full p-2 border rounded" />
@@ -179,7 +216,11 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on )}
{products.map((product, index) => ( -
+
@@ -205,7 +251,12 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on handlePriceUpdate(product.id, parseInt(e.target.value))} + onChange={(e) => + handlePriceUpdate( + product.id, + parseInt(e.target.value) + ) + } className="w-full p-2 border rounded" />
@@ -214,18 +265,33 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on handleStockUpdate(product.id, parseInt(e.target.value))} + onChange={(e) => + handleStockUpdate( + product.id, + parseInt(e.target.value) + ) + } className="w-full p-2 border rounded" />
{/* 할인 정보 수정 부분 */}
-

할인 정보

+

+ 할인 정보 +

{editingProduct.discounts.map((discount, index) => ( -
- {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 +
+ + {discount.quantity}개 이상 구매 시{" "} + {discount.rate * 100}% 할인 +
-
-

쿠폰 관리

-
-
- setNewCoupon({ ...newCoupon, name: e.target.value })} - className="w-full p-2 border rounded" - /> - setNewCoupon({ ...newCoupon, code: e.target.value })} - className="w-full p-2 border rounded" - /> -
- - setNewCoupon({ ...newCoupon, discountValue: parseInt(e.target.value) })} - className="w-full p-2 border rounded" - /> -
- -
-
-

현재 쿠폰 목록

-
- {coupons.map((coupon, index) => ( -
- {coupon.name} ({coupon.code}): - {coupon.discountType === 'amount' ? `${coupon.discountValue}원` : `${coupon.discountValue}%`} 할인 -
- ))} -
-
-
-
+
); diff --git a/src/refactoring/components/coupons/CouponManage.tsx b/src/refactoring/components/coupons/CouponManage.tsx new file mode 100644 index 00000000..a9423a11 --- /dev/null +++ b/src/refactoring/components/coupons/CouponManage.tsx @@ -0,0 +1,96 @@ +import { Coupon } from "../../../types"; + +interface CouponManageProps { + newCoupon: Coupon; + setNewCoupon: React.Dispatch>; + coupons: Coupon[]; + handleAddCoupon: () => void; +} + +const CouponManage: React.FC = ({ + newCoupon, + setNewCoupon, + coupons, + handleAddCoupon, +}) => { + return ( +
+

쿠폰 관리

+
+
+ + setNewCoupon({ ...newCoupon, name: e.target.value }) + } + className="w-full p-2 border rounded" + /> + + setNewCoupon({ ...newCoupon, code: e.target.value }) + } + className="w-full p-2 border rounded" + /> +
+ + + setNewCoupon({ + ...newCoupon, + discountValue: parseInt(e.target.value), + }) + } + className="w-full p-2 border rounded" + /> +
+ +
+
+

현재 쿠폰 목록

+
+ {coupons.map((coupon, index) => ( +
+ {coupon.name} ({coupon.code}): + {coupon.discountType === "amount" + ? `${coupon.discountValue}원` + : `${coupon.discountValue}%`}{" "} + 할인 +
+ ))} +
+
+
+
+ ); +}; + +export default CouponManage; From d793fcac27a8d18dd235ffe4ccc4a2a21e798321 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Fri, 17 Jan 2025 02:11:25 +0900 Subject: [PATCH 23/24] =?UTF-8?q?Refactor=20:=20DiscountList=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/components/AdminPage.tsx | 65 ++------------ .../components/coupons/DiscountList.tsx | 84 +++++++++++++++++++ 2 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 src/refactoring/components/coupons/DiscountList.tsx diff --git a/src/refactoring/components/AdminPage.tsx b/src/refactoring/components/AdminPage.tsx index c9632e89..d9c9b445 100644 --- a/src/refactoring/components/AdminPage.tsx +++ b/src/refactoring/components/AdminPage.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { Coupon, Discount, Product } from "../../types.ts"; import CouponManage from "./coupons/CouponManage.tsx"; +import DiscountList from "./coupons/DiscountList.tsx"; interface Props { products: Product[]; @@ -275,62 +276,14 @@ export const AdminPage = ({ />
{/* 할인 정보 수정 부분 */} -
-

- 할인 정보 -

- {editingProduct.discounts.map((discount, index) => ( -
- - {discount.quantity}개 이상 구매 시{" "} - {discount.rate * 100}% 할인 - - -
- ))} -
- - setNewDiscount({ - ...newDiscount, - quantity: parseInt(e.target.value), - }) - } - className="w-1/3 p-2 border rounded" - /> - - setNewDiscount({ - ...newDiscount, - rate: parseInt(e.target.value) / 100, - }) - } - className="w-1/3 p-2 border rounded" - /> - -
-
+ +
+ )) + ) : ( +
+ {product.discounts.map((discount, index) => ( +
+ + {discount.quantity}개 이상 구매 시 {discount.rate * 100}% 할인 + +
+ ))} +
+ )} +
+ + setNewDiscount({ + ...newDiscount, + quantity: parseInt(e.target.value), + }) + } + className="w-1/3 p-2 border rounded" + /> + + setNewDiscount({ + ...newDiscount, + rate: parseInt(e.target.value) / 100, + }) + } + className="w-1/3 p-2 border rounded" + /> + +
+
+ ); +}; + +export default DiscountList; From 27c33a480f07a8f8ccec406ec541330d3f2b8e82 Mon Sep 17 00:00:00 2001 From: Bae seong-kyu Date: Fri, 17 Jan 2025 02:51:12 +0900 Subject: [PATCH 24/24] =?UTF-8?q?Refactor=20:=20ProductList=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/components/CartPage.tsx | 38 +++------------ .../components/carts/ProductList.tsx | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 32 deletions(-) create mode 100644 src/refactoring/components/carts/ProductList.tsx diff --git a/src/refactoring/components/CartPage.tsx b/src/refactoring/components/CartPage.tsx index bfefe887..8266ec3b 100644 --- a/src/refactoring/components/CartPage.tsx +++ b/src/refactoring/components/CartPage.tsx @@ -1,10 +1,9 @@ import { CartItem, Coupon, Product } from "../../types.ts"; import { useCart, useLocalStorage } from "../hooks"; import CartItemDisplay from "./carts/CartItemDisplay.tsx"; -import DiscountInfo from "./carts/DiscountInfo.tsx"; -import ProductPrice from "./carts/ProductPrice.tsx"; import OrderSummary from "./carts/OrderSummary.tsx"; import ApplyCoupon from "./carts/ApplyCoupon.tsx"; +import ProductList from "./carts/ProductList.tsx"; interface Props { products: Product[]; @@ -46,39 +45,14 @@ export const CartPage = ({ products, coupons }: Props) => {

상품 목록

-
- {products.map((product) => { - const remainingStock = getRemainingStock(product); - return ( -
- - - -
- ); - })} -
+

장바구니 내역

-
{cart.map((item) => { const appliedDiscount = getAppliedDiscount(item); diff --git a/src/refactoring/components/carts/ProductList.tsx b/src/refactoring/components/carts/ProductList.tsx new file mode 100644 index 00000000..831a2206 --- /dev/null +++ b/src/refactoring/components/carts/ProductList.tsx @@ -0,0 +1,46 @@ +import { Product } from "../../../types"; +import DiscountInfo from "./DiscountInfo"; +import ProductPrice from "./ProductPrice"; + +interface ProductListProps { + products: Product[]; + addToCart: (product: Product) => void; + getRemainingStock: (product: Product) => number; +} + +const ProductList: React.FC = ({ + products, + addToCart, + getRemainingStock, +}) => { + return ( +
+ {products.map((product) => { + const remainingStock = getRemainingStock(product); + return ( +
+ + + +
+ ); + })} +
+ ); +}; + +export default ProductList;