From c4b75c52fa20ec40ef8fe219fa3fee540c0c4009 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:06:04 +0900 Subject: [PATCH 01/25] =?UTF-8?q?remove:=20=EB=A6=AC=EC=BD=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 27 --------------------------- package.json | 1 - src/components/common/Breadcrumb.tsx | 1 - src/store/cart.ts | 24 +++++++++++------------- src/store/products.ts | 26 ++++++++++++-------------- 5 files changed, 23 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a41c8e..e1b8604 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,6 @@ "react-dom": "^18.2.0", "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.11.2", - "recoil": "^0.7.7", "tailwindcss": "^3.3.2", "typescript": "^5.0.4", "vite": "^4.3.9" @@ -2800,12 +2799,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==", - "dev": true - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -5454,26 +5447,6 @@ "node": ">=8.10.0" } }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dev": true, - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/registry-auth-token": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", diff --git a/package.json b/package.json index c593f21..9f01e08 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "react-dom": "^18.2.0", "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.11.2", - "recoil": "^0.7.7", "tailwindcss": "^3.3.2", "typescript": "^5.0.4", "vite": "^4.3.9" diff --git a/src/components/common/Breadcrumb.tsx b/src/components/common/Breadcrumb.tsx index d1e518f..2590fb4 100644 --- a/src/components/common/Breadcrumb.tsx +++ b/src/components/common/Breadcrumb.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Category } from "../../constants/category"; interface IBreadCrumbsPros { diff --git a/src/store/cart.ts b/src/store/cart.ts index 1e530e9..e66d409 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -1,4 +1,3 @@ -import { atom, selector } from "recoil"; import { CART_ITEM } from "../constants/category"; export interface ICartInfo { @@ -22,17 +21,16 @@ export interface ICartState { * 카트의 상태는 localStorage 기준으로 초기화 됩니다. * 카트의 상태는 새로고침해도 유지되어야 하기 때문입니다. */ -export const cartState = atom({ - key: "cart", - default: {}, - effects: [ - ({ setSelf, onSet }) => { - localStorage.getItem(CART_ITEM) && setSelf(JSON.parse(localStorage.getItem(CART_ITEM) as string)); - onSet((value) => localStorage.setItem(CART_ITEM, JSON.stringify(value))); - }, - ], -}); - +// export const cartState = atom({ +// key: "cart", +// default: {}, +// effects: [ +// ({ setSelf, onSet }) => { +// localStorage.getItem(CART_ITEM) && setSelf(JSON.parse(localStorage.getItem(CART_ITEM) as string)); +// onSet((value) => localStorage.setItem(CART_ITEM, JSON.stringify(value))); +// }, +// ], +// }); /** * cartList를 구현 하세요. @@ -54,4 +52,4 @@ export const removeFromCart = (cart: ICartState, id: string) => { /** * 그 외에 화면을 참고하며 필요한 기능들을 구현 하세요. - */ \ No newline at end of file + */ diff --git a/src/store/products.ts b/src/store/products.ts index 591aa61..f3bcefa 100644 --- a/src/store/products.ts +++ b/src/store/products.ts @@ -1,5 +1,3 @@ -import React from "react"; -import { selector } from "recoil"; import CONSTANTS from "../constants/constants"; const productsURL = `${CONSTANTS.IS_DEV ? `/proxy` : `${import.meta.env.VITE_FAKE_STORE_API}`}/products`; @@ -22,15 +20,15 @@ export interface IProduct { * productList는 API 1회 요청 후에 유지됩니다. * 디테일 페이지에서는 productDetail/id로 각각 호출하셔도 무방합니다. */ -export const productsList = selector({ - key: "productsList", - get: async () => { - try { - const response = await fetch(productsURL); - return (await response.json()) || []; - } catch (error) { - console.log(`Error: ${error}`); - return []; - } - }, -}); +// export const productsList = selector({ +// key: "productsList", +// get: async () => { +// try { +// const response = await fetch(productsURL); +// return (await response.json()) || []; +// } catch (error) { +// console.log(`Error: ${error}`); +// return []; +// } +// }, +// }); From 1e8ee4dc42e63ca188c36332300c282a82319fa9 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:08:33 +0900 Subject: [PATCH 02/25] =?UTF-8?q?chore:=20=EB=A6=AC=EB=8D=95=EC=8A=A4,=20?= =?UTF-8?q?=EB=A6=AC=EB=8D=95=EC=8A=A4=20=ED=88=B4=ED=82=B7=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 54 ++++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e1b8604..09f8126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "react-shop", "version": "0.0.1", "dependencies": { + "@reduxjs/toolkit": "^2.2.7", "@tanstack/react-query": "^5.37.1", "@tanstack/react-query-devtools": "^5.37.1", "@vercel/node": "^2.15.10", - "axios": "^1.6.8" + "axios": "^1.6.8", + "redux": "^5.0.1" }, "devDependencies": { "@types/react": "^18.2.7", @@ -885,6 +887,29 @@ "node": ">=12" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.7.tgz", + "integrity": "sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.2.tgz", @@ -2947,6 +2972,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-lazy": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", @@ -5447,6 +5481,19 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/registry-auth-token": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", @@ -5491,6 +5538,11 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", diff --git a/package.json b/package.json index 9f01e08..a099e4d 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,11 @@ "vite": "^4.3.9" }, "dependencies": { + "@reduxjs/toolkit": "^2.2.7", "@tanstack/react-query": "^5.37.1", "@tanstack/react-query-devtools": "^5.37.1", "@vercel/node": "^2.15.10", - "axios": "^1.6.8" + "axios": "^1.6.8", + "redux": "^5.0.1" } } From 4c40e28ccc9999c0caec2ed05084eda6e4dd9119 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:17:41 +0900 Subject: [PATCH 03/25] =?UTF-8?q?remove:=20=EB=A6=AC=EC=BD=94=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom.js | 8 -------- src/main.tsx | 11 ++++------- 2 files changed, 4 insertions(+), 15 deletions(-) delete mode 100644 src/atom.js diff --git a/src/atom.js b/src/atom.js deleted file mode 100644 index cd2b9c8..0000000 --- a/src/atom.js +++ /dev/null @@ -1,8 +0,0 @@ -import { atom } from "recoil"; - -const countState = atom({ - key: "countState", - default: "0", -}); - -export default countState; diff --git a/src/main.tsx b/src/main.tsx index 115b218..1451f8b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,8 @@ import React from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { createRoot } from "react-dom/client"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import App from "./App"; -import { createRoot } from "react-dom/client"; -import { RecoilRoot } from "recoil"; -import { cartState } from "./store/cart"; import { CART_ITEM } from "./constants/category"; const queryClient = new QueryClient(); @@ -15,10 +13,9 @@ const initialValue = JSON.parse(localStorage.getItem(CART_ITEM) as string) ?? {} root.render( - {/* Recoil이나 Redux를 사용하시면 됩니다. 현업에서는 Redux-toolkit이 가장 많습니다. */} - Object.assign(cartState, initialValue)}> + - + From 415ba05bd617257b5b6d29219a87ead0d2829bf0 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:18:22 +0900 Subject: [PATCH 04/25] =?UTF-8?q?chore:=20=EB=A6=AC=EB=8D=95=EC=8A=A4,=20?= =?UTF-8?q?=EB=A6=AC=EB=8D=95=EC=8A=A4=20=ED=88=B4=ED=82=B7=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 53 ++++++++++++++++++++++++++++++++++++----------- package.json | 1 + 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09f8126..18f1517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@tanstack/react-query-devtools": "^5.37.1", "@vercel/node": "^2.15.10", "axios": "^1.6.8", + "react-redux": "^9.1.2", "redux": "^5.0.1" }, "devDependencies": { @@ -1118,16 +1119,15 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.7.tgz", - "integrity": "sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==", - "dev": true, + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "devOptional": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -1150,11 +1150,10 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, "node_modules/@vercel/build-utils": { "version": "6.8.3", @@ -2099,7 +2098,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "devOptional": true }, "node_modules/daisyui": { "version": "2.51.6", @@ -5366,6 +5365,28 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -6750,6 +6771,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index a099e4d..81edfea 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@tanstack/react-query-devtools": "^5.37.1", "@vercel/node": "^2.15.10", "axios": "^1.6.8", + "react-redux": "^9.1.2", "redux": "^5.0.1" } } From 7f97493dbf23f44bd72c62f3cb19fbead7e52255 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 16:28:55 +0900 Subject: [PATCH 05/25] =?UTF-8?q?feat:=20store.ts=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.tsx | 1 + src/store/store.ts | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 src/store/store.ts diff --git a/src/main.tsx b/src/main.tsx index 1451f8b..d546ed1 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,5 +1,6 @@ import React from "react"; import { createRoot } from "react-dom/client"; +import { Provider } from "react-redux"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import App from "./App"; diff --git a/src/store/store.ts b/src/store/store.ts new file mode 100644 index 0000000..afa9133 --- /dev/null +++ b/src/store/store.ts @@ -0,0 +1,5 @@ +import { configureStore } from "@reduxjs/toolkit"; + +export default configureStore({ + reducer: {}, +}); From c97854292b02d5508b269cdd05087a9706012c71 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:55:20 +0900 Subject: [PATCH 06/25] =?UTF-8?q?feat:=20cartSlice=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.tsx | 1 + src/store/cart.ts | 61 ++++++++++++++++++++++++++++++++++--------- src/store/index.ts | 10 +++++++ src/store/products.ts | 34 ------------------------ src/store/store.ts | 5 ---- 5 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 src/store/index.ts delete mode 100644 src/store/products.ts delete mode 100644 src/store/store.ts diff --git a/src/main.tsx b/src/main.tsx index d546ed1..4b19c63 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,6 +4,7 @@ import { Provider } from "react-redux"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import App from "./App"; +import store from "./store"; import { CART_ITEM } from "./constants/category"; const queryClient = new QueryClient(); diff --git a/src/store/cart.ts b/src/store/cart.ts index e66d409..0c03269 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -1,3 +1,4 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { CART_ITEM } from "../constants/category"; export interface ICartInfo { @@ -14,9 +15,15 @@ export interface ICartItems { } export interface ICartState { - readonly items?: Record; + readonly items: ICartItems[]; + totalAmount: number; } +const initialState: ICartState = { + items: [], + totalAmount: 0, +}; + /** * 카트의 상태는 localStorage 기준으로 초기화 됩니다. * 카트의 상태는 새로고침해도 유지되어야 하기 때문입니다. @@ -32,6 +39,35 @@ export interface ICartState { // ], // }); +const cartSlice = createSlice({ + name: "cart", + initialState, + reducers: { + addToCart(state, action: PayloadAction) { + const newItem = action.payload; + const existingItem = state.items.find((item) => item.id === newItem.id); + if (existingItem) { + existingItem.count += newItem.count; + } else { + state.items.push(newItem); + } + state.totalAmount += newItem.price * newItem.count; + }, + removeFromCart(state, action: PayloadAction) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + if (existingItem) { + state.totalAmount -= existingItem.price * existingItem.count; + state.items = state.items.filter((item) => item.id !== id); + } + }, + clearCart(state) { + state.items = []; + state.totalAmount = 0; + }, + }, +}); + /** * cartList를 구현 하세요. * id, image, count 등을 return합니다. @@ -40,16 +76,15 @@ export interface ICartState { // addToCart는 구현 해보세요. // removeFromCart는 참고 하세요. -export const removeFromCart = (cart: ICartState, id: string) => { - const tempCart = { ...cart }; - if (tempCart[id].count === 1) { - delete tempCart[id]; - return tempCart; - } else { - return { ...tempCart, [id]: { id: id, count: cart[id].count - 1 } }; - } -}; +// export const removeFromCart = (cart: ICartState, id: string) => { +// const tempCart = { ...cart }; +// if (tempCart[id].count === 1) { +// delete tempCart[id]; +// return tempCart; +// } else { +// return { ...tempCart, [id]: { id: id, count: cart[id].count - 1 } }; +// } +// }; -/** - * 그 외에 화면을 참고하며 필요한 기능들을 구현 하세요. - */ +export const { addToCart, removeFromCart, clearCart } = cartSlice.actions; +export default cartSlice.reducer; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..87d1d81 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,10 @@ +import { configureStore } from "@reduxjs/toolkit"; +import productsReducer from "./productsSlice"; + +const store = configureStore({ + reducer: { + products: productsReducer, + }, +}); + +export default store; diff --git a/src/store/products.ts b/src/store/products.ts deleted file mode 100644 index f3bcefa..0000000 --- a/src/store/products.ts +++ /dev/null @@ -1,34 +0,0 @@ -import CONSTANTS from "../constants/constants"; - -const productsURL = `${CONSTANTS.IS_DEV ? `/proxy` : `${import.meta.env.VITE_FAKE_STORE_API}`}/products`; - -interface IRating { - readonly rate?: number; - readonly count?: number; -} -export interface IProduct { - readonly id: number; - readonly title: string; - readonly description: string; - readonly category: string; - readonly price: number; - readonly image: string; - readonly rating: IRating; -} - -/** - * productList는 API 1회 요청 후에 유지됩니다. - * 디테일 페이지에서는 productDetail/id로 각각 호출하셔도 무방합니다. - */ -// export const productsList = selector({ -// key: "productsList", -// get: async () => { -// try { -// const response = await fetch(productsURL); -// return (await response.json()) || []; -// } catch (error) { -// console.log(`Error: ${error}`); -// return []; -// } -// }, -// }); diff --git a/src/store/store.ts b/src/store/store.ts deleted file mode 100644 index afa9133..0000000 --- a/src/store/store.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { configureStore } from "@reduxjs/toolkit"; - -export default configureStore({ - reducer: {}, -}); From cd268d5ad471312a6fc300a00f25d01c104483ef Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:59:40 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat:=20Redux=20=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EC=96=B4=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store/index.ts b/src/store/index.ts index 87d1d81..b64a750 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,9 +1,9 @@ import { configureStore } from "@reduxjs/toolkit"; -import productsReducer from "./productsSlice"; +import cartReducer from "./cart"; const store = configureStore({ reducer: { - products: productsReducer, + cart: cartReducer, }, }); From f084baac88e5841fe1de0db208e66d1497a599d1 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:01:55 +0900 Subject: [PATCH 08/25] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartList.tsx | 8 ++++++++ src/components/{carts => cart}/CartView.tsx | 1 - src/components/carts/CartList.tsx | 19 ------------------- 3 files changed, 8 insertions(+), 20 deletions(-) create mode 100644 src/components/cart/CartList.tsx rename src/components/{carts => cart}/CartView.tsx (96%) delete mode 100644 src/components/carts/CartList.tsx diff --git a/src/components/cart/CartList.tsx b/src/components/cart/CartList.tsx new file mode 100644 index 0000000..c3691f0 --- /dev/null +++ b/src/components/cart/CartList.tsx @@ -0,0 +1,8 @@ +import { Link } from "react-router-dom"; +import { toCurrencyFormat } from "../../utils/util"; + +const CartList = (): JSX.Element => { + return
{/* 카트 리스트 화면을 구성 해보세요. */}
; +}; + +export default CartList; diff --git a/src/components/carts/CartView.tsx b/src/components/cart/CartView.tsx similarity index 96% rename from src/components/carts/CartView.tsx rename to src/components/cart/CartView.tsx index 0603b74..5bbbd51 100644 --- a/src/components/carts/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Link } from "react-router-dom"; import BreadCrumb from "../common/Breadcrumb"; import Confirm from "../common/Confirm"; diff --git a/src/components/carts/CartList.tsx b/src/components/carts/CartList.tsx deleted file mode 100644 index 84ae7eb..0000000 --- a/src/components/carts/CartList.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import { ICartState, cartState, removeFromCart } from "../../store/cart"; -import { toCurrencyFormat } from "../../utils/util"; -import { useRecoilState } from "recoil"; - -const CartList = (): JSX.Element => { - // Recoil을 사용해서 cart데이터를 가져오는 예제입니다. - const [cart, setCart] = useRecoilState(cartState); - - // store/cart.ts를 참고하세요. - const removeFromCartHandler = (id: string) => { - setCart(removeFromCart(cart, id)); - }; - - return
{/* 카트 리스트 화면을 구성 해보세요. */}
; -}; - -export default CartList; From 153236910a358daa65fb56457deacbe8fd7be952 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:43:07 +0900 Subject: [PATCH 09/25] =?UTF-8?q?refactor:=20=EC=83=81=ED=92=88=20api=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/fetchProductsData.ts | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/api/fetchProductsData.ts b/api/fetchProductsData.ts index c848049..458523a 100644 --- a/api/fetchProductsData.ts +++ b/api/fetchProductsData.ts @@ -1,6 +1,17 @@ import { AxiosRequestConfig } from "axios"; +import { useQuery } from "@tanstack/react-query"; import { axiosInstance } from "./../src/utils/axiosInstance"; +export interface IProduct { + readonly id: number; + readonly title: string; + readonly description: string; + readonly category: string; + readonly price: number; + readonly image: string; + readonly rating: IRating; +} + export interface Item extends AxiosRequestConfig { id: number; title: string; @@ -10,13 +21,18 @@ export interface Item extends AxiosRequestConfig { image: string; } -const fetchProductsData = async () => { - try { - const res = await axiosInstance.get("products"); - return res.data; - } catch (error) { - throw new Error(`"Error fetching data:", ${error}`); - } +interface IRating { + readonly rate?: number; + readonly count?: number; +} + +const fetchProductsData = async (): Promise => { + const response = await axiosInstance.get("products"); + return response.data; +}; + +export const useProducts = () => { + return useQuery({ queryKey: ["products"], queryFn: fetchProductsData }); }; export default fetchProductsData; From cba8b0dab4c48274884c45f5b03f0d0f2fbd3497 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:44:34 +0900 Subject: [PATCH 10/25] =?UTF-8?q?refactor:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=B7=B0=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/products/ProductDetail.tsx | 10 ++++++++-- src/router/router.tsx | 4 ++-- src/store/products.ts | 20 ++++++++++++++++++++ src/views/Product.tsx | 10 +++++----- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/store/products.ts diff --git a/src/components/products/ProductDetail.tsx b/src/components/products/ProductDetail.tsx index ec82c36..b87f197 100644 --- a/src/components/products/ProductDetail.tsx +++ b/src/components/products/ProductDetail.tsx @@ -1,7 +1,12 @@ +import { useDispatch } from "react-redux"; +import { addToCart } from "../../store/cart"; import Rating from "../common/Rating"; const ProductDetail = ({ product }): JSX.Element => { if (product === undefined) return
; + + const dispatch = useDispatch(); + console.log(product); return (
@@ -15,11 +20,12 @@ const ProductDetail = ({ product }): JSX.Element => { NEW

{product.description}

-

$ {product?.price}

- + 장바구니로 이동 diff --git a/src/router/router.tsx b/src/router/router.tsx index 2f4cf66..c085712 100644 --- a/src/router/router.tsx +++ b/src/router/router.tsx @@ -1,11 +1,11 @@ import { Routes, Route } from "react-router-dom"; -import React, { memo } from "react"; +import { memo } from "react"; import Error from "../views/Error"; import Index from "../views/Index"; import Fashion from "../views/Fashion"; import Accessory from "../views/Accessory"; import Digital from "../views/Digital"; -import CartView from "../components/carts/CartView"; +import CartView from "../components/cart/CartView"; import Product from "../views/Product"; const Router = (): JSX.Element => { diff --git a/src/store/products.ts b/src/store/products.ts new file mode 100644 index 0000000..e5e8656 --- /dev/null +++ b/src/store/products.ts @@ -0,0 +1,20 @@ +import CONSTANTS from "../constants/constants"; + +const productsURL = `${CONSTANTS.IS_DEV ? `/proxy` : `${import.meta.env.VITE_FAKE_STORE_API}`}/products`; + +/** + * productList는 API 1회 요청 후에 유지됩니다. + * 디테일 페이지에서는 productDetail/id로 각각 호출하셔도 무방합니다. + */ +// export const productsList = selector({ +// key: "productsList", +// get: async () => { +// try { +// const response = await fetch(productsURL); +// return (await response.json()) || []; +// } catch (error) { +// console.log(`Error: ${error}`); +// return []; +// } +// }, +// }); diff --git a/src/views/Product.tsx b/src/views/Product.tsx index 78f437b..a9ce892 100644 --- a/src/views/Product.tsx +++ b/src/views/Product.tsx @@ -1,15 +1,15 @@ -import React, { useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import BreadCrumb from "../components/common/Breadcrumb"; import ProductDetail from "../components/products/ProductDetail"; -import { IProduct, productsList } from "../store/products"; -import { useRecoilValue } from "recoil"; +import { IProduct, useProducts } from "../../api/fetchProductsData"; const Product = (): JSX.Element => { - const productListData = useRecoilValue(productsList); + const { data: productListData, isLoading, error } = useProducts(); const [selectedProduct, setSelectedProduct] = useState(null); + useEffect(() => { const productId = Number(window.location.pathname.split("/")[2]); - const selectedProduct = productListData.find((product) => product.id === productId); + const selectedProduct = productListData?.find((product) => product.id === productId); setSelectedProduct(selectedProduct || null); }, [productListData]); From 82f74300f879f91a0a16467b3bd407b5ff12ce9a Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:48:18 +0900 Subject: [PATCH 11/25] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20recoil=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Confirm.tsx | 13 ++++++------- src/components/layout/Nav.tsx | 1 - src/views/Index.tsx | 1 - 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/common/Confirm.tsx b/src/components/common/Confirm.tsx index 97e7a6c..cbc8bc8 100644 --- a/src/components/common/Confirm.tsx +++ b/src/components/common/Confirm.tsx @@ -1,10 +1,9 @@ -import { useRecoilState } from "recoil"; -import React from "react"; -import { ICartState, cartState } from "../../store/cart"; +//import { useRecoilState } from "recoil"; +//import { ICartState, cartState } from "../../store/cart"; const Confirm = (): JSX.Element => { - const [cart, setCart] = useRecoilState(cartState); - const buyItems = () => setCart({} as ICartState); + // const [cart, setCart] = useState(cartState); + //const buyItems = () => setCart({} as ICartState); return ( <> @@ -13,9 +12,9 @@ const Confirm = (): JSX.Element => {

정말로 구매하시겠습니까?

장바구니의 모든 상품들이 삭제됩니다.

- */} diff --git a/src/components/layout/Nav.tsx b/src/components/layout/Nav.tsx index 0c9db90..a057a0f 100644 --- a/src/components/layout/Nav.tsx +++ b/src/components/layout/Nav.tsx @@ -1,6 +1,5 @@ import React from "react"; import countState from "../../atom"; -import { RecoilState } from "recoil"; import bars from "../../assets/img/svg/bars.svg"; import sun from "../../assets/img/svg/sun.svg"; import moon from "../../assets/img/svg/moon.svg"; diff --git a/src/views/Index.tsx b/src/views/Index.tsx index 57acfb3..1a79a94 100644 --- a/src/views/Index.tsx +++ b/src/views/Index.tsx @@ -1,5 +1,4 @@ import Slider from "../components/common/Slider"; -import { useRecoilState } from "recoil"; import ItemList from "../components/products/ItemList"; const fashionFilter = (index) => index >= 0 && index <= 3; From b4f808dfecb175d35bba9330f5c57dbb32b1795f Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:52:28 +0900 Subject: [PATCH 12/25] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EB=8B=B4=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/products/ProductDetail.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/products/ProductDetail.tsx b/src/components/products/ProductDetail.tsx index b87f197..f07301e 100644 --- a/src/components/products/ProductDetail.tsx +++ b/src/components/products/ProductDetail.tsx @@ -23,7 +23,17 @@ const ProductDetail = ({ product }): JSX.Element => {

$ {product?.price}

- From 310f3d698ca7955d1917fd360960f5d1a11a8071 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Sun, 4 Aug 2024 18:57:53 +0900 Subject: [PATCH 13/25] =?UTF-8?q?feat:=20=EC=B9=B4=ED=8A=B8=20store=20type?= =?UTF-8?q?=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/store/index.ts b/src/store/index.ts index b64a750..a62e32e 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -7,4 +7,6 @@ const store = configureStore({ }, }); +export type AppDispatch = typeof store.dispatch; +export type RootState = ReturnType; export default store; From 61b2c7602c99302afac013bd01b13a8177024671 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:50:04 +0900 Subject: [PATCH 14/25] =?UTF-8?q?refactor:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EB=8B=B4=EB=8A=94=20item=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartView.tsx | 23 +++++++++++++++-------- src/components/products/ProductDetail.tsx | 8 ++++++-- src/store/cart.ts | 14 +++++++------- src/store/products.ts | 20 -------------------- 4 files changed, 28 insertions(+), 37 deletions(-) delete mode 100644 src/store/products.ts diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index 5bbbd51..969db2f 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -1,20 +1,27 @@ import { Link } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; import BreadCrumb from "../common/Breadcrumb"; import Confirm from "../common/Confirm"; +import { AppDispatch, RootState } from "../../store"; const CartView = (): JSX.Element => { + const dispatch: AppDispatch = useDispatch(); + const { items, totalAmount } = useSelector((state: RootState) => state.cart); + console.log(items); return (
- {/* 물품이 없다면? */} -
-

장바구니에 물품이 없습니다.

- - 담으러 가기 - -
- {/* 구매하기 버튼 등 화면을 구성 해보세요. */} + {!items.length ? ( +
+

장바구니에 물품이 없습니다.

+ + 담으러 가기 + +
+ ) : ( +
+ )}
diff --git a/src/components/products/ProductDetail.tsx b/src/components/products/ProductDetail.tsx index f07301e..b539aba 100644 --- a/src/components/products/ProductDetail.tsx +++ b/src/components/products/ProductDetail.tsx @@ -1,11 +1,12 @@ import { useDispatch } from "react-redux"; import { addToCart } from "../../store/cart"; +import { AppDispatch } from "../../store"; import Rating from "../common/Rating"; const ProductDetail = ({ product }): JSX.Element => { if (product === undefined) return
; - const dispatch = useDispatch(); + const dispatch: AppDispatch = useDispatch(); console.log(product); return (
@@ -28,8 +29,11 @@ const ProductDetail = ({ product }): JSX.Element => { onClick={() => dispatch( addToCart({ - ...product, + id: product.id, + title: product.title, + price: product.price, count: 1, + image: product.image, }) ) } diff --git a/src/store/cart.ts b/src/store/cart.ts index 0c03269..dcc9c94 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -1,13 +1,8 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { CART_ITEM } from "../constants/category"; -export interface ICartInfo { - readonly id: number; - readonly count: number; -} - export interface ICartItems { - readonly id: string; + readonly id: number; readonly title: string; readonly price: number; readonly count: number; @@ -44,18 +39,23 @@ const cartSlice = createSlice({ initialState, reducers: { addToCart(state, action: PayloadAction) { + console.log("Action dispatched:", action.payload); const newItem = action.payload; const existingItem = state.items.find((item) => item.id === newItem.id); + if (existingItem) { existingItem.count += newItem.count; } else { state.items.push(newItem); } + state.totalAmount += newItem.price * newItem.count; + console.log("Updated cart state:", state); }, - removeFromCart(state, action: PayloadAction) { + removeFromCart(state, action: PayloadAction) { const id = action.payload; const existingItem = state.items.find((item) => item.id === id); + if (existingItem) { state.totalAmount -= existingItem.price * existingItem.count; state.items = state.items.filter((item) => item.id !== id); diff --git a/src/store/products.ts b/src/store/products.ts deleted file mode 100644 index e5e8656..0000000 --- a/src/store/products.ts +++ /dev/null @@ -1,20 +0,0 @@ -import CONSTANTS from "../constants/constants"; - -const productsURL = `${CONSTANTS.IS_DEV ? `/proxy` : `${import.meta.env.VITE_FAKE_STORE_API}`}/products`; - -/** - * productList는 API 1회 요청 후에 유지됩니다. - * 디테일 페이지에서는 productDetail/id로 각각 호출하셔도 무방합니다. - */ -// export const productsList = selector({ -// key: "productsList", -// get: async () => { -// try { -// const response = await fetch(productsURL); -// return (await response.json()) || []; -// } catch (error) { -// console.log(`Error: ${error}`); -// return []; -// } -// }, -// }); From b4657c4b3d9b1ffafea76ef900882475399ff92b Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:58:16 +0900 Subject: [PATCH 15/25] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=EB=8B=B4?= =?UTF-8?q?=EC=9D=80=20cart=20=EB=B7=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartView.tsx | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index 969db2f..e48bd21 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -20,7 +20,27 @@ const CartView = (): JSX.Element => {
) : ( -
+
+
    + {items.map((item, index) => ( +
  • +
    + {item.title} +
    +
    + {item.title} + {item.price} + {item.count} +
    +
  • + ))} + +
+
+ {totalAmount.toFixed(2)} + +
+
)}
From 81a4a90bb4969a7d46417ad1d05048222387cd2a Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:22:01 +0900 Subject: [PATCH 16/25] =?UTF-8?q?feat:=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartView.tsx | 2 +- src/store/cart.ts | 43 ++++++-------------------------- 2 files changed, 8 insertions(+), 37 deletions(-) diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index e48bd21..151b0b4 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -7,7 +7,7 @@ import { AppDispatch, RootState } from "../../store"; const CartView = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const { items, totalAmount } = useSelector((state: RootState) => state.cart); - console.log(items); + console.log(items, totalAmount); return (
diff --git a/src/store/cart.ts b/src/store/cart.ts index dcc9c94..65e0fdc 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -1,5 +1,4 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { CART_ITEM } from "../constants/category"; export interface ICartItems { readonly id: number; @@ -15,25 +14,10 @@ export interface ICartState { } const initialState: ICartState = { - items: [], + items: JSON.parse(localStorage.getItem("cartItems") || "[]"), totalAmount: 0, }; -/** - * 카트의 상태는 localStorage 기준으로 초기화 됩니다. - * 카트의 상태는 새로고침해도 유지되어야 하기 때문입니다. - */ -// export const cartState = atom({ -// key: "cart", -// default: {}, -// effects: [ -// ({ setSelf, onSet }) => { -// localStorage.getItem(CART_ITEM) && setSelf(JSON.parse(localStorage.getItem(CART_ITEM) as string)); -// onSet((value) => localStorage.setItem(CART_ITEM, JSON.stringify(value))); -// }, -// ], -// }); - const cartSlice = createSlice({ name: "cart", initialState, @@ -48,9 +32,10 @@ const cartSlice = createSlice({ } else { state.items.push(newItem); } - + console.log("Before updating totalAmount"); state.totalAmount += newItem.price * newItem.count; console.log("Updated cart state:", state); + localStorage.setItem("cartItems", JSON.stringify(state.items)); }, removeFromCart(state, action: PayloadAction) { const id = action.payload; @@ -59,32 +44,18 @@ const cartSlice = createSlice({ if (existingItem) { state.totalAmount -= existingItem.price * existingItem.count; state.items = state.items.filter((item) => item.id !== id); + + localStorage.setItem("cartItems", JSON.stringify(state.items)); } }, clearCart(state) { state.items = []; state.totalAmount = 0; + + localStorage.setItem("cartItems", JSON.stringify([])); }, }, }); -/** - * cartList를 구현 하세요. - * id, image, count 등을 return합니다. - */ - -// addToCart는 구현 해보세요. - -// removeFromCart는 참고 하세요. -// export const removeFromCart = (cart: ICartState, id: string) => { -// const tempCart = { ...cart }; -// if (tempCart[id].count === 1) { -// delete tempCart[id]; -// return tempCart; -// } else { -// return { ...tempCart, [id]: { id: id, count: cart[id].count - 1 } }; -// } -// }; - export const { addToCart, removeFromCart, clearCart } = cartSlice.actions; export default cartSlice.reducer; From 6ce718a3c692bf00ec5b67ba3c1b83a49ef598b8 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:47:40 +0900 Subject: [PATCH 17/25] =?UTF-8?q?design:=20=EC=83=81=ED=92=88=20=EB=8B=B4?= =?UTF-8?q?=EC=9D=80=20cart=20=EB=B7=B0=20css=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartView.tsx | 38 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index 151b0b4..c3b4e12 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -7,7 +7,7 @@ import { AppDispatch, RootState } from "../../store"; const CartView = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const { items, totalAmount } = useSelector((state: RootState) => state.cart); - console.log(items, totalAmount); + console.log(totalAmount); return (
@@ -20,25 +20,31 @@ const CartView = (): JSX.Element => {
) : ( -
+
    - {items.map((item, index) => ( -
  • -
    - {item.title} -
    -
    - {item.title} - {item.price} - {item.count} + {items.map((item) => ( +
    +
    + {item.title} +
    +
    +

    {item.title}

    +
    + ${item.price} +
    +
    + + + +
    +
    -
  • +
))} - -
- {totalAmount.toFixed(2)} - +
+ 총: ${totalAmount.toFixed(2)} +
)} From 900f6cbea0ef22d9426ab40c4129818f5d90eee6 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:12:22 +0900 Subject: [PATCH 18/25] =?UTF-8?q?feat:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EC=B4=9D=EC=95=A1=20=EB=A1=9C=EC=BB=AC=20=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/cart.ts | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/store/cart.ts b/src/store/cart.ts index 65e0fdc..601ddf4 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -14,13 +14,39 @@ export interface ICartState { } const initialState: ICartState = { - items: JSON.parse(localStorage.getItem("cartItems") || "[]"), + items: [], totalAmount: 0, }; +// localStorage에서 카트 데이터를 불러오는 함수 +const loadCartFromLocalStorage = (): ICartState => { + const savedCart = localStorage.getItem("cartItems"); + if (savedCart) { + try { + const { items, totalAmount } = JSON.parse(savedCart); + return { + items: items || [], + totalAmount: totalAmount || 0, + }; + } catch (error) { + console.error("Failed to parse cart data from localStorage", error); + return initialState; + } + } + return initialState; +}; + +const saveCartToLocalStorage = (state: ICartState) => { + const cartData = { + items: state.items, + totalAmount: state.totalAmount, + }; + localStorage.setItem("cartItems", JSON.stringify(cartData)); +}; + const cartSlice = createSlice({ name: "cart", - initialState, + initialState: loadCartFromLocalStorage(), reducers: { addToCart(state, action: PayloadAction) { console.log("Action dispatched:", action.payload); @@ -32,10 +58,9 @@ const cartSlice = createSlice({ } else { state.items.push(newItem); } - console.log("Before updating totalAmount"); - state.totalAmount += newItem.price * newItem.count; - console.log("Updated cart state:", state); - localStorage.setItem("cartItems", JSON.stringify(state.items)); + state.totalAmount = state.items.reduce((total, item) => total + item.price * item.count, 0); + + saveCartToLocalStorage(state); }, removeFromCart(state, action: PayloadAction) { const id = action.payload; @@ -45,14 +70,14 @@ const cartSlice = createSlice({ state.totalAmount -= existingItem.price * existingItem.count; state.items = state.items.filter((item) => item.id !== id); - localStorage.setItem("cartItems", JSON.stringify(state.items)); + saveCartToLocalStorage(state); } }, clearCart(state) { state.items = []; state.totalAmount = 0; - localStorage.setItem("cartItems", JSON.stringify([])); + saveCartToLocalStorage(state); }, }, }); From 850399f3ed346f6e9c4ce3b7ee05141a31cf3497 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:54:39 +0900 Subject: [PATCH 19/25] =?UTF-8?q?feat:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EC=88=98=EB=9F=89=20=EC=A1=B0=EC=A0=88=20=EC=95=A1?= =?UTF-8?q?=EC=85=98=20=ED=95=A8=EC=88=98=20=EB=82=B4=EB=B3=B4=EB=82=B4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartView.tsx | 19 +++++++++++++--- src/store/cart.ts | 37 ++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index c3b4e12..336a730 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -3,11 +3,20 @@ import { useDispatch, useSelector } from "react-redux"; import BreadCrumb from "../common/Breadcrumb"; import Confirm from "../common/Confirm"; import { AppDispatch, RootState } from "../../store"; +import { increaseItemCount, decreaseItemCount } from "../../store/cart"; const CartView = (): JSX.Element => { const dispatch: AppDispatch = useDispatch(); const { items, totalAmount } = useSelector((state: RootState) => state.cart); - console.log(totalAmount); + + const handleAddCount = (id: number) => { + dispatch(increaseItemCount(id)); + }; + + const handleDecreaseCount = (id: number) => { + dispatch(decreaseItemCount(id)); + }; + return (
@@ -33,9 +42,13 @@ const CartView = (): JSX.Element => { ${item.price}
- + - +

diff --git a/src/store/cart.ts b/src/store/cart.ts index 601ddf4..614dc59 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -1,15 +1,15 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; export interface ICartItems { - readonly id: number; - readonly title: string; - readonly price: number; - readonly count: number; - readonly image: string; + id: number; + title: string; + price: number; + count: number; + image: string; } export interface ICartState { - readonly items: ICartItems[]; + items: ICartItems[]; totalAmount: number; } @@ -73,6 +73,29 @@ const cartSlice = createSlice({ saveCartToLocalStorage(state); } }, + increaseItemCount(state, action: PayloadAction) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + + if (existingItem) { + existingItem.count += 1; + state.totalAmount = state.items.reduce((total, item) => total + item.price * item.count, 0); + saveCartToLocalStorage(state); + } + }, + decreaseItemCount(state, action: PayloadAction) { + const id = action.payload; + const existingItem = state.items.find((item) => item.id === id); + + if (existingItem) { + existingItem.count -= 1; + if (existingItem.count <= 0) { + state.items = state.items.filter((item) => item.id !== id); + } + state.totalAmount = state.items.reduce((total, item) => total + item.price * item.count, 0); + saveCartToLocalStorage(state); + } + }, clearCart(state) { state.items = []; state.totalAmount = 0; @@ -82,5 +105,5 @@ const cartSlice = createSlice({ }, }); -export const { addToCart, removeFromCart, clearCart } = cartSlice.actions; +export const { addToCart, removeFromCart, clearCart, increaseItemCount, decreaseItemCount } = cartSlice.actions; export default cartSlice.reducer; From 62ea7dad282a93b5f85b68dac9a9781dcce3ec39 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:01:53 +0900 Subject: [PATCH 20/25] =?UTF-8?q?design:=20=EC=9E=A5=EB=B0=94=EA=B5=AC?= =?UTF-8?q?=EB=8B=88=20=EC=83=81=ED=92=88=20=EB=8B=B4=EA=B8=B4=20=EB=B7=B0?= =?UTF-8?q?=20css=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartView.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index 336a730..a76177e 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -33,7 +33,7 @@ const CartView = (): JSX.Element => {
    {items.map((item) => (
    -
    +
    {item.title}
    @@ -50,7 +50,6 @@ const CartView = (): JSX.Element => { +
    -
))} From 786297578a44472abeb49f07a6849260949d5870 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:11:13 +0900 Subject: [PATCH 21/25] =?UTF-8?q?feat:=20nav=EB=B0=94=20=EC=9E=A5=EB=B0=94?= =?UTF-8?q?=EA=B5=AC=EB=8B=88=20=EC=88=98=EB=9F=89=20=ED=91=9C=EC=8B=9C=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/components/layout/Nav.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/components/layout/Nav.tsx b/src/components/layout/Nav.tsx index a057a0f..fd6a428 100644 --- a/src/components/layout/Nav.tsx +++ b/src/components/layout/Nav.tsx @@ -5,8 +5,14 @@ import sun from "../../assets/img/svg/sun.svg"; import moon from "../../assets/img/svg/moon.svg"; import magnifyingGlass from "../../assets/img/svg/magnifying-glass.svg"; import cart from "../../assets/img/svg/cart-shopping-solid.svg"; +import { useDispatch, useSelector } from "react-redux"; +import { AppDispatch, RootState } from "../../store"; const Nav = () => { + const { items, totalAmount } = useSelector((state: RootState) => state.cart); + + const totalCount = items.reduce((total, item) => total + item.count, 0); + return (
From 7dde88c54b62a6acbcc267c7a907b8429a6a7a75 Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:14:42 +0900 Subject: [PATCH 22/25] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout/Nav.tsx | 8 +++----- src/components/products/ProductDetail.tsx | 2 +- src/store/cart.ts | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/layout/Nav.tsx b/src/components/layout/Nav.tsx index fd6a428..8b03f0d 100644 --- a/src/components/layout/Nav.tsx +++ b/src/components/layout/Nav.tsx @@ -1,15 +1,13 @@ -import React from "react"; -import countState from "../../atom"; import bars from "../../assets/img/svg/bars.svg"; import sun from "../../assets/img/svg/sun.svg"; import moon from "../../assets/img/svg/moon.svg"; import magnifyingGlass from "../../assets/img/svg/magnifying-glass.svg"; import cart from "../../assets/img/svg/cart-shopping-solid.svg"; -import { useDispatch, useSelector } from "react-redux"; -import { AppDispatch, RootState } from "../../store"; +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; const Nav = () => { - const { items, totalAmount } = useSelector((state: RootState) => state.cart); + const { items } = useSelector((state: RootState) => state.cart); const totalCount = items.reduce((total, item) => total + item.count, 0); diff --git a/src/components/products/ProductDetail.tsx b/src/components/products/ProductDetail.tsx index b539aba..c303d5b 100644 --- a/src/components/products/ProductDetail.tsx +++ b/src/components/products/ProductDetail.tsx @@ -7,7 +7,7 @@ const ProductDetail = ({ product }): JSX.Element => { if (product === undefined) return
; const dispatch: AppDispatch = useDispatch(); - console.log(product); + return (
diff --git a/src/store/cart.ts b/src/store/cart.ts index 614dc59..fa3a4e4 100644 --- a/src/store/cart.ts +++ b/src/store/cart.ts @@ -18,7 +18,6 @@ const initialState: ICartState = { totalAmount: 0, }; -// localStorage에서 카트 데이터를 불러오는 함수 const loadCartFromLocalStorage = (): ICartState => { const savedCart = localStorage.getItem("cartItems"); if (savedCart) { From b4d832d420a9f2d25ea0d64707c713432ffea19d Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:20:16 +0900 Subject: [PATCH 23/25] =?UTF-8?q?style:=20=EC=B9=B4=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartList.tsx | 52 ++++++++++++++++++++-- src/components/cart/CartView.tsx | 48 +++----------------- src/utils/{util.ts => toCurrencyFormat.ts} | 4 -- 3 files changed, 54 insertions(+), 50 deletions(-) rename src/utils/{util.ts => toCurrencyFormat.ts} (67%) diff --git a/src/components/cart/CartList.tsx b/src/components/cart/CartList.tsx index c3691f0..5aaee14 100644 --- a/src/components/cart/CartList.tsx +++ b/src/components/cart/CartList.tsx @@ -1,8 +1,54 @@ -import { Link } from "react-router-dom"; -import { toCurrencyFormat } from "../../utils/util"; +import { useDispatch, useSelector } from "react-redux"; +import { AppDispatch, RootState } from "../../store"; +import { increaseItemCount, decreaseItemCount } from "../../store/cart"; +import { toCurrencyFormat } from "../../utils/toCurrencyFormat"; const CartList = (): JSX.Element => { - return
{/* 카트 리스트 화면을 구성 해보세요. */}
; + const dispatch: AppDispatch = useDispatch(); + const { items, totalAmount } = useSelector((state: RootState) => state.cart); + + const handleAddCount = (id: number) => { + dispatch(increaseItemCount(id)); + }; + + const handleDecreaseCount = (id: number) => { + dispatch(decreaseItemCount(id)); + }; + + return ( +
+
+
    + {items.map((item) => ( +
    +
    + {item.title} +
    +
    +

    {item.title}

    +
    + ${item.price} +
    +
    + + + +
    +
    +
    + ))} +
+
+ 총: ${totalAmount.toFixed(2)} + +
+
+
+ ); }; export default CartList; diff --git a/src/components/cart/CartView.tsx b/src/components/cart/CartView.tsx index a76177e..88eb2fd 100644 --- a/src/components/cart/CartView.tsx +++ b/src/components/cart/CartView.tsx @@ -1,21 +1,12 @@ import { Link } from "react-router-dom"; -import { useDispatch, useSelector } from "react-redux"; +import { useSelector } from "react-redux"; import BreadCrumb from "../common/Breadcrumb"; import Confirm from "../common/Confirm"; -import { AppDispatch, RootState } from "../../store"; -import { increaseItemCount, decreaseItemCount } from "../../store/cart"; +import { RootState } from "../../store"; +import CartList from "./CartList"; const CartView = (): JSX.Element => { - const dispatch: AppDispatch = useDispatch(); - const { items, totalAmount } = useSelector((state: RootState) => state.cart); - - const handleAddCount = (id: number) => { - dispatch(increaseItemCount(id)); - }; - - const handleDecreaseCount = (id: number) => { - dispatch(decreaseItemCount(id)); - }; + const { items } = useSelector((state: RootState) => state.cart); return (
@@ -29,36 +20,7 @@ const CartView = (): JSX.Element => {
) : ( -
-
    - {items.map((item) => ( -
    -
    - {item.title} -
    -
    -

    {item.title}

    -
    - ${item.price} -
    -
    - - - -
    -
    -
    - ))} -
-
- 총: ${totalAmount.toFixed(2)} - -
-
+ )}
diff --git a/src/utils/util.ts b/src/utils/toCurrencyFormat.ts similarity index 67% rename from src/utils/util.ts rename to src/utils/toCurrencyFormat.ts index c6fb1f3..1cabfd7 100644 --- a/src/utils/util.ts +++ b/src/utils/toCurrencyFormat.ts @@ -1,7 +1,3 @@ -/* - * 여러가지 util들을 추가하는 파일입니다. - * util.ts라고 하셔도 됩니다. - */ const currencyFormat = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", From 0babd690fbc0c68c4b4499b118b8f283636580ba Mon Sep 17 00:00:00 2001 From: Seoin <144193370+Seoin02@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:26:25 +0900 Subject: [PATCH 24/25] =?UTF-8?q?style:=20=EB=8B=AC=EB=9F=AC=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8=EC=88=98=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/cart/CartList.tsx | 4 ++-- src/components/products/ItemList.tsx | 4 +++- src/components/products/ProductDetail.tsx | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/components/cart/CartList.tsx b/src/components/cart/CartList.tsx index 5aaee14..3c7d2e4 100644 --- a/src/components/cart/CartList.tsx +++ b/src/components/cart/CartList.tsx @@ -27,7 +27,7 @@ const CartList = (): JSX.Element => {

{item.title}


- ${item.price} + {toCurrencyFormat(item.price)}
diff --git a/src/components/products/ItemList.tsx b/src/components/products/ItemList.tsx index 89630a4..6e6b365 100644 --- a/src/components/products/ItemList.tsx +++ b/src/components/products/ItemList.tsx @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import fetchProductsData from "../../../api/fetchProductsData"; import { Item } from "../../../api/fetchProductsData"; +import { toCurrencyFormat } from "../../utils/toCurrencyFormat"; const ItemList = ({ categoryName, filterItem }: { categoryName?: string; filterItem?: (index: number) => boolean }) => { const { data, error, isLoading } = useQuery({ @@ -12,6 +13,7 @@ const ItemList = ({ categoryName, filterItem }: { categoryName?: string; filterI if (error) return
Error: {error.message}
; if (data === undefined) return
; + return (
{data @@ -30,7 +32,7 @@ const ItemList = ({ categoryName, filterItem }: { categoryName?: string; filterI
{item.title}
-
${item.price}
+
{toCurrencyFormat(item.price)}
))} diff --git a/src/components/products/ProductDetail.tsx b/src/components/products/ProductDetail.tsx index c303d5b..8ed44af 100644 --- a/src/components/products/ProductDetail.tsx +++ b/src/components/products/ProductDetail.tsx @@ -2,6 +2,7 @@ import { useDispatch } from "react-redux"; import { addToCart } from "../../store/cart"; import { AppDispatch } from "../../store"; import Rating from "../common/Rating"; +import { toCurrencyFormat } from "../../utils/toCurrencyFormat"; const ProductDetail = ({ product }): JSX.Element => { if (product === undefined) return
; @@ -22,7 +23,7 @@ const ProductDetail = ({ product }): JSX.Element => {

{product.description}

-

$ {product?.price}

+

{toCurrencyFormat(product?.price)}

-
+
-
+ /> +