diff --git a/package.json b/package.json index 306ae02..871f433 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "prepare": "husky" }, "dependencies": { + "@radix-ui/react-primitive": "^2.1.3", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-slot": "^1.2.3", "@react-spring/web": "^10.0.3", "@react-three/drei": "^10.7.6", "@react-three/fiber": "^9.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b54ded1..7bac4f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,15 @@ settings: importers: .: dependencies: + '@radix-ui/react-primitive': + specifier: ^2.1.3 + version: 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.13)(react@19.1.0) '@react-spring/web': specifier: ^10.0.3 version: 10.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -512,6 +521,33 @@ packages: } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@floating-ui/core@1.7.3': + resolution: + { + integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==, + } + + '@floating-ui/dom@1.7.4': + resolution: + { + integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==, + } + + '@floating-ui/react-dom@2.1.6': + resolution: + { + integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==, + } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: + { + integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==, + } + '@humanfs/core@0.19.1': resolution: { @@ -967,6 +1003,336 @@ packages: } engines: { node: '>=12.4.0' } + '@radix-ui/number@1.1.1': + resolution: + { + integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==, + } + + '@radix-ui/primitive@1.1.3': + resolution: + { + integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==, + } + + '@radix-ui/react-arrow@1.1.7': + resolution: + { + integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: + { + integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: + { + integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: + { + integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: + { + integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: + { + integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: + { + integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: + { + integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: + { + integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: + { + integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: + { + integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: + { + integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: + { + integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: + { + integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: + { + integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: + { + integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: + { + integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: + { + integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: + { + integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: + { + integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: + { + integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: + { + integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==, + } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: + { + integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==, + } + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: + { + integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==, + } + '@react-native/assets-registry@0.81.4': resolution: { @@ -2176,6 +2542,13 @@ packages: integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, } + aria-hidden@1.2.6: + resolution: + { + integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==, + } + engines: { node: '>=10' } + aria-query@5.3.2: resolution: { @@ -2264,12 +2637,6 @@ packages: integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==, } - asynckit@0.4.0: - resolution: - { - integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, - } - autoprefixer@10.4.21: resolution: { @@ -2294,12 +2661,6 @@ packages: } engines: { node: '>=4' } - axios@1.12.2: - resolution: - { - integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==, - } - axobject-query@4.1.0: resolution: { @@ -2579,13 +2940,6 @@ packages: } engines: { node: '>=12.5.0' } - combined-stream@1.0.8: - resolution: - { - integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, - } - engines: { node: '>= 0.8' } - commander@12.1.0: resolution: { @@ -2720,13 +3074,6 @@ packages: } engines: { node: '>= 0.4' } - delayed-stream@1.0.0: - resolution: - { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, - } - engines: { node: '>=0.4.0' } - depd@2.0.0: resolution: { @@ -2754,6 +3101,12 @@ packages: } engines: { node: '>=8' } + detect-node-es@1.1.0: + resolution: + { + integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, + } + doctrine@2.1.0: resolution: { @@ -3294,31 +3647,12 @@ packages: integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==, } - follow-redirects@1.15.11: - resolution: - { - integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==, - } - engines: { node: '>=4.0' } - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.5: - resolution: - { - integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, - } - engines: { node: '>= 0.4' } - - form-data@4.0.4: + for-each@0.3.5: resolution: { - integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==, + integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, } - engines: { node: '>= 6' } + engines: { node: '>= 0.4' } fraction.js@4.3.7: resolution: @@ -3404,6 +3738,13 @@ packages: } engines: { node: '>= 0.4' } + get-nonce@1.0.1: + resolution: + { + integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==, + } + engines: { node: '>=6' } + get-package-type@0.1.0: resolution: { @@ -5005,12 +5346,6 @@ packages: integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, } - proxy-from-env@1.1.0: - resolution: - { - integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, - } - punycode@2.3.1: resolution: { @@ -5163,6 +5498,32 @@ packages: } engines: { node: '>=0.10.0' } + react-remove-scroll-bar@2.3.8: + resolution: + { + integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==, + } + engines: { node: '>=10' } + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: + { + integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==, + } + engines: { node: '>=10' } + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-spring@10.0.3: resolution: { @@ -5172,6 +5533,19 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-style-singleton@2.2.3: + resolution: + { + integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==, + } + engines: { node: '>=10' } + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + react-tilt@1.0.2: resolution: { @@ -6035,6 +6409,32 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, } + use-callback-ref@1.3.3: + resolution: + { + integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==, + } + engines: { node: '>=10' } + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: + { + integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==, + } + engines: { node: '>=10' } + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.5.0: resolution: { @@ -6517,6 +6917,23 @@ snapshots: '@eslint/core': 0.15.2 levn: 0.4.1 + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@floating-ui/utils@0.2.10': {} + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.7': @@ -6765,6 +7182,224 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-context@1.1.2(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.13)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.13)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.13)(react@19.1.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.13 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + '@types/react-dom': 19.1.9(@types/react@19.1.13) + + '@radix-ui/rect@1.1.1': {} + '@react-native/assets-registry@0.81.4': {} '@react-native/codegen@0.81.4(@babel/core@7.28.4)': @@ -7590,6 +8225,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -7667,8 +8306,6 @@ snapshots: async-limiter@1.0.1: {} - asynckit@0.4.0: {} - autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.26.0 @@ -7685,14 +8322,6 @@ snapshots: axe-core@4.10.3: {} - axios@1.12.2: - dependencies: - follow-redirects: 1.15.11 - form-data: 4.0.4 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axobject-query@4.1.0: {} babel-jest@29.7.0(@babel/core@7.28.4): @@ -7890,10 +8519,6 @@ snapshots: color-string: 1.9.1 optional: true - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - commander@12.1.0: {} commander@2.20.3: {} @@ -7969,8 +8594,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} - depd@2.0.0: {} destroy@1.2.0: {} @@ -7981,6 +8604,8 @@ snapshots: detect-libc@2.0.4: {} + detect-node-es@1.1.0: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -8446,20 +9071,10 @@ snapshots: flow-enums-runtime@0.0.6: {} - follow-redirects@1.15.11: {} - for-each@0.3.5: dependencies: is-callable: 1.2.7 - form-data@4.0.4: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - fraction.js@4.3.7: {} framer-motion@12.23.14(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -8508,6 +9123,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-package-type@0.1.0: {} get-proto@1.0.1: @@ -9479,8 +10096,6 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - proxy-from-env@1.1.0: {} - punycode@2.3.1: {} qs@6.14.0: @@ -9609,6 +10224,25 @@ snapshots: react-refresh@0.14.2: {} + react-remove-scroll-bar@2.3.8(@types/react@19.1.13)(react@19.1.0): + dependencies: + react: 19.1.0 + react-style-singleton: 2.2.3(@types/react@19.1.13)(react@19.1.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.13 + + react-remove-scroll@2.7.1(@types/react@19.1.13)(react@19.1.0): + dependencies: + react: 19.1.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.13)(react@19.1.0) + react-style-singleton: 2.2.3(@types/react@19.1.13)(react@19.1.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.13)(react@19.1.0) + use-sidecar: 1.1.3(@types/react@19.1.13)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.13 + react-spring@10.0.3(@react-three/fiber@9.3.0(@types/react@19.1.13)(immer@10.1.3)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react@19.1.0)(three@0.180.0))(konva@10.0.2)(react-dom@19.1.0(react@19.1.0))(react-konva@19.0.10(@types/react@19.1.13)(konva@10.0.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.4)(@types/react@19.1.13)(react@19.1.0))(react-zdog@1.2.2)(react@19.1.0)(three@0.180.0)(zdog@1.1.3): dependencies: '@react-spring/core': 10.0.3(react@19.1.0) @@ -9628,6 +10262,14 @@ snapshots: - three - zdog + react-style-singleton@2.2.3(@types/react@19.1.13)(react@19.1.0): + dependencies: + get-nonce: 1.0.1 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.13 + react-tilt@1.0.2(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@types/react': 19.1.13 @@ -10230,6 +10872,21 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@19.1.13)(react@19.1.0): + dependencies: + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.13 + + use-sidecar@1.1.3(@types/react@19.1.13)(react@19.1.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.13 + use-sync-external-store@1.5.0(react@19.1.0): dependencies: react: 19.1.0 diff --git a/src/app/compare/CompareItemCard.tsx b/src/app/compare/CompareItemCard.tsx new file mode 100644 index 0000000..aee8533 --- /dev/null +++ b/src/app/compare/CompareItemCard.tsx @@ -0,0 +1,166 @@ +'use client'; + +import React from 'react'; +import Image from 'next/image'; +import { ProductType } from '@/types/CategoryType'; +import CloseComponent from '@/components/UI/ShoppingCartIcons/CloseComponent'; +import { toast } from 'sonner'; +import { removeFromCompareByKey } from '@/lib/features/compare/compareSlice'; +import { useDispatch } from 'react-redux'; +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import AddOrNavToCartButton from '@/components/UI/AddOrNavToCartButton'; +import FavoriteButton from '@/components/Products/FavoriteButton'; +import { mapDetailsToSummary } from '@/lib/mappers'; + +interface CompareItemCardProps { + product: ProductType; + index?: number; + disableOnce?: boolean; + showSpecs?: boolean; + differingSpecs?: ProductType[]; +} + +const CompareItemCard: React.FC = ({ + product, + index = 0, + disableOnce = false, + showSpecs, + differingSpecs, +}) => { + const imgSrc = `/${product.images?.[0] || 'placeholder.png'}`; + + const dispatch = useDispatch(); + + const specList = [ + { key: 'capacity', label: 'Capacity' }, + { key: 'capacityAvailable', label: 'Capacity Available' }, + { key: 'screen', label: 'Screen' }, + { key: 'resolution', label: 'Resolution' }, + { key: 'processor', label: 'Processor' }, + { key: 'ram', label: 'RAM' }, + { key: 'camera', label: 'Camera' }, + { key: 'zoom', label: 'Zoom' }, + { key: 'cell', label: 'Cell' }, + ] as const; + + const handleRemove = () => { + dispatch( + removeFromCompareByKey({ + id: product.id, + color: product.color, + capacity: product.capacity, + }), + ); + toast(`${product.name} removed from compare`); + }; + + const differingKeys = React.useMemo(() => { + if (!differingSpecs || differingSpecs.length < 2) return []; + + return specList + .map((s) => s.key) + .filter((key) => { + const values = differingSpecs.map( + (item) => item[key as keyof ProductType], + ); + return new Set(values.map(String)).size > 1; + }); + }, [differingSpecs]); + + const specsToShow = + showSpecs ? specList : ( + specList.filter((s) => differingKeys.includes(s.key)) + ); + + return ( + + + + {product.name} + +

+ + {product.name} + +

+
+
+

+ ${product.priceDiscount} +

+

+ ${product.priceRegular} +

+
+
+
+
+ {specsToShow.map((spec) => { + const raw = product[spec.key]; + const value = + Array.isArray(raw) ? raw.join(' | ') + : raw !== undefined && raw !== null ? String(raw) + : '-'; + + return ( +
+ + {spec.label} + + + {value} + +
+ ); + })} +
+
+ + + +
+
+ ); +}; + +export default CompareItemCard; diff --git a/src/app/compare/CompareList.tsx b/src/app/compare/CompareList.tsx new file mode 100644 index 0000000..7a0a0e7 --- /dev/null +++ b/src/app/compare/CompareList.tsx @@ -0,0 +1,173 @@ +'use client'; + +import React, { useRef, useState, useEffect } from 'react'; +import { Swiper, SwiperSlide } from 'swiper/react'; +import { Navigation } from 'swiper/modules'; +import { motion } from 'framer-motion'; + +import 'swiper/css'; +import 'swiper/css/navigation'; +import CompareItemCard from './CompareItemCard'; +import { useSelector } from 'react-redux'; +import { ProductType } from '@/types/CategoryType'; +import { RootState } from '@/lib/store'; + +interface CompareSliderProps { + title?: string; + showSpecs: boolean; +} + +export default function CompareList({ title, showSpecs }: CompareSliderProps) { + const prevRef = useRef(null); + const nextRef = useRef(null); + const [isBeginning, setIsBeginning] = useState(true); + const [isEnd, setIsEnd] = useState(false); + const [windowWidth, setWindowWidth] = useState(0); + + const compareItems = useSelector( + (state: RootState) => state.persisted.compare.items, + ) as ProductType[]; + + const filteredItems = + title ? + compareItems.filter((item: ProductType) => item.category === title) + : compareItems; + + useEffect(() => { + setWindowWidth(window.innerWidth); + const handleResize = () => setWindowWidth(window.innerWidth); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + const getSlidesPerView = (width: number) => { + if (width >= 1200) return 4; + if (width >= 990) return 3; + if (width >= 640) return 2; + return 1; + }; + + const shouldShowNavButtons = + filteredItems.length > getSlidesPerView(windowWidth); + + if (filteredItems.length === 0) return null; + + const normalizeTitle = + title!.slice(0, 1).toUpperCase() + title!.slice(1).toLowerCase(); + + return ( +
+
+ + {normalizeTitle} + + +
+ {shouldShowNavButtons && ( + <> + + + + + )} +
+
+ +
+ { + if (prevRef.current && nextRef.current) { + // @ts-expect-error: Swiper types don't know about refs + swiper.params.navigation.prevEl = prevRef.current; + // @ts-expect-error: Swiper types don't know about refs + swiper.params.navigation.nextEl = nextRef.current; + } + }} + onInit={(swiper) => { + swiper.navigation.init(); + swiper.navigation.update(); + setIsBeginning(swiper.isBeginning); + setIsEnd(swiper.isEnd); + }} + onSlideChange={(swiper) => { + setIsBeginning(swiper.isBeginning); + setIsEnd(swiper.isEnd); + }} + breakpoints={{ + 0: { slidesPerView: 1 }, + 640: { slidesPerView: 2 }, + 990: { slidesPerView: 3 }, + 1200: { slidesPerView: 4 }, + }} + className="multiple-slide-carousel" + > + {filteredItems.map((product, idx) => ( + + + + ))} + +
+
+ ); +} diff --git a/src/app/compare/ComparePage.tsx b/src/app/compare/ComparePage.tsx new file mode 100644 index 0000000..a1cc2a1 --- /dev/null +++ b/src/app/compare/ComparePage.tsx @@ -0,0 +1,176 @@ +'use client'; + +import React, { useMemo, useState, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { RootState } from '@/lib/store'; +import { ProductType } from '@/types/CategoryType'; +import { CategoryName } from '@/types/CategoryName'; +import SelectArrow from '@/components/UI/SelectArrow'; +import { + clearCompare, + clearCompareByCategory, +} from '@/lib/features/compare/compareSlice'; +import CategoryHeader from '@/components/UI/CategoryHeader'; +import ActionButton from '@/components/UI/ActionButton'; +import CompareList from './CompareList'; + +const categoryLabels: Partial> = { + [CategoryName.Phones]: 'Phones', + [CategoryName.Tablets]: 'Tablets', + [CategoryName.Accessories]: 'Accessories', +}; + +const ComparePage = () => { + const [showSpecs, setShowSpecs] = useState(true); + const compareItems = useSelector( + (state: RootState) => state.persisted.compare.items, + ); + + const [mounted, setMounted] = useState(false); + const [selectedCategory, setSelectedCategory] = useState( + '', + ); + + const dispatch = useDispatch(); + + useEffect(() => setMounted(true), []); + + const itemsByCategory = useMemo(() => { + return compareItems.reduce>( + (acc, item) => { + if (!acc[item.category]) acc[item.category] = []; + acc[item.category].push(item); + return acc; + }, + {} as Record, + ); + }, [compareItems]); + + const availableCategories = Object.keys(itemsByCategory).filter( + (cat) => itemsByCategory[cat as CategoryName].length > 0, + ) as CategoryName[]; + + useEffect(() => { + if (!selectedCategory && availableCategories.length > 0) { + setSelectedCategory(availableCategories[0]); + } else if ( + selectedCategory && + (!itemsByCategory[selectedCategory] || + itemsByCategory[selectedCategory].length === 0) + ) { + setSelectedCategory(availableCategories[0] || ''); + } + }, [selectedCategory, itemsByCategory, availableCategories]); + + if (!mounted) return null; + + const productTypes = + selectedCategory && itemsByCategory[selectedCategory] ? + itemsByCategory[selectedCategory] + : []; + + const hasProducts = productTypes.length > 0; + if (!hasProducts) { + return ( +
+

+ Your compare list is empty +

+

+ Looks like you haven't added anything yet. +

+ +
+ ); + } + + return ( +
+ + + {hasProducts && ( + <> + {/* Вибір категорії */} +
+
+ + + +
+ +
+ {[ + { + label: 'Show All', + value: true, + onClick: () => setShowSpecs(true), + isToggle: true, + }, + { + label: 'Show Differences', + value: false, + onClick: () => setShowSpecs(false), + isToggle: true, + }, + { label: 'Clear all', onClick: () => dispatch(clearCompare()) }, + { + label: 'Clear category', + onClick: () => + dispatch(clearCompareByCategory(selectedCategory)), + }, + ].map((btn) => ( + + ))} +
+
+ + + )} +
+ ); +}; + +export default ComparePage; diff --git a/src/app/compare/page.tsx b/src/app/compare/page.tsx new file mode 100644 index 0000000..1e4c77f --- /dev/null +++ b/src/app/compare/page.tsx @@ -0,0 +1,7 @@ +import ComparePage from './ComparePage'; + +const page = () => { + return ; +}; + +export default page; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index cb45d41..7bdf40d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -65,10 +65,7 @@ export default function RootLayout({ }, }} /> -
+
diff --git a/src/components/Layout/NavBar/HydradetIcons.tsx b/src/components/Layout/NavBar/HydradetIcons.tsx index 6508eeb..fb2c771 100644 --- a/src/components/Layout/NavBar/HydradetIcons.tsx +++ b/src/components/Layout/NavBar/HydradetIcons.tsx @@ -5,6 +5,8 @@ import { HydrationProvider } from '@/lib/HydrationProvider'; import { IconsSkeleton } from './IconsSkeleton'; import FavouritesLink from '@/components/UI/NavBar/FavouritesLink'; import ShoppingCartLink from '@/components/UI/NavBar/ShoppingCartLink'; +import ThemeSwitcher from '@/components/UI/ThemeSwitcher'; +import CompareLink from '@/components/UI/NavBar/CompareLink'; type IconsProps = { onClose?: () => void; @@ -14,6 +16,35 @@ export function HydratedIcons({ onClose }: IconsProps) { return ( }> {/* Цей блок буде показано ПІСЛЯ гідратації */} + +
+
+ +
+ +
+ +
+
= ({ onClose }) => { direction="col" />
-
- -
- - {/*
- - -
*/}
); diff --git a/src/components/Layout/NavBar/NavBar.tsx b/src/components/Layout/NavBar/NavBar.tsx index 41136b6..6c313d1 100644 --- a/src/components/Layout/NavBar/NavBar.tsx +++ b/src/components/Layout/NavBar/NavBar.tsx @@ -31,7 +31,7 @@ const NavBar: React.FC = () => { return ( <> -
diff --git a/src/components/Products/ProductCart.tsx b/src/components/Products/ProductCart.tsx index 5f1c684..f9c7a1a 100644 --- a/src/components/Products/ProductCart.tsx +++ b/src/components/Products/ProductCart.tsx @@ -1,5 +1,5 @@ import Image from 'next/image'; -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Product } from '@/types/product'; import Link from 'next/link'; import { motion } from 'framer-motion'; @@ -7,6 +7,8 @@ import { motion } from 'framer-motion'; import FavoriteButton from '@/components/Products/FavoriteButton'; import AddOrNavToCartButton from '../UI/AddOrNavToCartButton'; +import AddToCompareButton from './AddToCompareButton'; +import { useGetProductByIdQuery } from '@/lib/features/api/apiSlice'; interface ProductCartProps { product: Product; @@ -21,6 +23,8 @@ const ProductCart = ({ }: ProductCartProps) => { const imgSrc = `/${product.image}`; + const { data: productDetails } = useGetProductByIdQuery(product.itemId); + return ( -
+
+ {productDetails && ( +
+ +
+ )} +

- {total} {!isFavourites ? 'models' : 'items'} -

+ > + {total} {!isFavourites ? 'models' : 'items'} +

+
); }; diff --git a/src/components/UI/CategoryHeader.tsx b/src/components/UI/CategoryHeader.tsx index 337a60c..b15f970 100644 --- a/src/components/UI/CategoryHeader.tsx +++ b/src/components/UI/CategoryHeader.tsx @@ -9,11 +9,12 @@ type Props = { isFavourites?: true; categoryName: CategoryName; - total: number; + total?: number; }; const CategoryHeader: React.FC = ({ isCart, isFavourites, + categoryName, total, }) => { @@ -25,12 +26,12 @@ const CategoryHeader: React.FC = ({ {isFavourites ? : } diff --git a/src/components/UI/NavBar/CategoriesMenu.tsx b/src/components/UI/NavBar/CategoriesMenu.tsx index 2e9b252..bc2e28f 100644 --- a/src/components/UI/NavBar/CategoriesMenu.tsx +++ b/src/components/UI/NavBar/CategoriesMenu.tsx @@ -15,17 +15,13 @@ const CategoriesMenu: React.FC = ({ direction = 'row', onClose }) => { return (
    {NavBarCategories.slice(0, 4).map((category) => { // TODO: must be remove to separate component in future const isActive = - ( - pathname === `/${category}` || - (category === CategoryName.Home && pathname === '/') - ) ? - 'after:absolute after:left-0 after:right-0 after:h-[3px] after:bg-light-theme-text-hover after:bottom-0 after:scale-x-100' - : ''; + pathname === `/${category}` || + (category === CategoryName.Home && pathname === '/'); return (
  • @@ -33,14 +29,16 @@ const CategoriesMenu: React.FC = ({ direction = 'row', onClose }) => { href={category === CategoryName.Home ? `/` : `/${category}`} onClick={onClose} className={` - relative text-light-theme-text-menu text-[12px] font-extrabold uppercase tracking-[1] + relative text-light-theme-text-menu text-[12px] font-extrabold uppercase tracking-[1] + hover:text-light-theme-text-hover transition-colors duration-200 after:absolute after:left-0 after:right-0 after:h-[2px] after:bg-light-theme-text-hover after:scale-x-0 after:transition-transform after:duration-200 - hover:after:scale-x-100 - ${isRow ? 'after:h-[3px] after:bottom-[-14px] lg:after:bottom-[-22px]' : 'after:bottom-[-8px]'} - dark:hover:text-white dark:after:bg-dark-theme-text dark:text-text-gray - ${isActive} + hover:after:scale-x-100 + ${isRow ? 'after:h-[3px] after:bottom-[-14px] lg:after:bottom-[-24px]' : 'after:bottom-[-8px]'} + + dark:hover:text-white dark:after:bg-dark-theme-text dark:text-text-gray + ${isActive && 'dark:text-white text-product-add-btn-selected after:absolute after:left-0 after:right-0 after:h-[3px] after:bg-light-theme-text-hover after:bottom-0 after:scale-x-100'} `} > {category} diff --git a/src/components/UI/NavBar/CompareLink.tsx b/src/components/UI/NavBar/CompareLink.tsx new file mode 100644 index 0000000..b61d905 --- /dev/null +++ b/src/components/UI/NavBar/CompareLink.tsx @@ -0,0 +1,60 @@ +// #region Imports +import Image from 'next/image'; +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import ScaleBlack from '@/components/UI/icons/Scale(Black).svg'; +import ScaleWhite from '@/components/UI/icons/Scale(White).svg'; +import Link from 'next/link'; +import { CategoryName } from '@/types/CategoryName'; +import { RootState } from '@/lib/store'; + +// #endregion +type Props = { + onClose?: () => void; + isBurger?: boolean; +}; +const CompareLink: React.FC = ({ onClose, isBurger = false }) => { + const [isClient, setIsClient] = useState(false); + const compareCount = useSelector( + (state: RootState) => state.persisted.compare.items.length, + ); + + useEffect(() => { + setIsClient(true); + }, []); + + return ( + <> + + + + + ); +}; + +export default CompareLink; diff --git a/src/components/UI/NavBar/FavouritesLink.tsx b/src/components/UI/NavBar/FavouritesLink.tsx index 911f0d0..5830e1e 100644 --- a/src/components/UI/NavBar/FavouritesLink.tsx +++ b/src/components/UI/NavBar/FavouritesLink.tsx @@ -28,39 +28,41 @@ const FavouritesLink: React.FC = ({ onClose, isBurger = false }) => { setIsClient(true); }, []); - const baseClasses = ` - relative block - ${ - isBurger ? - `w-full h-16 flex justify-center items-center // Додаємо items-center - border-r border-t border-light-theme-border-color dark:border-dark-theme-border-color - after:absolute after:left-0 after:right-0 after:h-[2px] after:bg-light-theme-text-hover after:bottom-0 - after:scale-x-0 hover:after:scale-x-100 after:origin-bottom after:transition-transform after:duration-200 dark:after:bg-dark-theme-text` - : 'p-2' - }`; - + const baseClasses = + isBurger ? + `flex justify-center items-center h-full w-full relative + border-r border-t border-light-theme-border-color dark:border-dark-theme-border-color + after:absolute after:left-0 after:right-0 after:h-[2px] after:bg-light-theme-text-hover after:bottom-0 + after:scale-x-0 hover:after:scale-x-100 after:origin-bottom after:transition-transform after:duration-200 dark:after:bg-dark-theme-text` + : ''; return ( - FavouritesBlack +
    + FavouritesBlack - FavouritesWhite - {isClient && favoritesCount > 0 && ( - - {favoritesCount > 9 ? '9+' : favoritesCount} - - )} + FavouritesWhite + {isClient && favoritesCount > 0 && ( + + {favoritesCount > 9 ? '9+' : favoritesCount} + + )} +
    ); }; diff --git a/src/components/UI/NavBar/ShoppingCartLink.tsx b/src/components/UI/NavBar/ShoppingCartLink.tsx index e7abff5..c155f16 100644 --- a/src/components/UI/NavBar/ShoppingCartLink.tsx +++ b/src/components/UI/NavBar/ShoppingCartLink.tsx @@ -24,33 +24,40 @@ const ShoppingCartLink: React.FC = ({ onClose, fullWidth = false }) => { setIsClient(true); }, []); + const baseClasses = + fullWidth ? + `flex justify-center items-center h-full w-full relative + border-r border-t border-light-theme-border-color dark:border-dark-theme-border-color + after:absolute after:left-0 after:right-0 after:h-[2px] after:bg-light-theme-text-hover after:bottom-0 + after:scale-x-0 hover:after:scale-x-100 after:origin-bottom after:transition-transform after:duration-200 dark:after:bg-dark-theme-text` + : ''; return ( - CartBlack - CartWhite - {isClient && itemCount > 0 && ( - - {itemCount > 9 ? '9+' : itemCount} - - )} +
    + CartBlack + CartWhite + {isClient && itemCount > 0 && ( + + {itemCount > 9 ? '9+' : itemCount} + + )} +
    ); }; diff --git a/src/components/UI/SelectArrow.tsx b/src/components/UI/SelectArrow.tsx index e81fea8..4bf1670 100644 --- a/src/components/UI/SelectArrow.tsx +++ b/src/components/UI/SelectArrow.tsx @@ -2,12 +2,17 @@ import React from 'react'; type Props = { className?: string; + isCompare?: boolean; + isCategory?: boolean; }; -const SelectArrow: React.FC = ({ className }) => { +const SelectArrow: React.FC = ({ className, isCategory, isCompare }) => { return ( = ({ placement = 'default' }) => { return ( <> - {/* black close */} - {/* white close */} {

    diff --git a/src/components/UI/icons/Cart(Black).svg b/src/components/UI/icons/Cart(Black).svg index 1aaff84..ecf9a93 100644 --- a/src/components/UI/icons/Cart(Black).svg +++ b/src/components/UI/icons/Cart(Black).svg @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/src/components/UI/icons/Cart(White).svg b/src/components/UI/icons/Cart(White).svg index d12428f..425ee63 100644 --- a/src/components/UI/icons/Cart(White).svg +++ b/src/components/UI/icons/Cart(White).svg @@ -1,5 +1,5 @@ - - - - + + + + diff --git a/src/components/UI/icons/Scale(Black).svg b/src/components/UI/icons/Scale(Black).svg new file mode 100644 index 0000000..6fc8952 --- /dev/null +++ b/src/components/UI/icons/Scale(Black).svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/UI/icons/Scale(Grey).svg b/src/components/UI/icons/Scale(Grey).svg new file mode 100644 index 0000000..df6fcd5 --- /dev/null +++ b/src/components/UI/icons/Scale(Grey).svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/UI/icons/Scale(White).svg b/src/components/UI/icons/Scale(White).svg new file mode 100644 index 0000000..cb16e56 --- /dev/null +++ b/src/components/UI/icons/Scale(White).svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/UI/icons/Scale(Yellow).svg b/src/components/UI/icons/Scale(Yellow).svg new file mode 100644 index 0000000..2a27a46 --- /dev/null +++ b/src/components/UI/icons/Scale(Yellow).svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/components/animate-ui/icons/x.tsx b/src/components/animate-ui/icons/x.tsx new file mode 100644 index 0000000..2bfb466 --- /dev/null +++ b/src/components/animate-ui/icons/x.tsx @@ -0,0 +1,126 @@ +'use client'; + +import * as React from 'react'; +import { motion, type Variants } from 'motion/react'; + +import { + getVariants, + useAnimateIconContext, + IconWrapper, + type IconProps, +} from '@/components/animate-ui/icons/icon'; + +type XProps = IconProps; + +const animations = { + default: { + line1: { + initial: { + rotate: 0, + transition: { ease: 'easeInOut', duration: 0.4 }, + }, + animate: { + rotate: 90, + transition: { ease: 'easeInOut', duration: 0.4 }, + }, + }, + line2: { + initial: { + rotate: 0, + transition: { ease: 'easeInOut', duration: 0.4, delay: 0.1 }, + }, + animate: { + rotate: 90, + transition: { ease: 'easeInOut', duration: 0.4, delay: 0.1 }, + }, + }, + } satisfies Record, + plus: { + line1: { + initial: { + rotate: 0, + x1: 6, + y1: 18, + x2: 18, + y2: 6, + transition: { ease: 'easeInOut', duration: 0.3, delay: 0.1 }, + }, + animate: { + rotate: 45, + x1: 7.1, + y1: 16.9, + x2: 16.9, + y2: 7.1, + transition: { ease: 'easeInOut', duration: 0.3, delay: 0.1 }, + }, + }, + line2: { + initial: { + rotate: 0, + x1: 6, + y1: 6, + x2: 18, + y2: 18, + transition: { ease: 'easeInOut', duration: 0.3 }, + }, + animate: { + rotate: 45, + x1: 7.1, + y1: 7.1, + x2: 16.9, + y2: 16.9, + transition: { ease: 'easeInOut', duration: 0.3 }, + }, + }, + } satisfies Record, +} as const; + +function IconComponent({ size, ...props }: XProps) { + const { controls } = useAnimateIconContext(); + const variants = getVariants(animations); + + return ( + + + + + ); +} + +function X(props: XProps) { + return ( + + ); +} + +export { animations, X, X as XIcon, type XProps, type XProps as XIconProps }; diff --git a/src/components/home/CategoryShowcase/CategoryShowcase.tsx b/src/components/home/CategoryShowcase/CategoryShowcase.tsx index ef1848f..dd94427 100644 --- a/src/components/home/CategoryShowcase/CategoryShowcase.tsx +++ b/src/components/home/CategoryShowcase/CategoryShowcase.tsx @@ -21,7 +21,6 @@ const containerVariants = { const CategoryShowcase: React.FC = ({ categories }) => { // TODO remove comments - // console.log(categories); // const { data: categories, isLoading, isError } = useGetCategoriesQuery(); // if (isLoading) { diff --git a/src/components/pages/Favourites.tsx b/src/components/pages/Favourites.tsx index db5e8e4..6136e46 100644 --- a/src/components/pages/Favourites.tsx +++ b/src/components/pages/Favourites.tsx @@ -42,7 +42,7 @@ const Favourites: React.FC = () => { return ( <> -

    +
    = ({ product, + productForCompare, capacityAvailable, namespaceId, colorsAvailable, @@ -52,8 +53,6 @@ const ProductDetailsOrderOptions: React.FC = ({ priceRegular, variants, }) => { - const router = useRouter(); - return (
    @@ -127,6 +126,9 @@ const ProductDetailsOrderOptions: React.FC = ({
    +
    + +
    diff --git a/src/lib/features/compare/compareSlice.tsx b/src/lib/features/compare/compareSlice.tsx new file mode 100644 index 0000000..5839626 --- /dev/null +++ b/src/lib/features/compare/compareSlice.tsx @@ -0,0 +1,76 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { ProductType } from '@/types/CategoryType'; + +interface CompareState { + items: ProductType[]; +} + +const initialState: CompareState = { + items: [], +}; + +const compareSlice = createSlice({ + name: 'compare', + initialState, + reducers: { + addToCompare: (state, action: PayloadAction) => { + const product = action.payload; + const exists = state.items.some( + (i) => + i.id === product.id && + i.color === product.color && + i.capacity === product.capacity, + ); + + if (!exists) state.items.push(product); + }, + + removeFromCompareByKey: ( + state, + action: PayloadAction<{ + id: number | string; + color: string; + capacity: string; + }>, + ) => { + const { id, color, capacity } = action.payload; + state.items = state.items.filter( + (i) => !(i.id === id && i.color === color && i.capacity === capacity), + ); + }, + + clearCompare: (state) => { + state.items = []; + }, + + clearCompareByCategory: (state, action: PayloadAction) => { + state.items = state.items.filter((p) => p.category !== action.payload); + }, + + toggleCompare: (state, action: PayloadAction) => { + const product = action.payload; + const existsIndex = state.items.findIndex( + (i) => + i.id === product.id && + i.color === product.color && + i.capacity === product.capacity, + ); + + if (existsIndex >= 0) { + state.items.splice(existsIndex, 1); + } else { + state.items.push(product); + } + }, + }, +}); + +export const { + addToCompare, + removeFromCompareByKey, + clearCompare, + clearCompareByCategory, + toggleCompare, +} = compareSlice.actions; + +export default compareSlice.reducer; diff --git a/src/lib/store.ts b/src/lib/store.ts index af506a8..574ca09 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -4,6 +4,7 @@ import { combineReducers, configureStore } from '@reduxjs/toolkit'; import { apiSlice } from '@/lib/features/api/apiSlice'; import favouritesReducer from '@/lib/features/favourites/favouritesSlice'; import cartReducer from '@/lib/features/cart/cartSlice'; +import compareReducer from '@/lib/features/compare/compareSlice'; import { persistReducer, persistStore } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; @@ -12,12 +13,13 @@ import { CategoryName } from '@/types/CategoryName'; const persistedReducers = combineReducers({ favourites: favouritesReducer, cart: cartReducer, + compare: compareReducer, }); const persistConfig = { key: 'root', storage, - whitelist: [CategoryName.Favourites, CategoryName.Cart], + whitelist: [CategoryName.Favourites, CategoryName.Cart, CategoryName.Compare], }; const persistedReducer = persistReducer(persistConfig, persistedReducers); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 8164923..2819a83 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,4 +1,3 @@ -import { CategoryName } from '@/types/CategoryName'; import { clsx, type ClassValue } from 'clsx'; import { twMerge } from 'tailwind-merge'; diff --git a/src/types/CategoryName.ts b/src/types/CategoryName.ts index 7f9b919..176250b 100644 --- a/src/types/CategoryName.ts +++ b/src/types/CategoryName.ts @@ -5,4 +5,5 @@ export enum CategoryName { Accessories = 'accessories', Favourites = 'favourites', Cart = 'cart', + Compare = 'compare', } diff --git a/src/types/CategoryType.ts b/src/types/CategoryType.ts index 54beeaf..46197f2 100644 --- a/src/types/CategoryType.ts +++ b/src/types/CategoryType.ts @@ -13,6 +13,7 @@ export type DescriptionItem = { export type ProductType = { id: string; + itemId: string; category: CategoryName; namespaceId: string; name: string; diff --git a/src/types/NavBarRightComponents.tsx b/src/types/NavBarRightComponents.tsx index b3590e1..c74f0e4 100644 --- a/src/types/NavBarRightComponents.tsx +++ b/src/types/NavBarRightComponents.tsx @@ -2,9 +2,11 @@ import FavouritesLink from '@/components/UI/NavBar/FavouritesLink'; import ShoppingCartLink from '@/components/UI/NavBar/ShoppingCartLink'; import ThemeSwitcher from '@/components/UI/ThemeSwitcher'; import React from 'react'; -import { CategoryName } from './CategoryName'; +import { CategoryName } from '@/types/CategoryName'; +import CompareLink from '@/components/UI/NavBar/CompareLink'; export const NavBarRightComponents = [ + { id: CategoryName.Compare, element: }, { id: 'theme', element: }, { id: CategoryName.Favourites, element: }, { id: CategoryName.Cart, element: }, diff --git a/src/types/product.ts b/src/types/product.ts index 53bf0ec..8878379 100644 --- a/src/types/product.ts +++ b/src/types/product.ts @@ -2,7 +2,7 @@ import { CategoryName } from './CategoryName'; export const VALID_CATEGORIES = Object.values(CategoryName).slice(1, 4); -type Category = (typeof VALID_CATEGORIES)[number]; +export type Category = (typeof VALID_CATEGORIES)[number]; export interface Product { id: number;