From 875a0815af795093e9cea7e88870887f49a83bff Mon Sep 17 00:00:00 2001 From: niyobertin Date: Fri, 19 Jul 2024 13:53:11 +0200 Subject: [PATCH] feat(wishes):buyer should be able wish a products -Buyer should wish a products -Buyer should add a wished products to cart -Seller should be able to see a wished products [Deliver #187419141] --- package-lock.json | 179 ++++++++++-------- package.json | 1 + src/__test__/improveTest.test.tsx | 84 ++++++++ src/__test__/wishListSlice.test.tsx | 106 +++++++++++ src/components/cards/ProductCard.tsx | 21 +- src/components/common/header/Header.tsx | 32 +++- src/components/dashboard/SideBar.tsx | 7 +- .../dashboard/products/wishedProducts.tsx | 72 +++++++ src/components/dashboard/wishesTable.tsx | 158 ++++++++++++++++ src/dashboard/sellers/wishesList.tsx | 18 ++ src/pages/ReviewList.tsx | 4 +- src/pages/Wishes.tsx | 43 +++-- src/pages/updateProfile.tsx | 17 +- src/pages/userProfile.tsx | 26 ++- src/redux/reducers/reviewSlice.ts | 3 + src/routes/AppRoutes.tsx | 4 + type.d.ts | 1 + 17 files changed, 653 insertions(+), 123 deletions(-) create mode 100644 src/__test__/improveTest.test.tsx create mode 100644 src/__test__/wishListSlice.test.tsx create mode 100644 src/components/dashboard/products/wishedProducts.tsx create mode 100644 src/components/dashboard/wishesTable.tsx create mode 100644 src/dashboard/sellers/wishesList.tsx diff --git a/package-lock.json b/package-lock.json index 83021a8..67f1772 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "axios-mock-adapter": "^1.22.0", "eslint-config-airbnb-typescript": "^18.0.0", "expect-puppeteer": "^10.0.0", + "flowbite-react": "^0.10.1", "gsap": "^3.12.5", "install": "^0.13.0", "jest-environment-jsdom": "^29.7.0", @@ -109,7 +110,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -1553,6 +1553,20 @@ "@floating-ui/utils": "^0.2.3" } }, + "node_modules/@floating-ui/react": { + "version": "0.26.17", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.17.tgz", + "integrity": "sha512-ESD+jYWwqwVzaIgIhExrArdsCL1rOAzryG/Sjlu8yaD3Mtqi3uVyhbE2V7jD58Mo52qbzKz2eUY/Xgh5I86FCQ==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.0", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", @@ -1831,7 +1845,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1848,7 +1861,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -1860,7 +1872,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -1872,7 +1883,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1889,7 +1899,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1904,7 +1913,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -2870,7 +2878,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -4564,8 +4571,7 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", @@ -4582,8 +4588,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -5078,7 +5083,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "engines": { "node": ">=8" }, @@ -5199,7 +5203,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -5247,7 +5250,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -5271,7 +5273,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -5298,6 +5299,11 @@ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -5755,7 +5761,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "bin": { "cssesc": "bin/cssesc" }, @@ -5876,6 +5881,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.1.0.tgz", + "integrity": "sha512-OkL3+0pPWCqoBc/nhO9u6TIQNTK44fnBnzuVtJAbp13Naxw9R6u21x+8tVTka87AhDZ3htqZ2pSSsZl9fqL2Wg==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/debug": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", @@ -6014,8 +6030,7 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/diff": { "version": "4.0.2", @@ -6048,8 +6063,7 @@ "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/doctrine": { "version": "3.0.0", @@ -6103,8 +6117,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { "version": "1.4.812", @@ -6125,8 +6138,7 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/engine.io-client": { "version": "6.5.4", @@ -7352,6 +7364,42 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, + "node_modules/flowbite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.3.0.tgz", + "integrity": "sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==", + "dependencies": { + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" + } + }, + "node_modules/flowbite-react": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.10.1.tgz", + "integrity": "sha512-T6rdfrEvIqrf7aIB+OLkuvDaa/h0Ufnl7/5vJR9JJ4IpKIvJm/JzhAiYmkD+jDj3HuILsN21+ZVV6gd4tlndYQ==", + "dependencies": { + "@floating-ui/core": "1.6.2", + "@floating-ui/react": "0.26.17", + "classnames": "2.5.1", + "debounce": "2.1.0", + "flowbite": "2.3.0", + "react-icons": "5.2.1", + "tailwind-merge": "2.3.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "tailwindcss": "^3" + } + }, + "node_modules/flowbite-react/node_modules/@floating-ui/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "dependencies": { + "@floating-ui/utils": "^0.2.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -7383,7 +7431,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -8108,7 +8155,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -8587,7 +8633,6 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -10024,7 +10069,6 @@ "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "dev": true, "bin": { "jiti": "bin/jiti.js" } @@ -10230,7 +10274,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, "engines": { "node": ">=14" }, @@ -10600,6 +10643,14 @@ "node": ">=4" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -10626,7 +10677,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -10761,7 +10811,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -13297,7 +13346,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -13542,8 +13590,7 @@ "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -13618,7 +13665,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -13634,7 +13680,6 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, "engines": { "node": "14 || >=16.14" } @@ -13685,7 +13730,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13804,7 +13848,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -13821,7 +13864,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -13840,7 +13882,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -13875,7 +13916,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", - "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.11" }, @@ -13894,7 +13934,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -13906,8 +13945,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -14237,7 +14275,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -14246,7 +14283,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -14672,7 +14708,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -14883,7 +14918,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14896,14 +14930,12 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -15039,7 +15071,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -15099,7 +15130,6 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -15121,7 +15151,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -15130,7 +15159,6 @@ "version": "10.4.2", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", - "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -15210,11 +15238,27 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, + "node_modules/tailwind-merge": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.3.0.tgz", + "integrity": "sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==", + "dependencies": { + "@babel/runtime": "^7.24.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", - "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -15251,7 +15295,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, "engines": { "node": ">=10" } @@ -15310,7 +15353,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -15319,7 +15361,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -15419,8 +15460,7 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/ts-jest": { "version": "29.1.5", @@ -15760,8 +15800,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", @@ -16036,7 +16075,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -16053,7 +16091,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -16068,7 +16105,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -16079,20 +16115,17 @@ "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -16101,7 +16134,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -16230,7 +16262,6 @@ "version": "2.4.5", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", - "dev": true, "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index 3d42ef3..0277112 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "axios-mock-adapter": "^1.22.0", "eslint-config-airbnb-typescript": "^18.0.0", "expect-puppeteer": "^10.0.0", + "flowbite-react": "^0.10.1", "gsap": "^3.12.5", "install": "^0.13.0", "jest-environment-jsdom": "^29.7.0", diff --git a/src/__test__/improveTest.test.tsx b/src/__test__/improveTest.test.tsx new file mode 100644 index 0000000..a5c4953 --- /dev/null +++ b/src/__test__/improveTest.test.tsx @@ -0,0 +1,84 @@ +import MockAdapter from "axios-mock-adapter"; +import { configureStore } from "@reduxjs/toolkit"; + +import cartReducer, { + cartManage, + cartDelete, + addToCart, + removeFromCart, + updateCarts, + increaseQuantity, + decreaseQuantity, +} from "../redux/reducers/cartSlice"; +import api from "../redux/api/action"; + +const mock = new MockAdapter(api); + +describe("test improvement on cart", () => { + beforeEach(() => { + mock.reset(); + }); + + it("cartManage dispatches fulfilled action when data is returned", async () => { + const store = configureStore({ reducer: { carts: cartReducer } }); + const mockCartItems = [{ id: 1, name: "Item 1", quantity: 1 }]; + mock.onGet("/carts").reply(200, { userCart: { items: mockCartItems } }); + + await store.dispatch(cartManage()); + const state = store.getState(); + expect(state.carts.data).toEqual(mockCartItems); + expect(state.carts.isLoading).toBe(false); + expect(state.carts.error).toBe(false); + }); + + it("cartManage dispatches rejected action on failed request", async () => { + const store = configureStore({ reducer: { carts: cartReducer } }); + mock.onGet("/carts").networkError(); + + await store.dispatch(cartManage()); + const state = store.getState(); + expect(state.carts.data).toEqual([]); + expect(state.carts.isLoading).toBe(false); + expect(state.carts.error).toBe(true); + }); + + it("cartDelete dispatches rejected action on failed request", async () => { + const store = configureStore({ reducer: { carts: cartReducer } }); + mock.onDelete("/carts").networkError(); + + await store.dispatch(cartDelete()); + const state = store.getState(); + expect(state.carts.delete.error).toBe(true); + }); + + it("addToCart dispatches fulfilled action when item is added", async () => { + const store = configureStore({ reducer: { carts: cartReducer } }); + const newCartItem = { id: 2, name: "Item 2", quantity: 2 }; + mock.onPost("/carts").reply(200, newCartItem); + + await store.dispatch(addToCart({ productId: 2, quantity: 2 })); + const state = store.getState(); + expect(state.carts.data).toContainEqual(newCartItem); + expect(state.carts.add.isLoading).toBe(false); + expect(state.carts.add.error).toBeNull(); + }); + + it("addToCart dispatches rejected action on failed request", async () => { + const store = configureStore({ reducer: { carts: cartReducer } }); + mock.onPost("/carts").reply(500, { message: "Internal Server Error" }); + + await store.dispatch(addToCart({ productId: 999, quantity: 1 })); + const state = store.getState(); + expect(state.carts.add.isLoading).toBe(false); + }); + + it("removeFromCart dispatches fulfilled action when item is removed", async () => { + const store = configureStore({ reducer: { carts: cartReducer } }); + const productId = 2; + mock.onPut("/carts").reply(200, { id: productId }); + // @ts-ignore + await store.dispatch(removeFromCart(productId)); + const state = store.getState(); + expect(state.carts.remove.error).toBe(false); + }); +}); diff --git a/src/__test__/wishListSlice.test.tsx b/src/__test__/wishListSlice.test.tsx new file mode 100644 index 0000000..4b31ac6 --- /dev/null +++ b/src/__test__/wishListSlice.test.tsx @@ -0,0 +1,106 @@ +import MockAdapter from "axios-mock-adapter"; +import { configureStore } from "@reduxjs/toolkit"; + +import wishListSlice, { + fetchWishes, + addWish, + deleteWish, +} from "../redux/reducers/wishListSlice"; +import api from "../redux/api/action"; + +const mock = new MockAdapter(api); + +describe("wishesSlice test", () => { + beforeEach(() => { + mock.reset(); + }); + + it("fetchWishes dispatches fulfilled action when data is returned", async () => { + const store = configureStore({ reducer: { wishes: wishListSlice } }); + const mockWishes = [ + { + id: 1, + product: { + id: 1, + name: "Product", + images: [], + stockQuantity: 10, + price: 100, + }, + user: { name: "User", userName: "username", email: "email@test.com" }, + }, + ]; + mock.onGet(`/wishes`).reply(200, { wishes: mockWishes }); + + await store.dispatch(fetchWishes()); + const state = store.getState(); + expect(state.wishes.wishes).toEqual(mockWishes); + expect(state.wishes.isLoading).toBe(false); + expect(state.wishes.error).toBeNull(); + }); + + it("fetchWishes dispatches rejected action on failed request", async () => { + const store = configureStore({ reducer: { wishes: wishListSlice } }); + mock.onGet(`/wishes`).networkError(); + + await store.dispatch(fetchWishes()); + const state = store.getState(); + expect(state.wishes.wishes).toEqual([]); + expect(state.wishes.isLoading).toBe(false); + }); + + it("addWish dispatches fulfilled action when a wish is added", async () => { + const store = configureStore({ reducer: { wishes: wishListSlice } }); + const newWish = { + id: 2, + product: { + id: 2, + name: "Product2", + images: [], + stockQuantity: 5, + price: 200, + }, + user: { name: "User2", userName: "user2", email: "user2@test.com" }, + }; + mock.onPost(`/wishes`).reply(200, newWish); + + await store.dispatch(addWish({ productId: 2 })); + const state = store.getState(); + expect(state.wishes.wishes).toContainEqual(newWish); + expect(state.wishes.isLoading).toBe(false); + expect(state.wishes.error).toBeNull(); + }); + + it("addWish dispatches rejected action on failed request", async () => { + const store = configureStore({ reducer: { wishes: wishListSlice } }); + mock.onPost(`/wishes`).reply(500, { message: "Internal Server Error" }); + + await store.dispatch(addWish({ productId: 999 })); + const state = store.getState(); + expect(state.wishes.isLoading).toBe(false); + expect(state.wishes.error).toEqual("Internal Server Error"); + }); + + it("deleteWish dispatches fulfilled action when a wish is removed", async () => { + const store = configureStore({ reducer: { wishes: wishListSlice } }); + mock.onDelete(`/products/2/wishes`).reply(200); + + await store.dispatch(deleteWish({ productId: 2 })); + const state = store.getState(); + expect(state.wishes.wishes).not.toContainEqual( + expect.objectContaining({ id: 2 }), + ); + expect(state.wishes.isLoading).toBe(false); + expect(state.wishes.error).toBeNull(); + }); + + it("deleteWish dispatches rejected action on failed request", async () => { + const store = configureStore({ reducer: { wishes: wishListSlice } }); + mock.onDelete(`/products/999/wishes`).reply(404, { message: "Not Found" }); + + await store.dispatch(deleteWish({ productId: 999 })); + const state = store.getState(); + expect(state.wishes.isLoading).toBe(false); + expect(state.wishes.error).toEqual("Not Found"); + }); +}); diff --git a/src/components/cards/ProductCard.tsx b/src/components/cards/ProductCard.tsx index 756a4a3..2235f13 100644 --- a/src/components/cards/ProductCard.tsx +++ b/src/components/cards/ProductCard.tsx @@ -1,13 +1,13 @@ import { IconButton, Rating, Typography } from "@mui/material"; import { FaEye } from "react-icons/fa"; -import { CiHeart } from "react-icons/ci"; +import { IoHeartSharp } from "react-icons/io5"; import React, { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { ToastContainer, toast } from "react-toastify"; import { AxiosError } from "axios"; import { useSelector } from "react-redux"; -import { Spinner } from "flowbite-react"; +import Spinner from "../dashboard/Spinner"; import { addWish, fetchWishes, @@ -37,7 +37,12 @@ const ProductCard: React.FC = ({ product }) => { const { wishes } = useSelector((state: RootState) => state.wishes); const dispatch = useAppDispatch(); const navigate = useNavigate(); - + const loggedInUserToken = localStorage.getItem("accessToken"); + let loggedInUser; + if (loggedInUserToken) { + // @ts-ignore + loggedInUser = JSON.parse(atob(loggedInUserToken.split(".")[1])); + } const formatPrice = (price: number) => { if (price < 1000) { return price.toString(); @@ -216,18 +221,18 @@ const ProductCard: React.FC = ({ product }) => { className="bg-white" sx={{ paddingY: 0.5, paddingX: 0.5 }} > - {!localStorage.getItem("accessToken") ? ( + {!loggedInUserToken || loggedInUser.roleId !== 1 ? ( "" ) : loadWishes ? ( - + ) : alreadyWished ? ( - ) : ( - = ({ searchQuery, setSearchQuery }) => { const [isLoggedIn, setIsLoggedIn] = useState(false); + const { profile } = useSelector((state: RootState) => state.usersProfile); const handleSearch = (e: React.ChangeEvent) => { setSearchQuery(e.target.value); }; - let userInfo; const accessToken = localStorage.getItem("accessToken"); if (accessToken) { @@ -34,6 +35,11 @@ const Header: React.FC = ({ searchQuery, setSearchQuery }) => { } const dispatch = useAppDispatch(); + useEffect(() => { + // @ts-ignore + dispatch(getProfile()); + }, [dispatch]); + useEffect(() => { if (accessToken) { try { @@ -64,6 +70,18 @@ const Header: React.FC = ({ searchQuery, setSearchQuery }) => { dispatch(cartManage()); }, [dispatches]); const userCart = useSelector((state: RootState) => state.cart.data); + + useEffect(() => { + const fetchData = async () => { + try { + await dispatch(fetchWishes()); + } catch (error) { + console.error(error); + } + }; + fetchData(); + }, [dispatch]); + const { wishes } = useSelector((state: RootState) => state.wishes); return ( @@ -109,7 +127,15 @@ const Header: React.FC = ({ searchQuery, setSearchQuery }) => { - + {localStorage.getItem("accessToken") && userInfo.roleId === 2 ? ( + + + + ) : ( + + + + )} {isLoggedIn ? ( @@ -117,7 +143,7 @@ const Header: React.FC = ({ searchQuery, setSearchQuery }) => { - {userInfo.name.split(" ")[0]} + {profile && profile.fullName} diff --git a/src/components/dashboard/SideBar.tsx b/src/components/dashboard/SideBar.tsx index a6b1980..c2646ee 100644 --- a/src/components/dashboard/SideBar.tsx +++ b/src/components/dashboard/SideBar.tsx @@ -14,7 +14,8 @@ interface SidebarProps { const SideBar: React.FC = ({ isOpen }) => { const location = useLocation(); - const getLinkClass = (path: string) => (location.pathname === path ? "text-primary" : "text-dark-gray"); + const getLinkClass = (path: string) => + (location.pathname === path ? "text-primary" : "text-dark-gray"); return (
= ({ isOpen }) => {

diff --git a/src/components/dashboard/products/wishedProducts.tsx b/src/components/dashboard/products/wishedProducts.tsx new file mode 100644 index 0000000..bf93e4f --- /dev/null +++ b/src/components/dashboard/products/wishedProducts.tsx @@ -0,0 +1,72 @@ +import React from "react"; + +interface Product { + id: number; + name: string; + images: string[]; + stockQuantity: number; + price: number; +} + +interface WishedProductsDetails { + products: Product[]; + onClose: () => void; +} + +const WisheProductDetails: React.FC = ({ + products, + onClose, +}) => ( +
+
+

+ Wished Products details +

+ + + + + + + + + + + {products.map((product) => ( + + + + + + ))} + +
+ Products + + Price + + Stock Quantity +
+ {product.name} + {product.name} + + {product.price} + + {product.stockQuantity} +
+ + +
+
+); + +export default WisheProductDetails; diff --git a/src/components/dashboard/wishesTable.tsx b/src/components/dashboard/wishesTable.tsx new file mode 100644 index 0000000..2eead8b --- /dev/null +++ b/src/components/dashboard/wishesTable.tsx @@ -0,0 +1,158 @@ +import React, { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; + +import { fetchWishes } from "../../redux/reducers/wishListSlice"; +import { RootState, AppDispatch } from "../../redux/store"; + +import Spinner from "./Spinner"; +import WisheProductDetails from "./products/wishedProducts"; + +interface Product { + id: number; + name: string; + images: string[]; + stockQuantity: number; + price: number; +} +interface WishedProductsInfo { + userEmail: string; + userName: string; + wishedProductsCount: number; + products: Product[]; +} + +const WishesTable: React.FC = () => { + const dispatch: AppDispatch = useDispatch(); + const { wishes, isLoading, error } = useSelector( + (state: RootState) => state.wishes, + ); + + const [wishedProducts, setwishedProducts] = useState( + [], + ); + const [selectedUserProducts, setSelectedUserProducts] = useState< + Product[] | null + >(null); + + useEffect(() => { + const fetchData = async () => { + try { + await dispatch(fetchWishes()); + } catch (error) { + console.error(error); + } + }; + + fetchData(); + }, [dispatch]); + + useEffect(() => { + if (wishes.length > 0) { + const aggregatedData: Record = {}; + + wishes.forEach((wish) => { + const userEmail = wish.user.email; + if (!aggregatedData[userEmail]) { + aggregatedData[userEmail] = { + userEmail, + userName: wish.user.name, + wishedProductsCount: 0, + products: [], + }; + } + aggregatedData[userEmail].wishedProductsCount += 1; + aggregatedData[userEmail].products.push({ + id: wish.product.id, + name: wish.product.name, + images: wish.product.images, + stockQuantity: wish.product.stockQuantity, + price: wish.product.price, + }); + }); + + setwishedProducts(Object.values(aggregatedData)); + } + }, [wishes]); + const handleDetailsClick = (products: Product[]) => { + setSelectedUserProducts(products); + }; + + const handleCloseDetails = () => { + setSelectedUserProducts(null); + }; + + return ( +
+ {selectedUserProducts && ( + + )} +
+ + + + + + + + + + + {isLoading ? ( + + + + ) : wishedProducts.length === 0 ? ( + + + + ) : ( + wishedProducts.map((item) => ( + + + + + + + )) + )} + +
+ Customer Name + + Email + + Quatity + + Action +
+ +
+ No products found. +
+ + {item?.userName} + + + {item.userEmail} + + {item.wishedProductsCount} + + +
+
+
+ ); +}; + +export default WishesTable; diff --git a/src/dashboard/sellers/wishesList.tsx b/src/dashboard/sellers/wishesList.tsx new file mode 100644 index 0000000..8e18053 --- /dev/null +++ b/src/dashboard/sellers/wishesList.tsx @@ -0,0 +1,18 @@ +import Layout from "../../components/layouts/SellerLayout"; +import WishesTable from "../../components/dashboard/wishesTable"; + +const Wishes = () => ( + +
+

Wishes List

+

+ Detailed information about your wished products +

+
+
+ +
+
+); + +export default Wishes; diff --git a/src/pages/ReviewList.tsx b/src/pages/ReviewList.tsx index 48f3409..755c856 100644 --- a/src/pages/ReviewList.tsx +++ b/src/pages/ReviewList.tsx @@ -70,7 +70,7 @@ const ReviewsList: React.FC = ({ productId }) => { setNewRating(""); setNewFeedback(""); // @ts-ignore - if (response.error) { + if (response && response.error) { // @ts-ignore toast.error(response.payload.message); } else { @@ -212,7 +212,7 @@ const ReviewsList: React.FC = ({ productId }) => { } }} placeholder="Write your feedback" - className="border-[#DB4444] p-2 rounded" + className="border-2 border-[#DB4444] p-2 rounded" /> {errors.newFeedback && (

{errors.newFeedback}

diff --git a/src/pages/Wishes.tsx b/src/pages/Wishes.tsx index 390ff32..2e13096 100644 --- a/src/pages/Wishes.tsx +++ b/src/pages/Wishes.tsx @@ -3,13 +3,15 @@ import { useDispatch, useSelector } from "react-redux"; import { toast, ToastContainer } from "react-toastify"; import { MdOutlineClose } from "react-icons/md"; import { AxiosError } from "axios"; -import { Spinner } from "flowbite-react"; +import { useNavigate } from "react-router-dom"; +import Spinner from "../components/dashboard/Spinner"; import MainSpinner from "../components/common/auth/Loader"; import Warning from "../components/common/notify/Warning"; import { deleteWish, fetchWishes } from "../redux/reducers/wishListSlice"; import { RootState, AppDispatch } from "../redux/store"; import { addToCart } from "../redux/reducers/cartSlice"; +import LinkToUpdatePage from "../components/profile/linkToUpdate"; // @ts-ignore const BuyerWishesList: React.FC = () => { @@ -17,6 +19,13 @@ const BuyerWishesList: React.FC = () => { const { wishes, error } = useSelector((state: RootState) => state.wishes); const [loadingWish, setLoadingWish] = useState(null); const [isLoading, setLoading] = useState(false); + const navigate = useNavigate(); + const loggedInUserToken = localStorage.getItem("accessToken"); + let loggedInUser; + if (loggedInUserToken) { + // @ts-ignore + loggedInUser = JSON.parse(atob(loggedInUserToken.split(".")[1])); + } useEffect(() => { setLoading(true); const fetchData = async () => { @@ -31,13 +40,12 @@ const BuyerWishesList: React.FC = () => { fetchData(); }, [dispatch]); - const handleDeleteWish = async (productId) => { setLoadingWish(productId); try { - await dispatch(deleteWish({ productId })); + const response = await dispatch(deleteWish({ productId })); dispatch(fetchWishes()); - toast.success("Product removed from your wish list"); + toast.success("Wish removed from the list"); } catch (err) { const error = err as AxiosError; toast.error(error.message); @@ -71,23 +79,23 @@ const BuyerWishesList: React.FC = () => {

); } - if (!localStorage.getItem("accessToken")) { - return ; + if (!loggedInUserToken) { + navigate("/login"); } - if (error) { - return ( -
-

{error}

-
- ); + if (loggedInUserToken && loggedInUser.roleId !== 1) { + navigate("/"); } - return (
-

Home / Wishes

+

+ + Home + + / Wishes +

@@ -107,7 +115,7 @@ const BuyerWishesList: React.FC = () => { {wishes.length === 0 ? ( ) : ( @@ -120,10 +128,7 @@ const BuyerWishesList: React.FC = () => {
{loadingWish === wish.productId && (
- +
)}
=> { const UpdateUserProfile: React.FC = () => { const dispatch = useAppDispatch(); + const navigate = useNavigate(); const { profile } = useSelector((state: RootState) => state.usersProfile); useEffect(() => { // @ts-ignore @@ -77,7 +79,6 @@ const UpdateUserProfile: React.FC = () => { setImagePreview(reader.result as string); }; reader.readAsDataURL(file); - // @ts-ignore setSelectedImage(file); } }; @@ -99,8 +100,7 @@ const UpdateUserProfile: React.FC = () => { const formData = new FormData(); if (selectedImage) { formData.append("profileImage", selectedImage); - } - if (profile?.profileImage) { + } else if (profile?.profileImage) { // @ts-ignore const newFile = await convertUrlToFile(profile.profileImage); formData.append("profileImage", newFile); @@ -137,7 +137,10 @@ const UpdateUserProfile: React.FC = () => { { value: "RWF", label: "RWF" }, { value: "USD", label: "USD" }, ]; - + const loggedInUserToken = localStorage.getItem("accessToken"); + if (!loggedInUserToken) { + navigate("/login"); + } return (
@@ -147,7 +150,7 @@ const UpdateUserProfile: React.FC = () => {

- Home + Home /My Account

@@ -206,11 +209,11 @@ const UpdateUserProfile: React.FC = () => { )} - +
{ dispatch(getProfile()); }, [dispatch]); const loggedInUserToken = localStorage.getItem("accessToken"); - // @ts-ignore - const userData = JSON.parse(atob(loggedInUserToken.split(".")[1])); + if (!loggedInUserToken) { + navigate("/login"); + } if (loading) { return (
@@ -38,8 +39,19 @@ const UsersProfile: React.FC = () => { ); } if (error) { - console.log(error); - navigate("/profile/update"); +
+
+ +
+ +

+ Home + /Profile + {" "} +

+
+

error;

+
; } return (
@@ -50,7 +62,7 @@ const UsersProfile: React.FC = () => {

- Home + Home /Profile {" "}

@@ -72,7 +84,7 @@ const UsersProfile: React.FC = () => {

{profile?.fullName}

-

{userData.email}

+

{profile?.email}

@@ -91,7 +103,7 @@ const UsersProfile: React.FC = () => { {profile && (
- + ) => { state.isLoading = false; + if (!Array.isArray(state.reviews)) { + state.reviews = []; + } state.reviews.push(action.payload); }) .addCase(addReview.rejected, (state, action) => { diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx index 6ecd86b..ae5dbc2 100644 --- a/src/routes/AppRoutes.tsx +++ b/src/routes/AppRoutes.tsx @@ -24,11 +24,14 @@ import CartManagement from "../pages/CartManagement"; import { setNavigate } from "../redux/api/api"; import ChatPage from "../pages/ChatPage"; import BuyerWishesList from "../pages/Wishes"; +// import { setNavigateFunction } from "../redux/api/api"; +import Wishes from "../dashboard/sellers/wishesList"; const AppRoutes = () => { const navigate = useNavigate(); useEffect(() => { setNavigate(navigate); + // setNavigateFunction(navigate); }, [navigate]); const AlreadyLogged = ({ children }) => { @@ -81,6 +84,7 @@ const AppRoutes = () => { } /> } /> } /> + } /> ); }; diff --git a/type.d.ts b/type.d.ts index d67a736..8f3b4d2 100644 --- a/type.d.ts +++ b/type.d.ts @@ -48,6 +48,7 @@ type passwordType = { export type Profile = { profileImage: string; fullName: string; + email: string; gender: string; birthdate: string; preferredLanguage: string;
- No wishes found + No wishes found 😎