diff --git a/package-lock.json b/package-lock.json index 03205a6..425d076 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,9 @@ "@storybook/nextjs": "^8.1.3", "@storybook/react": "^8.1.3", "@storybook/test": "^8.1.3", + "@tanstack/react-query": "^5.40.1", "@tanstack/react-query-devtools": "^5.44.0", + "@tanstack/react-table": "^8.17.3", "@testing-library/dom": "^10.1.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", @@ -4427,9 +4429,9 @@ "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.4.tgz", - "integrity": "sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", + "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", "dev": true, "dependencies": { "glob": "10.3.10" @@ -8246,18 +8248,19 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" }, "node_modules/@tanstack/query-core": { - "version": "5.50.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.50.1.tgz", - "integrity": "sha512-lpfhKPrJlyV2DSVcQb/HuozH3Av3kws4ge22agx+lNGpFkS4vLZ7St0l3GLwlAD+bqB+qXGex3JdRKUNtMviEQ==", + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.1.tgz", + "integrity": "sha512-fJBMQMpo8/KSsWW5ratJR5+IFr7YNJ3K2kfP9l5XObYHsgfVy1w3FJUWU4FT2fj7+JMaEg33zOcNDBo0LMwHnw==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/query-devtools": { - "version": "5.50.1", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.50.1.tgz", - "integrity": "sha512-MQ5JK3yRwBP1SRuwoJVPGZP4cMLXCQ0t+6blDbcAVGEoqrEuvbgTdwlN729AKBR0hidOWPFR9n5YpI2Y8bBZOQ==", + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.51.1.tgz", + "integrity": "sha512-rehG0WmL3EXER6MAI2uHQia/n0b5c3ZROohpYm7u3G7yg4q+HsfQy6nuAo6uy40NzHUe3FmnfWCZQ0Vb/3lE6g==", "dev": true, "funding": { "type": "github", @@ -8265,11 +8268,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.50.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.50.1.tgz", - "integrity": "sha512-s0DW3rVBDPReDDovUjVqItVa3R2nPfUANK9nqGvarO2DwTiY9U4EBTsqizMxItRCoGgK5apeM7D3mxlHrSKpdQ==", + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.1.tgz", + "integrity": "sha512-s47HKFnQ4HOJAHoIiXcpna/roMMPZJPy6fJ6p4ZNVn8+/onlLBEDd1+xc8OnDuwgvecqkZD7Z2mnSRbcWefrKw==", + "dev": true, "dependencies": { - "@tanstack/query-core": "5.50.1" + "@tanstack/query-core": "5.51.1" }, "funding": { "type": "github", @@ -8280,19 +8284,19 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.50.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.50.1.tgz", - "integrity": "sha512-zgPmEFv9GhLAx6eaf9r0ACbcxit1ZSuv/uPpOXBTTSPLijlWcfpQTOdZx0jYQ14t2cUfWjrAW41cUmcCvT4X/g==", + "version": "5.51.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.51.1.tgz", + "integrity": "sha512-bRShIVKGpUOHpwziGKT8Aq1Ty0lIlGmNI7E0KbGYtmyOaImErpdElTdxfES1bRaI7i/j+mf2hLy+E6q7SrCwPg==", "dev": true, "dependencies": { - "@tanstack/query-devtools": "5.50.1" + "@tanstack/query-devtools": "5.51.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.50.1", + "@tanstack/react-query": "^5.51.1", "react": "^18 || ^19" } }, @@ -8300,6 +8304,7 @@ "version": "8.19.2", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.2.tgz", "integrity": "sha512-itoSIAkA/Vsg+bjY23FSemcTyPhc5/1YjYyaMsr9QSH/cdbZnQxHVWrpWn0Sp2BWN71qkzR7e5ye8WuMmwyOjg==", + "dev": true, "dependencies": { "@tanstack/table-core": "8.19.2" }, @@ -8319,6 +8324,7 @@ "version": "8.19.2", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.2.tgz", "integrity": "sha512-KpRjhgehIhbfH78ARm/GJDXGnpdw4bCg3qas6yjWSi7czJhI/J6pWln7NHtmBkGE9ZbohiiNtLqwGzKmBfixig==", + "dev": true, "engines": { "node": ">=12" }, @@ -9839,7 +9845,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -9851,7 +9856,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -9859,8 +9863,7 @@ "node_modules/ansi-styles/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/any-promise": { "version": "1.3.0", @@ -11067,7 +11070,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -11136,7 +11138,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, "engines": { "node": ">=10" } @@ -11177,7 +11178,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -11323,7 +11323,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -11337,7 +11336,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" }, @@ -11351,14 +11349,12 @@ "node_modules/cliui/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/cliui/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" } @@ -11367,7 +11363,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", @@ -11381,7 +11376,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" }, @@ -11393,7 +11387,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", @@ -11793,9 +11786,9 @@ } }, "node_modules/create-next-app": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/create-next-app/-/create-next-app-14.2.4.tgz", - "integrity": "sha512-VE7jHFQnwRnvePXB/BsFkhrNsK01GUpfgphQuZz5eHKxaBEMp5mJEFl++VH+slVRf6WGIrVzqWLXkEsD6m1bIQ==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/create-next-app/-/create-next-app-14.2.5.tgz", + "integrity": "sha512-lw4iH8wCflQ2O9eX2+1m2hpDkgVKj0Pm0qCZHetZy9NSizzvEiGAzuyEVOUo8YxlQBQKVW9Ope7ri6tE18yS3w==", "bin": { "create-next-app": "dist/index.js" }, @@ -12551,9 +12544,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.823", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.823.tgz", - "integrity": "sha512-4h+oPeAiGQOHFyUJOqpoEcPj/xxlicxBzOErVeYVMMmAiXUXsGpsFd0QXBMaUUbnD8hhSfLf9uw+MlsoIA7j5w==", + "version": "1.4.825", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.825.tgz", + "integrity": "sha512-OCcF+LwdgFGcsYPYC5keEEFC2XT0gBhrYbeGzHCx7i9qRFbzO/AqTmc/C/1xNhJj+JA7rzlN7mpBuStshh96Cg==", "dev": true }, "node_modules/elliptic": { @@ -12964,7 +12957,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, "engines": { "node": ">=6" } @@ -12979,7 +12971,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -13125,12 +13116,12 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.4.tgz", - "integrity": "sha512-Qr0wMgG9m6m4uYy2jrYJmyuNlYZzPRQq5Kvb9IDlYwn+7yq6W6sfMNFgb+9guM1KYwuIo6TIaiFhZJ6SnQ/Efw==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz", + "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "14.2.4", + "@next/eslint-plugin-next": "14.2.5", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", @@ -14852,9 +14843,9 @@ } }, "node_modules/framer-motion": { - "version": "11.2.14", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.2.14.tgz", - "integrity": "sha512-0Nwg++Jymj4Yn7LFKH/nKuGrgVZTEIgIbLjl+LBBFBEzNd4rX+n3z/doqjEbvjk1xcmsim9h7du2+LTYdQTULw==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.2.tgz", + "integrity": "sha512-RgjSzrNFZmedWcvmW4MMc84A7UcoY37jocadE3Mbg3o+UMofodfyeNnYD/HR15UhP22/bb5KOebNhYOj4mYkpQ==", "dependencies": { "tslib": "^2.4.0" }, @@ -14907,7 +14898,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14919,7 +14909,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14930,8 +14919,7 @@ "node_modules/fs-minipass/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/fs-monkey": { "version": "1.0.6", @@ -15003,7 +14991,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -15260,7 +15247,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } @@ -15755,7 +15741,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -18576,8 +18561,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -19588,7 +19572,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -19601,7 +19584,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -19612,14 +19594,12 @@ "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -20046,9 +20026,9 @@ } }, "node_modules/npm": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.1.tgz", - "integrity": "sha512-Dp1C6SvSMYQI7YHq/y2l94uvI+59Eqbu1EpuKQHQ8p16txXRuRit5gH3Lnaagk2aXDIjg/Iru9pd05bnneKgdw==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", + "integrity": "sha512-x/AIjFIKRllrhcb48dqUNAAZl0ig9+qMuN91RpZo3Cb2+zuibfh+KISl6+kVVyktDz230JKc208UkQwwMqyB+w==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -20119,22 +20099,15 @@ "which", "write-file-atomic" ], - "workspaces": [ - "docs", - "smoke-tests", - "mock-globals", - "mock-registry", - "workspaces/*" - ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^7.5.3", - "@npmcli/config": "^8.3.3", + "@npmcli/arborist": "^7.5.4", + "@npmcli/config": "^8.3.4", "@npmcli/fs": "^3.1.1", "@npmcli/map-workspaces": "^3.0.6", - "@npmcli/package-json": "^5.1.1", + "@npmcli/package-json": "^5.2.0", "@npmcli/promise-spawn": "^7.0.2", - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^2.0.1", "@npmcli/run-script": "^8.1.0", "@sigstore/tuf": "^2.3.4", "abbrev": "^2.0.0", @@ -20145,7 +20118,7 @@ "cli-columns": "^4.0.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.4.1", + "glob": "^10.4.2", "graceful-fs": "^4.2.11", "hosted-git-info": "^7.0.2", "ini": "^4.1.3", @@ -20153,30 +20126,30 @@ "is-cidr": "^5.1.0", "json-parse-even-better-errors": "^3.0.2", "libnpmaccess": "^8.0.6", - "libnpmdiff": "^6.1.3", - "libnpmexec": "^8.1.2", - "libnpmfund": "^5.0.11", + "libnpmdiff": "^6.1.4", + "libnpmexec": "^8.1.3", + "libnpmfund": "^5.0.12", "libnpmhook": "^10.0.5", "libnpmorg": "^6.0.6", - "libnpmpack": "^7.0.3", + "libnpmpack": "^7.0.4", "libnpmpublish": "^9.0.9", "libnpmsearch": "^7.0.6", "libnpmteam": "^6.0.5", "libnpmversion": "^6.0.3", "make-fetch-happen": "^13.0.1", - "minimatch": "^9.0.4", + "minimatch": "^9.0.5", "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", "node-gyp": "^10.1.0", "nopt": "^7.2.1", - "normalize-package-data": "^6.0.1", + "normalize-package-data": "^6.0.2", "npm-audit-report": "^5.0.0", "npm-install-checks": "^6.3.0", "npm-package-arg": "^11.0.2", - "npm-pick-manifest": "^9.0.1", + "npm-pick-manifest": "^9.1.0", "npm-profile": "^10.0.0", - "npm-registry-fetch": "^17.0.1", + "npm-registry-fetch": "^17.1.0", "npm-user-validate": "^2.0.1", "p-map": "^4.0.0", "pacote": "^18.0.6", @@ -20299,7 +20272,7 @@ } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.5.3", + "version": "7.5.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -20347,16 +20320,16 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "8.3.3", + "version": "8.3.4", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", "ci-info": "^4.0.0", "ini": "^4.1.2", "nopt": "^7.2.1", "proc-log": "^4.2.0", - "read-package-json-fast": "^3.0.2", "semver": "^7.3.5", "walk-up-path": "^3.0.1" }, @@ -20376,11 +20349,12 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.7", + "version": "5.0.8", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", "proc-log": "^4.0.0", @@ -20454,7 +20428,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "5.1.1", + "version": "5.2.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -20493,7 +20467,7 @@ } }, "node_modules/npm/node_modules/@npmcli/redact": { - "version": "2.0.0", + "version": "2.0.1", "inBundle": true, "license": "ISC", "engines": { @@ -20865,7 +20839,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.4", + "version": "4.3.5", "inBundle": true, "license": "MIT", "dependencies": { @@ -20939,7 +20913,7 @@ } }, "node_modules/npm/node_modules/foreground-child": { - "version": "3.1.1", + "version": "3.2.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -20964,16 +20938,8 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.2", - "inBundle": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/npm/node_modules/glob": { - "version": "10.4.1", + "version": "10.4.2", "inBundle": true, "license": "ISC", "dependencies": { @@ -20981,6 +20947,7 @@ "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { @@ -20998,17 +20965,6 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/hasown": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/npm/node_modules/hosted-git-info": { "version": "7.0.2", "inBundle": true, @@ -21038,7 +20994,7 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.4", + "version": "7.0.5", "inBundle": true, "license": "MIT", "dependencies": { @@ -21147,17 +21103,6 @@ "node": ">=14" } }, - "node_modules/npm/node_modules/is-core-module": { - "version": "2.13.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "inBundle": true, @@ -21177,7 +21122,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/jackspeak": { - "version": "3.1.2", + "version": "3.4.0", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -21245,11 +21190,11 @@ } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.1.3", + "version": "6.1.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.3", + "@npmcli/arborist": "^7.5.4", "@npmcli/installed-package-contents": "^2.1.0", "binary-extensions": "^2.3.0", "diff": "^5.1.0", @@ -21263,11 +21208,11 @@ } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "8.1.2", + "version": "8.1.3", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.3", + "@npmcli/arborist": "^7.5.4", "@npmcli/run-script": "^8.1.0", "ci-info": "^4.0.0", "npm-package-arg": "^11.0.2", @@ -21283,11 +21228,11 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.11", + "version": "5.0.12", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.3" + "@npmcli/arborist": "^7.5.4" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -21318,11 +21263,11 @@ } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "7.0.3", + "version": "7.0.4", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.5.3", + "@npmcli/arborist": "^7.5.4", "@npmcli/run-script": "^8.1.0", "npm-package-arg": "^11.0.2", "pacote": "^18.0.6" @@ -21418,7 +21363,7 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.4", + "version": "9.0.5", "inBundle": true, "license": "ISC", "dependencies": { @@ -21488,26 +21433,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "inBundle": true, @@ -21653,12 +21578,11 @@ } }, "node_modules/npm/node_modules/normalize-package-data": { - "version": "6.0.1", + "version": "6.0.2", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, @@ -21730,7 +21654,7 @@ } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "9.0.1", + "version": "9.1.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -21756,15 +21680,15 @@ } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "17.0.1", + "version": "17.1.0", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", "make-fetch-happen": "^13.0.0", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", "npm-package-arg": "^11.0.0", "proc-log": "^4.0.0" @@ -21795,6 +21719,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, "node_modules/npm/node_modules/pacote": { "version": "18.0.6", "inBundle": true, @@ -22065,13 +21994,13 @@ } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.3", + "version": "8.0.4", "inBundle": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", - "socks": "^2.7.1" + "socks": "^2.8.3" }, "engines": { "node": ">= 14" @@ -23721,9 +23650,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.10", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", - "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", "dev": true }, "node_modules/nypm": { @@ -25895,7 +25824,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -26351,7 +26279,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -27366,7 +27293,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -27499,7 +27425,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -27516,7 +27441,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -27524,8 +27448,7 @@ "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/telejson": { "version": "7.2.0", @@ -27882,8 +27805,7 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" }, "node_modules/thenify": { "version": "3.3.1", @@ -28029,9 +27951,9 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/ts-jest": { - "version": "29.2.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.1.tgz", - "integrity": "sha512-7obwtH5gw0b0XZi0wmprCSvGSvHliMBI47lPnU47vmbxWS6B+v1X94yWFo1f1vt9k/he+gttsrXjkxmgY41XNQ==", + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.2.tgz", + "integrity": "sha512-sSW7OooaKT34AAngP6k1VS669a0HdLxkQZnlC7T76sckGCokXFnvJ3yRlQZGRTAoV5K19HfSgCiSwWOSIfcYlg==", "dev": true, "dependencies": { "bs-logger": "0.x", @@ -29358,7 +29280,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -29370,8 +29291,7 @@ "node_modules/write-file-atomic/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/ws": { "version": "8.18.0", @@ -29436,7 +29356,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -29460,7 +29379,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -29478,7 +29396,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -29486,14 +29403,12 @@ "node_modules/yargs/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/yargs/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" } @@ -29502,7 +29417,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", @@ -29516,7 +29430,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" }, diff --git a/package.json b/package.json index 24cd829..04eab5d 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,8 @@ "@storybook/nextjs": "^8.1.3", "@storybook/react": "^8.1.3", "@storybook/test": "^8.1.3", + "@tanstack/react-query": "^5.40.1", + "@tanstack/react-table": "^8.17.3", "@tanstack/react-query-devtools": "^5.44.0", "@testing-library/dom": "^10.1.0", "@testing-library/jest-dom": "^6.4.6", diff --git a/src/__tests__/productList.test.tsx b/src/__tests__/productList.test.tsx index c2e0e12..0ed0b17 100644 --- a/src/__tests__/productList.test.tsx +++ b/src/__tests__/productList.test.tsx @@ -11,6 +11,7 @@ import Provider from '@/app/providers'; import Page from '@/app/products/page'; import { useRouter } from 'next/navigation'; +jest.setTimeout(15000) const queryClient = new QueryClient(); const ProductListTest = () => { const { data } = useQuery({ diff --git a/src/__tests__/reviewPopupHook.test.tsx b/src/__tests__/reviewPopupHook.test.tsx new file mode 100644 index 0000000..58edf23 --- /dev/null +++ b/src/__tests__/reviewPopupHook.test.tsx @@ -0,0 +1,30 @@ +// WishlistOverlay.test.js + +import { renderHook, act } from '@testing-library/react'; +import ReviewPopup from '@/hooks/reviewPopup'; + +describe('ReviewPopup', () => { + it('should initialize with isOpen as false', () => { + const { result } = renderHook(() => ReviewPopup()); + + expect(result.current.isReviewPopupOpen).toBe(false); + }); + + it('should toggle isOpen when toggleReviewPopup is called', () => { + const { result } = renderHook(() => ReviewPopup()); + + expect(result.current.isReviewPopupOpen).toBe(false); + + act(() => { + result.current.toggleReviewPopup(); + }); + + expect(result.current.isReviewPopupOpen).toBe(true); + + act(() => { + result.current.toggleReviewPopup(); + }); + + expect(result.current.isReviewPopupOpen).toBe(false); + }); +}); diff --git a/src/__tests__/wiishlistOverlayHook.test.tsx b/src/__tests__/wiishlistOverlayHook.test.tsx new file mode 100644 index 0000000..1096866 --- /dev/null +++ b/src/__tests__/wiishlistOverlayHook.test.tsx @@ -0,0 +1,30 @@ +// WishlistOverlay.test.js + +import { renderHook, act } from '@testing-library/react'; +import useWishlistOverlay from '@/hooks/wishlistOverlay'; + +describe('useWishlistOverlay', () => { + it('should initialize with isOpen as false', () => { + const { result } = renderHook(() => useWishlistOverlay()); + + expect(result.current.isWishlistOverlayOpen).toBe(false); + }); + + it('should toggle isOpen when toggleWishlistSlider is called', () => { + const { result } = renderHook(() => useWishlistOverlay()); + + expect(result.current.isWishlistOverlayOpen).toBe(false); + + act(() => { + result.current.toggleWishlistSlider(); + }); + + expect(result.current.isWishlistOverlayOpen).toBe(true); + + act(() => { + result.current.toggleWishlistSlider(); + }); + + expect(result.current.isWishlistOverlayOpen).toBe(false); + }); +}); diff --git a/src/__tests__/wishlistSlice.test.tsx b/src/__tests__/wishlistSlice.test.tsx new file mode 100644 index 0000000..15d47e9 --- /dev/null +++ b/src/__tests__/wishlistSlice.test.tsx @@ -0,0 +1,26 @@ +// userWishlistSlice.test.ts + +import { AnyAction } from 'redux'; +import { createStore } from '@reduxjs/toolkit'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import userWishlistReducer, { handleWishlistCount, userWishlistSlice } from '@/redux/slices/wishlistSlice'; // Adjust the import path as per your project structure + +// Mocking axios request module +jest.mock('@/utils/axios', () => ({ + get: jest.fn() +})); + +describe('userWishlistSlice', () => { + let store: any; + + beforeEach(() => { + store = createStore(userWishlistReducer); + }); + it('should handle handleWishlistCount reducer', () => { + const previousState = { wishNumber: 0 };//@ts-ignore + const newState = userWishlistReducer(previousState, { type: handleWishlistCount.type, payload: 5 }); + expect(newState.wishNumber).toEqual(5); + }); + +}); diff --git a/src/app/products/[id]/page.tsx b/src/app/products/[id]/page.tsx index 09bcad4..e747d29 100644 --- a/src/app/products/[id]/page.tsx +++ b/src/app/products/[id]/page.tsx @@ -18,21 +18,26 @@ import Card from '@/components/Card'; import Header from '@/components/Header'; import Footer from '@/components/Footer'; import { useQuery } from '@tanstack/react-query'; -import ReviewCard from '@/components/ReviewCard'; -import Button from '@/components/Button'; import { averageReviews } from '@/utils/averageReviews'; -import { useAppDispatch } from '@/redux/store'; +import { RootState, useAppDispatch, useAppSelector } from '@/redux/store'; import { handleUserAddCart } from '@/redux/slices/userCartSlice'; +import { handleWishlistCount } from '@/redux/slices/wishlistSlice'; +import request from '@/utils/axios'; +import { showToast } from '@/helpers/toast'; +import ReviewWrapper from '@/components/ReviewsWrapper'; //import StripeProvider from '@/components/StripeProvider'; function Page() { + const { wishNumber } = useAppSelector( + (state: RootState) => state.wishlist + ) const [thumbsSwiper, setThumbsSwiper] = useState(null); const { id } = useParams(); const handleSwiper = (swiper: any) => { setThumbsSwiper(swiper); }; const _id: string = id.toLocaleString(); - const { data, isLoading, error } = useQuery({ + const { data, isLoading, error , refetch} = useQuery({ queryKey: ['product', id], queryFn: async () => { try { @@ -59,6 +64,17 @@ function Page() { const productId = data.product.id; dispatch(handleUserAddCart({ productPrice, productId })); }; + const handleAddRemoveWish = async(event: { preventDefault: () => void; })=>{ + event.preventDefault(); + const response:any = await request.post('/wishes', { productId:id }); + if(response.status == 200 || response.status == 203){ + const { status } = response; + dispatch(handleWishlistCount(status == 200 ? await wishNumber + 1 : await wishNumber - 1)); + showToast(response.message, 'success') + } + console.log('this is response', response) + + } return (
{/* // */} @@ -146,7 +162,7 @@ function Page() {
-
+
@@ -158,7 +174,7 @@ function Page() {
- {productPrice}{' '} + {productPrice.toLocaleString()}{' '} RWF
@@ -201,27 +217,10 @@ function Page() {
-
-

Reviews:

- -
-
- {reviews && reviews.length > 0 ? ( - reviews.map((review: ReviewType) => ( - - )) - ) : ( -

No ratings yet.

- )} -
+
+ )} diff --git a/src/components/Button.tsx b/src/components/Button.tsx index dea261d..54d7ea0 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { IoChevronBack } from 'react-icons/io5'; import { IoMdClose } from 'react-icons/io'; +import { MdClose } from "react-icons/md"; + interface Properties { name?: string; handle?: () => void; diff --git a/src/components/BuyerOrdersList.tsx b/src/components/BuyerOrdersList.tsx index f0d82d2..ae98bac 100644 --- a/src/components/BuyerOrdersList.tsx +++ b/src/components/BuyerOrdersList.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import ReviewProduct from './ReviewProductPopup'; import { useQuery } from "@tanstack/react-query"; -import requestAxios from '@/utils/axios'; +import request from '@/utils/axios'; import { ColumnDef, flexRender, @@ -37,7 +37,7 @@ const BuyerOrdersList = () => { const [orderId, setOrderId] = useState(null); const { isLoading, refetch, error, data } = useQuery({ queryKey: ['BuyerOrdersList'], - queryFn: () => requestAxios.get('/orders'), + queryFn: () => request.get('/orders'), }); const [pagination, setPagination] = useState({ pageIndex: 0, @@ -65,7 +65,7 @@ const BuyerOrdersList = () => { header: 'Price', accessorFn: row => row.Product.productPrice, cell: (row: any) => <> - {row.row.original.Product.productPrice} { row.row.original.Product.productCurrency} + {row.row.original.Product.productPrice.toLocaleString()} { row.row.original.Product.productCurrency} }, { @@ -162,6 +162,7 @@ const BuyerOrdersList = () => { ))} + {ordersData.length > 6 ?
< div className='flex gap-3' > table.previousPage()} isDisabled={!table.getCanPreviousPage()} /> @@ -169,6 +170,7 @@ const BuyerOrdersList = () => { < BackButton handle={() => table.nextPage()} isDisabled={!table.getCanNextPage()} rotate />
+ :""} diff --git a/src/components/BuyerWishlist.tsx b/src/components/BuyerWishlist.tsx new file mode 100644 index 0000000..e7fde9b --- /dev/null +++ b/src/components/BuyerWishlist.tsx @@ -0,0 +1,177 @@ +"use client" +import React, { useState } from 'react'; +import ReviewProduct from './ReviewProductPopup'; +import { useMutation, useQuery } from "@tanstack/react-query"; +import request from '@/utils/axios'; +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from '@tanstack/react-table' +import { BackButton, DeleteButton, ReviewButton } from './Button'; +import { BsTrash } from "react-icons/bs"; +import DeleteWishlistPopup from './DeleteWishlistPopup'; +import { showToast } from '@/helpers/toast'; +import { handleWishlistCount } from '@/redux/slices/wishlistSlice'; +import { RootState, useAppDispatch, useAppSelector } from '@/redux/store'; + +type ordersTable = { + No: number; + id: string; + product:{ + productName: string; + productThumbnail: string; + stockLevel: string; + productPrice: number; + productCurrency:string; + } +} +type PaginationState = { + pageIndex: number + pageSize: number +} +const BuyerWishList = () => { + const [isDeleteWishlistModalOpen, setIsDeleteWishlistModalOpen] = useState(false); + const [productId, setProductId] = useState(''); + const [deleting, setDeleting] = useState(''); + const { wishNumber } = useAppSelector( + (state: RootState) => state.wishlist + ) + const dispatch = useAppDispatch(); + const { isLoading, refetch, error, data } = useQuery({ + queryKey: ['BuyerWishList'], + queryFn: () => request.get('/wishes') + }); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 7 + }) + var wishesData = data?.wishes ?? []; + console.log('this is wishes from the database', wishesData) + const columns: ColumnDef[] = [ + + { + header: 'Image', + accessorFn: row => row.product.productThumbnail, + cell: (val: any) => + <> + + + }, + { + header: 'Product Name', + accessorFn: row => row.product.productName, + cell: (val: any) => {val.getValue()} + }, + { + header: 'Price', + accessorFn: row => row.product.productPrice, + cell: (row: any) => <> + {row.row.original.product.productPrice.toLocaleString()} {row.row.original.product.productCurrency} + + }, + { + id: 'Action', + header: () =>
Action
, + accessorFn: row => row.id, + cell: (row: any) => ( +
+ handleOpenWishlistPopup(row.row.original.product.id, 'product')}/> +
+ ) + }, + ] + const table = useReactTable({ + data: wishesData, + columns, + getPaginationRowModel: getPaginationRowModel(), + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + state: { + pagination + } + }) + const handleOpenWishlistPopup = (productId: string, deleting:string) => { + setProductId(productId); + setIsDeleteWishlistModalOpen(true); + setDeleting(deleting); + }; + + const handleClosePopup = () => { + setIsDeleteWishlistModalOpen(false); + setProductId(''); + }; + + const mutation = useMutation({ + mutationFn: () => { + return request.delete('/wishes') + }, + onError: (error) => console.log(error), + onSuccess: async () => { + dispatch(handleWishlistCount(await wishNumber - await wishNumber)); + showToast('successfully cleared your wish list','success') + await refetch(); + handleClosePopup(); + + }, + }); + + const handleClearAll =() => { + mutation.mutate(); + }; + return ( + <> +
+ Wishlist +
+ <> +
+ + + {table.getHeaderGroups().map(headerGroup => + ( + { + headerGroup.headers.map(header => + + ) + } + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ))} + +
+ {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+
+ handleOpenWishlistPopup(productId, 'wishlist')}/> +
+ +
+
+ {isDeleteWishlistModalOpen && + + } + + ); +}; + +export default BuyerWishList; diff --git a/src/components/Card.tsx b/src/components/Card.tsx index e48a4ef..d415cfb 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -9,7 +9,9 @@ import Link from 'next/link'; import { averageReviews } from '@/utils/averageReviews'; import { RootState, useAppDispatch, useAppSelector } from '@/redux/store'; import { handleUserAddCart } from '@/redux/slices/userCartSlice'; - +import request from '@/utils/axios'; +import { showToast } from '@/helpers/toast'; +import { handleWishlistCount } from '@/redux/slices/wishlistSlice'; // import { useRouter } from 'next/router'; function Card({ @@ -20,12 +22,28 @@ function Card({ id, reviews, }: Cards) { - const productId = id; +const { wishNumber } = useAppSelector( + (state: RootState) => state.wishlist +) + +const productId=id - const dispatch = useAppDispatch(); - const handleNewItem = () => { - dispatch(handleUserAddCart({ productPrice, productId })); - }; +const dispatch = useAppDispatch(); +const handleNewItem=()=>{ + dispatch(handleUserAddCart({productPrice,productId})); +} +const handleAddRemoveWish = async(event: { preventDefault: () => void; })=>{ + event.preventDefault(); + const response:any = await request.post('/wishes', { productId:id }); + + if(response.status == 200 || response.status == 203){ + const { status } = response; + dispatch(handleWishlistCount(status == 200 ? await wishNumber + 1 : await wishNumber - 1)); + showToast(response.message, 'success') + } + console.log('this is response', response) + +} return (
@@ -49,7 +67,7 @@ function Card({
- {productPrice} RWF + {productPrice.toLocaleString()} RWF - - { - handleNewItem(); - }} - /> + + {handleNewItem()}} />
); diff --git a/src/components/DeleteWishlistPopup.tsx b/src/components/DeleteWishlistPopup.tsx new file mode 100644 index 0000000..d1b35ae --- /dev/null +++ b/src/components/DeleteWishlistPopup.tsx @@ -0,0 +1,66 @@ +import React, { useEffect, useRef } from 'react'; +import { Button, CloseButton, DeleteButton } from './Button'; +import request from '@/utils/axios'; +import { useMutation, RefetchOptions, QueryObserverResult } from "@tanstack/react-query"; +import { showToast } from '@/helpers/toast'; +import { handleWishlistCount } from '@/redux/slices/wishlistSlice'; +import { RootState, useAppDispatch, useAppSelector } from '@/redux/store'; + +interface DeleteWishlistInterface { + isOpen: boolean, + id: string | null; + handleClose: () => void + refetch: (options?: RefetchOptions | undefined) => Promise>; + deleting:string; + handleClearWishlist:()=>void; +} + +const DeleteWishlistPopup: React.FC = ({ id, isOpen, handleClose, refetch, deleting, handleClearWishlist}) => { + const { wishNumber } = useAppSelector( + (state: RootState) => state.wishlist + ) + const dispatch = useAppDispatch(); + + const dialogRef = useRef(null); + useEffect(() => { + if (isOpen && dialogRef.current) { + dialogRef.current.showModal(); + } else if (dialogRef.current) { + dialogRef.current.close(); + } + }, [isOpen]); + + const mutation = useMutation({ + mutationFn: () => { + return request.post('/wishes', {productId:id} ) + }, + onError: (error) => console.log(error), + onSuccess: async (data:any) => { + const { status } = data; + dispatch(handleWishlistCount(status == 200 ? await wishNumber + 1 : await wishNumber - 1)); + showToast(data.message ,'success') + await refetch(); + handleClose(); + + }, + }) + + const handleDeletion = async () => { + mutation.mutate(); + }; + const action = deleting == 'product'? handleDeletion : handleClearWishlist; + return ( + e.stopPropagation()} className='z-50 bg-white backdrop-filter backdrop-brightness-75 rounded shadow-lg w-[80%] py-6 px-4 sm:w-[30%] mx-auto'> + +
+

Delete Wished Prroduct

+
+ Are you sure you want to {deleting == 'product' ? 'remove this product from your wish list?' : 'clear your wish list?!'} +
+ +
+
+ ); +}; + +export default DeleteWishlistPopup; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index d12ccaa..f7aa763 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -22,6 +22,8 @@ import { handleFetchUserCart } from '@/hooks/userCart'; import Logout from '@/hooks/logout'; import NotificationIcon from './ui-components/NotificationIcon'; import Notification from './ui-components/Notification'; +import WishlistOverlay from '@/hooks/wishlistOverlay'; +import WishlistContainer from './wishlistContainer'; import { Dropdown, @@ -33,9 +35,14 @@ import { } from '@nextui-org/react'; import { useRouter } from 'next/navigation'; const Header = () => { + const { wishNumber } = useAppSelector( + (state: RootState) => state.wishlist + ) + const { isOrdersOverlayOpen, toggleOrdersSlider } = OrdersOverlay(); - + const { isWishlistOverlayOpen, toggleWishlistSlider } = WishlistOverlay(); const [showlModal, setShowmodal] = useState(false); + const [showCart, setShowCart] = useState(false); const [showNotification, setShowNotification] = useState(false); @@ -66,6 +73,8 @@ const Header = () => { const handleshow = () => { setShowmodal(!showlModal); }; + + const [viewMenu, setViewmenu] = useState(false); const [userdata, setUserdata] = useState(null); useEffect(() => { @@ -96,23 +105,28 @@ const Header = () => {
{userdata && userdata.User.Role.name === 'buyer' && ( + <> {cart?.product.length} + + + {wishNumber} + + + + )} {userdata ? ( <> -
- -
-
- -
+ + ) : ( '' @@ -237,7 +251,7 @@ const Header = () => { ) : ( <> dropDownShowEvent(isOpen)} + onOpenChange={(isOpen: boolean) => dropDownShowEvent(isOpen)} >
+ {isWishlistOverlayOpen ? ( + + ) : ( + '' + )} {overlayComponent && ( {overlayComponent === 'cart' && ( diff --git a/src/components/LatestCard.tsx b/src/components/LatestCard.tsx index 95f41e8..889f4ed 100644 --- a/src/components/LatestCard.tsx +++ b/src/components/LatestCard.tsx @@ -27,7 +27,7 @@ const LatestCard: React.FC = ({

{name}

-

Price:{price} RWF

+

Price:{price.toLocaleString()} RWF

diff --git a/src/components/OrdersContainer.tsx b/src/components/OrdersContainer.tsx index d420ee5..dec9cf2 100644 --- a/src/components/OrdersContainer.tsx +++ b/src/components/OrdersContainer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import BuyerOrdersList from './BuyerOrdersList'; -import OrdersOverlay from '@/hooks/ordersOverlay'; + interface OrdersContainerInterface{ toggleOrdersSlider?:()=>void; isOrdersOverlayOpen?:boolean; diff --git a/src/components/ReviewCard.tsx b/src/components/ReviewCard.tsx index 43efc4b..a4b955e 100644 --- a/src/components/ReviewCard.tsx +++ b/src/components/ReviewCard.tsx @@ -13,7 +13,7 @@ interface ReviewInterface { const ReviewCard: React.FC =({ rating, feedback, image, firstName, lastName }) =>{ return(
-
+
diff --git a/src/components/ReviewProductPopup.tsx b/src/components/ReviewProductPopup.tsx index ed6a1a1..1395c70 100644 --- a/src/components/ReviewProductPopup.tsx +++ b/src/components/ReviewProductPopup.tsx @@ -4,6 +4,7 @@ import request from '@/utils/axios'; import { useQuery, useMutation, useQueryClient, RefetchOptions, QueryObserverResult } from "@tanstack/react-query"; import { toast } from 'react-toastify';//@ts-ignore import ReactStars from "react-rating-stars-component"; +import { showToast } from '@/helpers/toast'; interface ReviewProductInterface { isOpen: boolean, id: string | null; @@ -28,7 +29,7 @@ export const ReviewProduct: React.FC = ({ id, isOpen, h dialogRef.current.close(); } }, [isOpen]); - const ratingChanged = (newRating:any) => { + const ratingChanged = async (newRating:any) => { setRating(newRating); }; const mutation = useMutation({ @@ -36,18 +37,8 @@ export const ReviewProduct: React.FC = ({ id, isOpen, h return request.post(`/products/${id}/reviews`, { feedback, rating }) }, onError: (error) => console.log(error), - onSuccess: async () => { - toast('successfully submitted your review', { - position: "top-right", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - type: "success", - }); + onSuccess: async (data:any) => { + showToast(data.message, 'success'); await refetch(); handleClose(); }, @@ -55,12 +46,12 @@ export const ReviewProduct: React.FC = ({ id, isOpen, h const handleSubmitReiew = async () => { const feedback = textArea.current?.value as string; - if(feedback.length < 10 && rating === 0){ - setFeedbackErrro("Feedback must be 10 chars atleast"); + if(feedback.length < 10 && rating === 0 ){ + setFeedbackErrro("Feedback must have between 10 - 300 chars"); setRatingError('Please add rating'); return; - }if(feedback.length < 10) { - setFeedbackErrro("Feedback must be 10 chars atleast"); + }if(feedback.length < 10 || feedback.length > 300 ) { + setFeedbackErrro("Feedback must have between 10 - 300 chars"); return; }if(rating === 0){ setRatingError('Please add rating'); diff --git a/src/components/ReviewsWrapper.tsx b/src/components/ReviewsWrapper.tsx new file mode 100644 index 0000000..a3330f8 --- /dev/null +++ b/src/components/ReviewsWrapper.tsx @@ -0,0 +1,67 @@ +import { ReviewType } from '@/types/Product'; +import { QueryObserverResult, RefetchOptions, useQuery } from '@tanstack/react-query'; +import React, { useEffect, useState } from 'react'; +import ReviewCard from './ReviewCard'; +import Button from './Button'; +import ReviewPopup from '@/hooks/reviewPopup'; +import ReviewProduct from './ReviewProductPopup'; +import request from '@/utils/axios'; + +interface reviewWrapper{ + refetch: (options?: RefetchOptions | undefined) => Promise>; + reviews:ReviewType[]; + productId:string; +} + +const ReviewWrapper:React.FC=({ refetch, reviews, productId })=>{ + const { isReviewPopupOpen, setIsReviewPopupOpen, toggleReviewPopup} = ReviewPopup(); + const [currentUser, setCurrentUser] = useState([]); + + useEffect(()=>{ + const { User } = JSON.parse(localStorage.getItem('profile') as string); + const { id } = User + if(User){ + setCurrentUser(id) + } + }) + + const { isLoading, error, data } = useQuery({ + queryKey: ['CheckBuyerOrders'], + queryFn: () => request.get('/orders'), + }); + //setOrders(data) + var ordersData = data?.orders ?? []; + const canUserAddReview = ordersData.filter((item: { buyerId: any; productId:string; isPaid: any; }) => item.buyerId == currentUser && item.productId == productId && item.isPaid) + return( + <> +
+
+

Reviews:

+ {canUserAddReview.length ? + + :"" } +
+
+ {reviews && reviews.length > 0 ? ( + reviews.map((review: ReviewType) => ( + + )) + ) : ( +

No ratings yet.

+ )} +
+
+ {isReviewPopupOpen && + + } + + ) +}; + +export default ReviewWrapper; \ No newline at end of file diff --git a/src/components/productWrapper.tsx b/src/components/productWrapper.tsx new file mode 100644 index 0000000..db14f6f --- /dev/null +++ b/src/components/productWrapper.tsx @@ -0,0 +1,207 @@ +import React, { useState } from 'react'; +import image from '../../public/product.png'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import ReviewCard from '@/components/ReviewCard'; +import Button from '@/components/Button'; +import { averageReviews } from '@/utils/averageReviews'; +import { RootState, useAppDispatch, useAppSelector } from '@/redux/store'; +import { handleUserAddCart } from '@/redux/slices/userCartSlice'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import 'swiper/css'; +import 'swiper/css/free-mode'; +import 'swiper/css/navigation'; +import 'swiper/css/thumbs'; +import { FreeMode, Navigation, Thumbs } from 'swiper/modules'; +import Image from 'next/image'; //@ts-ignore +import ReactStars from 'react-rating-stars-component'; +import { MdOutlineShoppingCart } from 'react-icons/md'; +import { FaRegHeart } from 'react-icons/fa6'; +import { useParams } from 'next/navigation'; +import { Product } from '@/utils/requests'; +import { + ProductObj, + ProductType, + ReviewType, + imageType, +} from '@/types/Product'; +import Card from '@/components/Card'; +import ReviewWrapper from './ReviewsWrapper'; +import request from '@/utils/axios'; +import { showToast } from '@/helpers/toast'; +import { handleWishlistCount } from '@/redux/slices/wishlistSlice'; +const ProductWrapper = ()=>{ + const { wishNumber } = useAppSelector( + (state: RootState) => state.wishlist + ) + const [thumbsSwiper, setThumbsSwiper] = useState(null); + + const { id } :any= useParams(); + const handleSwiper = (swiper: any) => { + setThumbsSwiper(swiper); + }; + const _id: string = id.toLocaleString(); + const { data, isLoading, error , refetch} = useQuery({ + queryKey: ['product', id], + queryFn: async () => { + try { + const response: ProductType = (await Product.single( + _id, + )) as ProductType; + + return response; + } catch (error) { + throw new Error('Error fetching product data'); + } + }, + }); + if (isLoading) return Loading...; + + if (error) return Error: {error.message}; + + const { + productPictures, + productName, + productPrice, + productDescription, + reviews + } = data.product; + console.log('this is data.product >>>>>>>>', data.product); + const { relatedProducts } = data; + const dispatch = useAppDispatch(); + const handleNewItem = () => { + const productId = data.product.id; + dispatch(handleUserAddCart({ productPrice, productId })); + }; + + const handleAddRemoveWish = async(event: { preventDefault: () => void; })=>{ + event.preventDefault(); + const response:any = await request.post('/wishes', { productId:id }); + + if(response.status == 200 || response.status == 203){ + const { status } = response; + dispatch(handleWishlistCount(status == 200 ? await wishNumber + 1 : await wishNumber - 1)); + showToast(response.message, 'success') + } + console.log('this is response', response) + + } + return( +
+
+
+
+
+ {productPictures && productPictures.length > 0 ? ( + + {productPictures.map((image: imageType) => { + return ( + + image + + ); + })} + + ) : ( + {'no + )} +
+
+ {productPictures && productPictures.length > 0 ? ( +
+ + {productPictures.map((image: imageType) => { + return ( + + image + + ); + })} + +
+ ) : ( +

no image found!

+ )} +
+
+
+
+

{productName}

+
+
+
+
+ +
+
+ { + handleNewItem(); + }} + /> +
+
+ + RWF {productPrice.toLocaleString()} + +
+
+ +
+
+

Description:

+

{productDescription}

+
+
+
+
+

Related products:

+
+
+ {relatedProducts && relatedProducts.length > 0 ? ( + relatedProducts.map((product: ProductType) => ( + + )) + ) : ( +

+ No related products available. +

+ )} +
+
+
+ +
+
+ ) +}; + +export default ProductWrapper \ No newline at end of file diff --git a/src/components/wishlistContainer.tsx b/src/components/wishlistContainer.tsx new file mode 100644 index 0000000..7c77edb --- /dev/null +++ b/src/components/wishlistContainer.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import BuyerWishList from './BuyerWishlist'; + +interface WishlistContainerInterface{ + toggleWishlistSlider?:()=>void; + isWishlistOverlayOpen?:boolean; +} +const WishlistContainer:React.FC = ({ isWishlistOverlayOpen, toggleWishlistSlider }) => { + return ( + <> +
+
+
+
+ + + +
+
+ +
+
+
+ + ); +}; +export default WishlistContainer; \ No newline at end of file diff --git a/src/hooks/reviewPopup.tsx b/src/hooks/reviewPopup.tsx new file mode 100644 index 0000000..b0d2659 --- /dev/null +++ b/src/hooks/reviewPopup.tsx @@ -0,0 +1,18 @@ + +import React, { useState } from 'react'; + +function ReviewPopup() { + const [isReviewPopupOpen, setIsReviewPopupOpen] = useState(false) + const toggleReviewPopup = () => { + setIsReviewPopupOpen(!isReviewPopupOpen); + }; + + return{ + isReviewPopupOpen, + setIsReviewPopupOpen, + toggleReviewPopup + } +}; + +export default ReviewPopup; + \ No newline at end of file diff --git a/src/hooks/wishlistOverlay.tsx b/src/hooks/wishlistOverlay.tsx new file mode 100644 index 0000000..a38e9af --- /dev/null +++ b/src/hooks/wishlistOverlay.tsx @@ -0,0 +1,18 @@ + +import React, { useState } from 'react'; + +function WishlistOverlay() { + const [isWishlistOverlayOpen, setIsWishlistOverlayOpen] = useState(false) + const toggleWishlistSlider = () => { + setIsWishlistOverlayOpen(!isWishlistOverlayOpen); + }; + + return{ + isWishlistOverlayOpen, + setIsWishlistOverlayOpen, + toggleWishlistSlider + } +}; + +export default WishlistOverlay; + \ No newline at end of file diff --git a/src/redux/slices/wishlistSlice.ts b/src/redux/slices/wishlistSlice.ts new file mode 100644 index 0000000..228c2de --- /dev/null +++ b/src/redux/slices/wishlistSlice.ts @@ -0,0 +1,33 @@ +import request from '@/utils/axios'; +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; + +const handleFetchUserWishes = async ():Promise=>{ + const token = localStorage.getItem('token'); + if(token){ + const response:any = await request.get('/wishes'); + console.log('this is data from wishlist', response); + if(response.status == 200){ + return response.wishes.length; + } + } + return 0; +}; + +const initialState = { + wishNumber: handleFetchUserWishes() +}; +export const userWishlistSlice = createSlice({ + name: 'wishlistCounter', + initialState, + reducers: { + handleWishlistCount: (state, action: PayloadAction) => { + const result = action.payload; + state.wishNumber = result; + } + } +}); + +export const { handleWishlistCount } = userWishlistSlice.actions; +export default userWishlistSlice.reducer; + + diff --git a/src/redux/store.ts b/src/redux/store.ts index 0a92616..4372474 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -14,6 +14,9 @@ import userProfileSlice from '@/redux/slices/profileSlice'; import notificationSlice from './slices/notificationSlice'; +import userWishlistSlice from './slices/wishlistSlice'; + + const rootReducer = combineReducers({ auth: auth, productsAddReducers, @@ -24,6 +27,7 @@ const rootReducer = combineReducers({ profileReducer, userProfile: userProfileSlice, notification: notificationSlice, + wishlist: userWishlistSlice }); export const store = configureStore({