From ff29804f9f63022dbd9ffe7942902bf680a819d8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Sun, 12 Jan 2025 18:19:53 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=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 --- 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 063e0ff18a7efe894eeec9bc9f4f5d6e6f49dab6 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Mon, 13 Jan 2025 21:33:03 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20useCart=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EA=B3=84=EC=82=B0=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/hooks/useCart.ts | 97 ++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/src/refactoring/hooks/useCart.ts b/src/refactoring/hooks/useCart.ts index 67bf9208..df0c355e 100644 --- a/src/refactoring/hooks/useCart.ts +++ b/src/refactoring/hooks/useCart.ts @@ -7,19 +7,104 @@ export const useCart = () => { const [cart, setCart] = useState([]); const [selectedCoupon, setSelectedCoupon] = useState(null); - const addToCart = (product: Product) => {}; + const addToCart = (product: Product) => { + const remainingStock = getRemainingStock(product); + if (remainingStock <= 0) return; - const removeFromCart = (productId: string) => {}; + 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 updateQuantity = (productId: string, newQuantity: number) => {}; + const getRemainingStock = (product: Product) => { + const cartItem = cart.find((item) => item.product.id === product.id); + return product.stock - (cartItem?.quantity || 0); + }; - const applyCoupon = (coupon: Coupon) => {}; + const removeFromCart = (productId: string) => { + setCart((prevCart) => + prevCart.filter((item) => item.product.id !== productId) + ); + }; + 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 calculateTotal = () => ({ + const applyCoupon = (coupon: Coupon) => { + setSelectedCoupon(coupon); + }; + + /* const calculateTotal = () => ({ totalBeforeDiscount: 0, totalAfterDiscount: 0, totalDiscount: 0, - }); + }); */ + + 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 { cart, From 58f45bf24300a97c7c1af22024196baba2531f3b Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Mon, 13 Jan 2025 22:01:21 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20useProduct=20=EC=83=81=ED=92=88?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EA=B5=AC=ED=98=84=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/hooks/useProduct.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/refactoring/hooks/useProduct.ts b/src/refactoring/hooks/useProduct.ts index 028bacb6..47679fab 100644 --- a/src/refactoring/hooks/useProduct.ts +++ b/src/refactoring/hooks/useProduct.ts @@ -1,6 +1,25 @@ -import { useState } from 'react'; -import { Product } from '../../types.ts'; +import { useCallback, useState } from "react"; +import { Product } from "../../types.ts"; export const useProducts = (initialProducts: Product[]) => { - return { products: [], updateProduct: () => undefined, addProduct: () => undefined }; + const [products, setProducts] = useState(initialProducts); + console.log(initialProducts); + + const updateProduct = useCallback((updatedProduct: Product) => { + setProducts((prevProducts) => + prevProducts.map((product) => + product.id === updatedProduct.id ? updatedProduct : product + ) + ); + }, []); + + const addProduct = useCallback((newProduct: Product) => { + setProducts((prevProducts) => [...prevProducts, newProduct]); + }, []); + + return { + products, + updateProduct, + addProduct, + }; }; From 81d4cd9da0092791516cf446a5894504321632d8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Mon, 13 Jan 2025 22:07:04 +0900 Subject: [PATCH 04/10] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/hooks/useProduct.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/refactoring/hooks/useProduct.ts b/src/refactoring/hooks/useProduct.ts index 47679fab..2a0b8c58 100644 --- a/src/refactoring/hooks/useProduct.ts +++ b/src/refactoring/hooks/useProduct.ts @@ -3,7 +3,6 @@ import { Product } from "../../types.ts"; export const useProducts = (initialProducts: Product[]) => { const [products, setProducts] = useState(initialProducts); - console.log(initialProducts); const updateProduct = useCallback((updatedProduct: Product) => { setProducts((prevProducts) => From cd6eb1b16511cd35cc18cd113d949dc1860fb681 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Mon, 13 Jan 2025 22:09:48 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20useCoupon=20=EC=BF=A0=ED=8F=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/hooks/useCoupon.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/refactoring/hooks/useCoupon.ts b/src/refactoring/hooks/useCoupon.ts index dc1b9078..198e3e96 100644 --- a/src/refactoring/hooks/useCoupon.ts +++ b/src/refactoring/hooks/useCoupon.ts @@ -1,6 +1,15 @@ import { Coupon } from "../../types.ts"; -import { useState } from "react"; +import { useCallback, useState } from "react"; export const useCoupons = (initialCoupons: Coupon[]) => { - return { coupons: [], addCoupon: () => undefined }; + const [coupons, setCoupons] = useState(initialCoupons); + + const addCoupon = useCallback((newCoupon: Coupon) => { + setCoupons((prevCoupons) => [...prevCoupons, newCoupon]); + }, []); + + return { + coupons, + addCoupon, + }; }; From 95a2cd9bfa60f438765892567368767de8e00251 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Tue, 14 Jan 2025 19:35:08 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20cart=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/models/cart.ts | 73 +++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/src/refactoring/models/cart.ts b/src/refactoring/models/cart.ts index d8f4c2ea..ed373760 100644 --- a/src/refactoring/models/cart.ts +++ b/src/refactoring/models/cart.ts @@ -1,28 +1,89 @@ import { CartItem, Coupon } from "../../types"; +// 할인 없이 상품의 총 금액 계산 export const calculateItemTotal = (item: CartItem) => { - return 0; + const { product, quantity } = item; + const discount = getMaxApplicableDiscount(item); + + return product.price * quantity * (1 - discount); }; +// 상품 적용 가능한 최대 할인 금액 export const getMaxApplicableDiscount = (item: CartItem) => { - return 0; + const { product, quantity } = item; + + return product.discounts.reduce((maxDiscount, d) => { + return quantity >= d.quantity && d.rate > maxDiscount + ? d.rate + : maxDiscount; + }, 0); }; +// 쿠폰 없이 총액 계산 (useCart훅의 calculateTotal함수 참고) 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), }; }; +/* + * useCart훅의 updateQuantity함수 참고 + * 수량 업데이트 + * 수량 0으로 설정 -> 해당 항목 제거 + * 재고 한도 초과 x + */ export const updateCartItemQuantity = ( cart: CartItem[], productId: string, newQuantity: number ): CartItem[] => { - return []; + return 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); }; From 38272591085e4e81f3516fdffb3571f601756a67 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Wed, 15 Jan 2025 23:40:04 +0900 Subject: [PATCH 07/10] =?UTF-8?q?refactor:=20Button=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/components/CartPage.tsx | 106 ++++++++++++------- src/refactoring/components/common/Button.tsx | 40 +++++++ 2 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 src/refactoring/components/common/Button.tsx diff --git a/src/refactoring/components/CartPage.tsx b/src/refactoring/components/CartPage.tsx index bafe5ecb..b13ac763 100644 --- a/src/refactoring/components/CartPage.tsx +++ b/src/refactoring/components/CartPage.tsx @@ -1,5 +1,6 @@ -import { CartItem, Coupon, Product } from '../../types.ts'; +import { CartItem, Coupon, Product } from "../../types.ts"; import { useCart } from "../hooks"; +import Button from "./common/Button.tsx"; interface Props { products: Product[]; @@ -14,7 +15,7 @@ export const CartPage = ({ products, coupons }: Props) => { updateQuantity, applyCoupon, calculateTotal, - selectedCoupon + selectedCoupon, } = useCart(); const getMaxDiscount = (discounts: { quantity: number; rate: number }[]) => { @@ -22,11 +23,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 +49,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,22 +83,19 @@ 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)}% 할인
  • ))}
)} - + {remainingStock > 0 ? "장바구니에 추가" : "품절"} +
); })} @@ -94,41 +105,48 @@ 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)}% 할인 적용) + )} - +
- - + - +
); @@ -144,14 +162,20 @@ export const CartPage = ({ products, coupons }: Props) => { {coupons.map((coupon, index) => ( ))} {selectedCoupon && (

- 적용된 쿠폰: {selectedCoupon.name} - ({selectedCoupon.discountType === 'amount' ? `${selectedCoupon.discountValue}원` : `${selectedCoupon.discountValue}%`} 할인) + 적용된 쿠폰: {selectedCoupon.name}( + {selectedCoupon.discountType === "amount" + ? `${selectedCoupon.discountValue}원` + : `${selectedCoupon.discountValue}%`}{" "} + 할인)

)}
@@ -160,7 +184,9 @@ export const CartPage = ({ products, coupons }: Props) => {

주문 요약

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

-

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

+

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

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

diff --git a/src/refactoring/components/common/Button.tsx b/src/refactoring/components/common/Button.tsx new file mode 100644 index 00000000..7123ea10 --- /dev/null +++ b/src/refactoring/components/common/Button.tsx @@ -0,0 +1,40 @@ +interface ButtonProps { + onClick: () => void; + children: React.ReactNode; + className?: string; + wdSize?: string; + disabled?: boolean; + variant?: string; // 문자열로 자유롭게 입력 가능 +} + +const Button = ({ + onClick, + children, + className = "", + wdSize, + disabled, + variant = "fullBlueBtn", +}: ButtonProps) => { + const buttonStyle = + { + fullBlueBtn: + "w-full bg-blue-500 text-white hover:bg-blue-600 px-3 py-1 rounded", + fullGrayBtn: + "w-full bg-gray-300 text-gray-500 cursor-not-allowed px-3 py-1 rounded", + smallGrayBtn: + "bg-gray-300 text-gray-800 px-2 py-1 rounded mr-1 hover:bg-gray-400", + smallRedBtn: "bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600", + }[disabled ? "disabled" : variant] || ""; + + return ( + + ); +}; + +export default Button; From 89a7b538081dc1b81aa4dc7c69eab2c834321fb3 Mon Sep 17 00:00:00 2001 From: "DESKTOP-PPQ6A24\\USER" Date: Thu, 16 Jan 2025 23:05:54 +0900 Subject: [PATCH 08/10] =?UTF-8?q?refactor:=20Button=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/refactoring/App.tsx | 54 ++--- src/refactoring/components/AdminPage.tsx | 209 ++++++++++++++----- src/refactoring/components/common/Button.tsx | 7 + 3 files changed, 189 insertions(+), 81 deletions(-) diff --git a/src/refactoring/App.tsx b/src/refactoring/App.tsx index 31307173..60e774b4 100644 --- a/src/refactoring/App.tsx +++ b/src/refactoring/App.tsx @@ -1,46 +1,50 @@ -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"; +import Button from "./components/common/Button.tsx"; 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 +61,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 +75,7 @@ const App = () => { onCouponAdd={addCoupon} /> ) : ( - + )}
diff --git a/src/refactoring/components/AdminPage.tsx b/src/refactoring/components/AdminPage.tsx index 4ff2bbcd..227a415e 100644 --- a/src/refactoring/components/AdminPage.tsx +++ b/src/refactoring/components/AdminPage.tsx @@ -1,5 +1,6 @@ -import { useState } from 'react'; -import { Coupon, Discount, Product } from '../../types.ts'; +import { useState } from "react"; +import { Coupon, Discount, Product } from "../../types.ts"; +import Button from "./common/Button.tsx"; interface Props { products: Product[]; @@ -9,26 +10,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 +51,7 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on // handleEditProduct 함수 수정 const handleEditProduct = (product: Product) => { - setEditingProduct({...product}); + setEditingProduct({ ...product }); }; // 새로운 핸들러 함수 추가 @@ -69,7 +79,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 +88,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 +101,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 +115,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 +126,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); }; @@ -130,42 +140,69 @@ export const AdminPage = ({ products, coupons, onProductUpdate, onProductAdd, on

상품 관리

- + {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 +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}% 할인 + + {showNewProductForm && ( +
+

새 상품 추가

+
+ + + setNewProduct({ ...newProduct, name: e.target.value }) + } + className="w-full p-2 border rounded" + />
- )} -
- {products.map((product, index) => ( -
+
+ )} +
+ {products.map((product, index) => ( +
+ + {openProductIds.has(product.id) && ( +
+ {editingProduct && editingProduct.id === product.id ? ( +
+
+ + + handleProductNameUpdate(product.id, e.target.value) + } + className="w-full p-2 border rounded" + /> +
+
+ + + handlePriceUpdate( + product.id, + parseInt(e.target.value) + ) + } + className="w-full p-2 border rounded" + /> +
+
+ + + handleStockUpdate( + product.id, + parseInt(e.target.value) + ) + } + className="w-full p-2 border rounded" + /> +
+ {/* 할인 정보 수정 부분 */}
-
- - - handleProductNameUpdate( - product.id, - e.target.value - ) - } - className="w-full p-2 border rounded" - /> -
-
- +

+ 할인 정보 +

+ {editingProduct.discounts.map((discount, index) => ( +
+ + {discount.quantity}개 이상 구매 시{" "} + {discount.rate * 100}% 할인 + + +
+ ))} +
- handlePriceUpdate( - product.id, - parseInt(e.target.value) - ) + setNewDiscount({ + ...newDiscount, + quantity: parseInt(e.target.value), + }) } - className="w-full p-2 border rounded" + className="w-1/3 p-2 border rounded" /> -
-
- - handleStockUpdate( - product.id, - parseInt(e.target.value) - ) + setNewDiscount({ + ...newDiscount, + rate: parseInt(e.target.value) / 100, + }) } - className="w-full p-2 border rounded" + className="w-1/3 p-2 border rounded" /> +
- {/* 할인 정보 수정 부분 */} -
-

- 할인 정보 -

- {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}% 할인 - -
- ))} -
- )} -
- )} -
- ))} -
+ +
+ ) : ( +
+ {product.discounts.map((discount, index) => ( +
+ + {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, code: e.target.value }) + setNewCoupon({ + ...newCoupon, + discountValue: parseInt(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}%`}{" "} - 할인 -
- ))} -
+ +
+
+

현재 쿠폰 목록

+
+ {coupons.map((coupon, index) => ( +
+ {coupon.name} ({coupon.code}): + {coupon.discountType === "amount" + ? `${coupon.discountValue}원` + : `${coupon.discountValue}%`}{" "} + 할인 +
+ ))}
-
+ {/* 쿠폰 관리-- */} + ); }; diff --git a/src/refactoring/components/CartPage.tsx b/src/refactoring/components/CartPage.tsx index b13ac763..b554df97 100644 --- a/src/refactoring/components/CartPage.tsx +++ b/src/refactoring/components/CartPage.tsx @@ -1,6 +1,8 @@ import { CartItem, Coupon, Product } from "../../types.ts"; -import { useCart } from "../hooks"; +import { useCart } from "../hooks/index.ts"; +import PageSection from "./PageSection.tsx"; import Button from "./common/Button.tsx"; +import PageContainer from "./common/PageContainer.tsx"; interface Props { products: Product[]; @@ -43,157 +45,152 @@ export const CartPage = ({ products, coupons }: Props) => { }; return ( -
-

장바구니

-
-
-

상품 목록

-
- {products.map((product) => { - const remainingStock = getRemainingStock(product); - return ( -
+ {/* --상품목록 */} + + {products.map((product) => { + const remainingStock = getRemainingStock(product); + return ( +
+
+ {product.name} + + {product.price.toLocaleString()}원 + +
+
+ 0 ? "text-green-600" : "text-red-600" + }`} > -
- {product.name} - - {product.price.toLocaleString()}원 - -
-
- 0 ? "text-green-600" : "text-red-600" - }`} - > - 재고: {remainingStock}개 + 재고: {remainingStock}개 + + {product.discounts.length > 0 && ( + + 최대 {(getMaxDiscount(product.discounts) * 100).toFixed(0)}% + 할인 + + )} +
+ {product.discounts.length > 0 && ( +
    + {product.discounts.map((discount, index) => ( +
  • + {discount.quantity}개 이상:{" "} + {(discount.rate * 100).toFixed(0)}% 할인 +
  • + ))} +
+ )} + +
+ ); + })} + + {/* 상품목록-- */} + + {/* --장바구니 내역 */} + + {cart.map((item) => { + const appliedDiscount = getAppliedDiscount(item); + return ( +
+
+ {item.product.name} +
+ + {item.product.price}원 x {item.quantity} + {appliedDiscount > 0 && ( + + ({(appliedDiscount * 100).toFixed(0)}% 할인 적용) - {product.discounts.length > 0 && ( - - 최대{" "} - {(getMaxDiscount(product.discounts) * 100).toFixed(0)}% - 할인 - - )} -
- {product.discounts.length > 0 && ( -
    - {product.discounts.map((discount, index) => ( -
  • - {discount.quantity}개 이상:{" "} - {(discount.rate * 100).toFixed(0)}% 할인 -
  • - ))} -
)} - -
- ); - })} -
-
-
-

장바구니 내역

- -
- {cart.map((item) => { - const appliedDiscount = getAppliedDiscount(item); - return ( -
+
+
+ - - -
-
- ); - })} -
+ - + + + +
+
+ ); + })} -
-

쿠폰 적용

- - {selectedCoupon && ( -

- 적용된 쿠폰: {selectedCoupon.name}( - {selectedCoupon.discountType === "amount" - ? `${selectedCoupon.discountValue}원` - : `${selectedCoupon.discountValue}%`}{" "} - 할인) -

- )} -
+
+

쿠폰 적용

+ + {selectedCoupon && ( +

+ 적용된 쿠폰: {selectedCoupon.name}( + {selectedCoupon.discountType === "amount" + ? `${selectedCoupon.discountValue}원` + : `${selectedCoupon.discountValue}%`}{" "} + 할인) +

+ )} +
-
-

주문 요약

-
-

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

-

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

-

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

-
+
+

주문 요약

+
+

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

+

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

+

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

-
-
+ + + {/* 장바구니 내역-- */} + ); }; diff --git a/src/refactoring/components/PageSection.tsx b/src/refactoring/components/PageSection.tsx new file mode 100644 index 00000000..9585555c --- /dev/null +++ b/src/refactoring/components/PageSection.tsx @@ -0,0 +1,15 @@ +interface ContainerProps { + children: React.ReactNode; + title: string; +} + +const PageSection = ({ children, title }: ContainerProps) => { + return ( +
+

{title}

+
{children}
+
+ ); +}; + +export default PageSection; diff --git a/src/refactoring/components/common/PageContainer.tsx b/src/refactoring/components/common/PageContainer.tsx new file mode 100644 index 00000000..ead54d57 --- /dev/null +++ b/src/refactoring/components/common/PageContainer.tsx @@ -0,0 +1,15 @@ +interface ContainerProps { + children: React.ReactNode; + title: string; +} + +const PageContainer = ({ children, title }: ContainerProps) => { + return ( +
+

{title}

+
{children}
+
+ ); +}; + +export default PageContainer;