Browse Source

new filter

master
azri 2 weeks ago
parent
commit
5a1a0509b8

+ 70
- 0
package-lock.json View File

19
         "@testing-library/jest-dom": "^5.17.0",
19
         "@testing-library/jest-dom": "^5.17.0",
20
         "@testing-library/react": "^13.4.0",
20
         "@testing-library/react": "^13.4.0",
21
         "@testing-library/user-event": "^13.5.0",
21
         "@testing-library/user-event": "^13.5.0",
22
+        "animate.css": "^4.1.1",
22
         "axios": "^1.7.9",
23
         "axios": "^1.7.9",
23
         "dotenv": "^16.4.7",
24
         "dotenv": "^16.4.7",
25
+        "framer-motion": "^11.16.0",
24
         "react": "^18.3.1",
26
         "react": "^18.3.1",
25
         "react-dom": "^18.3.1",
27
         "react-dom": "^18.3.1",
26
         "react-redux": "^9.2.0",
28
         "react-redux": "^9.2.0",
27
         "react-router-dom": "^7.0.2",
29
         "react-router-dom": "^7.0.2",
28
         "react-scripts": "5.0.1",
30
         "react-scripts": "5.0.1",
29
         "shopify-api-node": "^3.14.0",
31
         "shopify-api-node": "^3.14.0",
32
+        "swiper": "^11.2.0",
30
         "web-vitals": "^2.1.4"
33
         "web-vitals": "^2.1.4"
31
       }
34
       }
32
     },
35
     },
5399
         "ajv": "^6.9.1"
5402
         "ajv": "^6.9.1"
5400
       }
5403
       }
5401
     },
5404
     },
5405
+    "node_modules/animate.css": {
5406
+      "version": "4.1.1",
5407
+      "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz",
5408
+      "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==",
5409
+      "license": "MIT"
5410
+    },
5402
     "node_modules/ansi-escapes": {
5411
     "node_modules/ansi-escapes": {
5403
       "version": "4.3.2",
5412
       "version": "4.3.2",
5404
       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
5413
       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
9419
         "url": "https://github.com/sponsors/rawify"
9428
         "url": "https://github.com/sponsors/rawify"
9420
       }
9429
       }
9421
     },
9430
     },
9431
+    "node_modules/framer-motion": {
9432
+      "version": "11.16.0",
9433
+      "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.16.0.tgz",
9434
+      "integrity": "sha512-oL2AWqLQuw0+CNEUa0sz3mWC/n3i147CckvpQn8bLRs30b+HxTxlRi0YR2FpHHhAbWV7DKjNdHU42KHLfBWh/g==",
9435
+      "license": "MIT",
9436
+      "dependencies": {
9437
+        "motion-dom": "^11.16.0",
9438
+        "motion-utils": "^11.16.0",
9439
+        "tslib": "^2.4.0"
9440
+      },
9441
+      "peerDependencies": {
9442
+        "@emotion/is-prop-valid": "*",
9443
+        "react": "^18.0.0 || ^19.0.0",
9444
+        "react-dom": "^18.0.0 || ^19.0.0"
9445
+      },
9446
+      "peerDependenciesMeta": {
9447
+        "@emotion/is-prop-valid": {
9448
+          "optional": true
9449
+        },
9450
+        "react": {
9451
+          "optional": true
9452
+        },
9453
+        "react-dom": {
9454
+          "optional": true
9455
+        }
9456
+      }
9457
+    },
9422
     "node_modules/fresh": {
9458
     "node_modules/fresh": {
9423
       "version": "0.5.2",
9459
       "version": "0.5.2",
9424
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
9460
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
12666
         "mkdirp": "bin/cmd.js"
12702
         "mkdirp": "bin/cmd.js"
12667
       }
12703
       }
12668
     },
12704
     },
12705
+    "node_modules/motion-dom": {
12706
+      "version": "11.16.0",
12707
+      "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.16.0.tgz",
12708
+      "integrity": "sha512-4bmEwajSdrljzDAYpu6ceEdtI4J5PH25fmN8YSx7Qxk6OMrC10CXM0D5y+VO/pFZjhmCvm2bGf7Rus482kwhzA==",
12709
+      "license": "MIT",
12710
+      "dependencies": {
12711
+        "motion-utils": "^11.16.0"
12712
+      }
12713
+    },
12714
+    "node_modules/motion-utils": {
12715
+      "version": "11.16.0",
12716
+      "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.16.0.tgz",
12717
+      "integrity": "sha512-ngdWPjg31rD4WGXFi0eZ00DQQqKKu04QExyv/ymlC+3k+WIgYVFbt6gS5JsFPbJODTF/r8XiE/X+SsoT9c0ocw==",
12718
+      "license": "MIT"
12719
+    },
12669
     "node_modules/ms": {
12720
     "node_modules/ms": {
12670
       "version": "2.1.3",
12721
       "version": "2.1.3",
12671
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
12722
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
17202
         "node": ">=4"
17253
         "node": ">=4"
17203
       }
17254
       }
17204
     },
17255
     },
17256
+    "node_modules/swiper": {
17257
+      "version": "11.2.0",
17258
+      "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.0.tgz",
17259
+      "integrity": "sha512-rjjAKgDEs+grR2eQshVDCcE4KNPC7CI294nfcbV9gE8WCsLdvOYXDeZKUYevqAZZp8j5hE7kpT3dAGVKFBWlxQ==",
17260
+      "funding": [
17261
+        {
17262
+          "type": "patreon",
17263
+          "url": "https://www.patreon.com/swiperjs"
17264
+        },
17265
+        {
17266
+          "type": "open_collective",
17267
+          "url": "http://opencollective.com/swiper"
17268
+        }
17269
+      ],
17270
+      "license": "MIT",
17271
+      "engines": {
17272
+        "node": ">= 4.7.0"
17273
+      }
17274
+    },
17205
     "node_modules/symbol-tree": {
17275
     "node_modules/symbol-tree": {
17206
       "version": "3.2.4",
17276
       "version": "3.2.4",
17207
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
17277
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",

+ 3
- 0
package.json View File

14
     "@testing-library/jest-dom": "^5.17.0",
14
     "@testing-library/jest-dom": "^5.17.0",
15
     "@testing-library/react": "^13.4.0",
15
     "@testing-library/react": "^13.4.0",
16
     "@testing-library/user-event": "^13.5.0",
16
     "@testing-library/user-event": "^13.5.0",
17
+    "animate.css": "^4.1.1",
17
     "axios": "^1.7.9",
18
     "axios": "^1.7.9",
18
     "dotenv": "^16.4.7",
19
     "dotenv": "^16.4.7",
20
+    "framer-motion": "^11.16.0",
19
     "react": "^18.3.1",
21
     "react": "^18.3.1",
20
     "react-dom": "^18.3.1",
22
     "react-dom": "^18.3.1",
21
     "react-redux": "^9.2.0",
23
     "react-redux": "^9.2.0",
22
     "react-router-dom": "^7.0.2",
24
     "react-router-dom": "^7.0.2",
23
     "react-scripts": "5.0.1",
25
     "react-scripts": "5.0.1",
24
     "shopify-api-node": "^3.14.0",
26
     "shopify-api-node": "^3.14.0",
27
+    "swiper": "^11.2.0",
25
     "web-vitals": "^2.1.4"
28
     "web-vitals": "^2.1.4"
26
   },
29
   },
27
   "scripts": {
30
   "scripts": {

+ 10
- 0
src/components/Carousel/Carousel.jsx View File

37
         overflow: "hidden",
37
         overflow: "hidden",
38
       }}
38
       }}
39
     >
39
     >
40
+      {/* Filter */}
41
+      <Box sx={{
42
+        position:"absolute",
43
+        top:0,
44
+        right:0,
45
+        backgroundColor:"rgba(0, 0, 0, 0.2)",
46
+        width:"100%",
47
+        height:"100%"
48
+      }}></Box>
49
+      
40
       {items.map((item, index) => (
50
       {items.map((item, index) => (
41
         <Box
51
         <Box
42
           key={index}
52
           key={index}

+ 216
- 42
src/components/Navbar/Navbar.jsx View File

1
-import React, { useState, useEffect } from "react";
1
+import { useState, useEffect, useRef } from "react";
2
 import AppBar from "@mui/material/AppBar";
2
 import AppBar from "@mui/material/AppBar";
3
 import Toolbar from "@mui/material/Toolbar";
3
 import Toolbar from "@mui/material/Toolbar";
4
 import Button from "@mui/material/Button";
4
 import Button from "@mui/material/Button";
21
 import { useSelector } from 'react-redux';
21
 import { useSelector } from 'react-redux';
22
 import ProductService from "../../services/ProductService";
22
 import ProductService from "../../services/ProductService";
23
 
23
 
24
+import { Swiper, SwiperSlide } from 'swiper/react';
25
+import { Scrollbar, A11y, Navigation } from 'swiper/modules';
26
+
27
+import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
28
+import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
29
+
24
 function calculateTotalQuantity(cartItems) {
30
 function calculateTotalQuantity(cartItems) {
25
   return cartItems.reduce((total, item) => total + item.quantity, 0);
31
   return cartItems.reduce((total, item) => total + item.quantity, 0);
26
 }
32
 }
56
   },
62
   },
57
 }));
63
 }));
58
 
64
 
65
+function mapProductTypesWithCollections(products) {
66
+  // Create a Map to group collections by productType
67
+  const productTypeMap = new Map();
68
+
69
+  products.forEach(product => {
70
+    const { productType, collections } = product;
71
+
72
+    // If productType already exists, merge its collections
73
+    if (productTypeMap.has(productType)) {
74
+      const existingCollections = productTypeMap.get(productType);
75
+      const newCollections = collections.map(collection => collection);
76
+      productTypeMap.set(productType, [...new Set([...existingCollections, ...newCollections])]);
77
+    } else {
78
+      // Add a new productType with its collections
79
+      productTypeMap.set(productType, collections.map(collection => collection));
80
+    }
81
+  });
82
+
83
+  // Convert the Map into the desired array structure
84
+  return Array.from(productTypeMap, ([productType, collection]) => ({ productType, collection }));
85
+}
86
+
87
+function getUniqueCollections(data) {
88
+  return data.map(item => {
89
+    // Use a Map to store unique collection titles with their corresponding image
90
+    const uniqueCollectionsMap = new Map();
91
+
92
+    item.collection.forEach(({ title, image }) => {
93
+      if (!uniqueCollectionsMap.has(title)) {
94
+        uniqueCollectionsMap.set(title, { title, image });
95
+      }
96
+    });
97
+
98
+    // Convert the Map back to an array
99
+    return {
100
+      productType: item.productType,
101
+      collection: Array.from(uniqueCollectionsMap.values())
102
+    };
103
+  });
104
+}
105
+
59
 const Navbar = () => {
106
 const Navbar = () => {
60
 
107
 
108
+  const swiperRef = useRef(null); // Create a ref for the Swiper instance
61
   const [showHeader, setShowHeader] = useState(true);
109
   const [showHeader, setShowHeader] = useState(true);
62
   const [lastScrollPos, setLastScrollPos] = useState(0);
110
   const [lastScrollPos, setLastScrollPos] = useState(0);
63
   const [language, setLanguage] = useState('English');
111
   const [language, setLanguage] = useState('English');
64
 
112
 
65
   const cart = useSelector((state) => state.cart.cart)
113
   const cart = useSelector((state) => state.cart.cart)
114
+  const products = useSelector((state) => state.products.products.data)
66
   const [cartAmount, setCartAmount] = useState(0);
115
   const [cartAmount, setCartAmount] = useState(0);
67
 
116
 
68
-  const [open, setOpen] = React.useState(false);
69
-  const [navItem, setNavItem] = useState([])
70
-
71
-  useEffect(()=>{
72
-    fetchProductTypes()
73
-  },[])
74
-
75
-  const fetchProductTypes = async () => {
76
-    try {
77
-      const data = await ProductService.getProductTypes();
78
-  
79
-      if (Array.isArray(data)) {
117
+  const [open, setOpen] = useState(false);
118
+  const [navItem, setNavItem] = useState([]);
80
 
119
 
81
-        let navItemData = data.map(({node})=>({title:`${node.toUpperCase()}`, link:`/collection/${node}` }))
82
-        navItemData = navItemData.filter(({title}) => (title !== "") )
83
-        setNavItem(navItemData);
84
-
85
-      } else {
86
-        setNavItem([]); 
87
-      }
88
-    } catch (error) {
89
-      console.error("Error fetching product types:", error);
90
-      setNavItem([]); // Handle error by setting fallback empty array
91
-    }
92
-  };
120
+  const [displayCollection, setDisplayCollection] = useState({
121
+    productType: null,
122
+    list:[]
123
+  })
93
 
124
 
94
   useEffect(() => {
125
   useEffect(() => {
95
 
126
 
99
 
130
 
100
   }, [cart])
131
   }, [cart])
101
 
132
 
133
+  useEffect(() => {
134
+
135
+    //Navbar item are based on products type, thus get all the item and then we should be good
136
+    if (products.length > 0) {
137
+
138
+      let navItemData = mapProductTypesWithCollections(products)
139
+      navItemData = getUniqueCollections(navItemData)
140
+      setNavItem(navItemData)
141
+
142
+    }
143
+
144
+  }, [products])
145
+
102
   const handleChange = (event) => {
146
   const handleChange = (event) => {
103
     setLanguage(event.target.value);
147
     setLanguage(event.target.value);
104
   };
148
   };
120
     };
164
     };
121
   }, [lastScrollPos]);
165
   }, [lastScrollPos]);
122
 
166
 
167
+  const renderCollectionDisplay = (colletion) => {
168
+
169
+    let { title, image } = colletion
170
+
171
+    return (
172
+      <SwiperSlide>
173
+        <Box
174
+          className="animate__animated animate__fadeIn animate__fast"
175
+          sx={{
176
+            backgroundImage: `url(${image.url})`,
177
+            backgroundSize: "cover",
178
+            backgroundRepeat: "no-repeat",
179
+            backgroundPosition: "center center",
180
+            height: "100%",
181
+            paddingTop: "85%",
182
+            position: "relative",
183
+            cursor: "pointer"
184
+          }}
185
+          onClick={()=>{
186
+            sessionStorage.setItem('amber-select-collection',title)
187
+            sessionStorage.setItem('amber-select-product-type',displayCollection.productType)
188
+            window.location.href = `/products`;
189
+          }}
190
+        >
191
+          <Typography
192
+            variant="body1"
193
+            sx={{
194
+              color: "white",
195
+              fontWeight: "bolder",
196
+              position: "absolute",
197
+              top: "50%",
198
+              left: "50%",
199
+              transform: "translate(-50%, -50%)"
200
+            }}
201
+          >
202
+            {title.toUpperCase()}
203
+          </Typography>
204
+        </Box>
205
+      </SwiperSlide>
206
+    )
207
+
208
+  }
209
+
210
+  const swipeToLeft = () => {
211
+    if (swiperRef.current) {
212
+      swiperRef.current.slidePrev(); // Move to the previous slide
213
+    }
214
+  };
215
+
216
+  const swipeToRight = () => {
217
+    if (swiperRef.current) {
218
+      swiperRef.current.slideNext(); // Move to the previous slide
219
+    }
220
+  };
221
+
222
+  const displayCollectionList = (collection, productType) => {
223
+    setDisplayCollection({
224
+      productType,
225
+      list:collection
226
+    })
227
+  }
228
+
229
+  const hideCollectionList = () => {
230
+    setDisplayCollection([])
231
+  }
232
+
123
   return (
233
   return (
124
     <>
234
     <>
125
       <AppBar position="fixed"
235
       <AppBar position="fixed"
126
         sx={{
236
         sx={{
127
-          backgroundColor: {
128
-            xs: "background.black",
129
-            md: "rgba(0,0,0,0.9)",
130
-            lg: "background.black"
131
-          },
132
-          boxShadow: {
133
-            xs: "none",
134
-            md: "none",
135
-            lg: "0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12)"
237
+          backgroundColor: "rgba(255, 255, 255, 0.3)", // Transparent white background
238
+          backdropFilter: "blur(10px)", // Apply blur effect
239
+          boxShadow: 0,
240
+          borderBottom: '1px solid white',
241
+          transition: "all 0.2s ease-in-out",
242
+          ":hover": {
243
+            backgroundColor: "rgba(255,255,255,1)",
244
+            backdropFilter: "none"
136
           }
245
           }
137
         }}
246
         }}
247
+        onMouseLeave={() => {
248
+          hideCollectionList()
249
+        }}
138
       >
250
       >
139
 
251
 
140
         {/* Conditionally render the Header */}
252
         {/* Conditionally render the Header */}
151
 
263
 
152
           {/* Left Section: Logo */}
264
           {/* Left Section: Logo */}
153
           <Box sx={{
265
           <Box sx={{
154
-            mr:5,
266
+            mr: 5,
155
             display: {
267
             display: {
156
               xs: "none",
268
               xs: "none",
157
               md: "none",
269
               md: "none",
200
                 md: "none",
312
                 md: "none",
201
                 lg: "flex"
313
                 lg: "flex"
202
               },
314
               },
203
-              gap: 2,
315
+              gap: 2
204
             }}
316
             }}
205
           >
317
           >
206
 
318
 
207
-            {navItem.map(({ title, link }) => (<Button
319
+            {navItem.map(({ productType, collection }) => (<Button
320
+              sx={{
321
+                position: "relative",
322
+                ':hover': {
323
+                  backgroundColor: "rgba(0,0,0,0)",
324
+                  color: "#95AAC5"
325
+                }
326
+              }}
208
               color="inherit"
327
               color="inherit"
209
               onClick={() => {
328
               onClick={() => {
210
-
211
-                sessionStorage.setItem('amber-select-product-type',title.toLocaleLowerCase())
212
-                window.location.href = link;
213
-              
329
+                sessionStorage.setItem('amber-select-product-type', productType)
330
+                window.location.href = `/products`;
331
+              }}
332
+              onMouseEnter={() => {
333
+                displayCollectionList(collection, productType)
214
               }}
334
               }}
215
             >
335
             >
216
-              {title}
336
+              <Typography variant="body2"
337
+                sx={{
338
+                  fontWeight: "400",
339
+                  color: "black",
340
+                  transition: "all 0.3s ease-in-out",
341
+                  ":hover": {
342
+                    color: "#95AAC5"
343
+                  }
344
+                }}>{productType}</Typography>
217
             </Button>))}
345
             </Button>))}
218
 
346
 
219
           </Box>
347
           </Box>
262
           </Box>
390
           </Box>
263
 
391
 
264
         </Toolbar>
392
         </Toolbar>
393
+
394
+        {/* On Hover Collection */}
395
+        <Box className="animate__animated animate__fadeIn" sx={{ backgroundColor: "white", position: "relative", py: 5, display: (displayCollection?.list?.length > 0) ? "block" : "none" }}>
396
+          <ChevronLeftIcon
397
+            style={{
398
+              cursor: "pointer",
399
+              color: "black",
400
+              position: "absolute",
401
+              top: "50%",
402
+              fontSize: 50,
403
+              left: 40,
404
+              transform: "translateY(-50%)",
405
+            }}
406
+            onClick={swipeToLeft}
407
+          />
408
+
409
+          <Box sx={{ px: 20 }}>
410
+            <Swiper
411
+              modules={[Navigation, Scrollbar, A11y]}
412
+              spaceBetween={20}
413
+              slidesPerView={4}
414
+              pagination={{ clickable: true }}
415
+              scrollbar={{ draggable: true }}
416
+              onSwiper={(swiper) => (swiperRef.current = swiper)}
417
+            >
418
+              {displayCollection?.list?.map((colletion) => {
419
+                return renderCollectionDisplay(colletion)
420
+              })}
421
+
422
+            </Swiper>
423
+          </Box>
424
+
425
+          <KeyboardArrowRightIcon
426
+            style={{
427
+              cursor: "pointer",
428
+              color: "black",
429
+              fontSize: 50,
430
+              position: "absolute",
431
+              top: "50%",
432
+              right: 40,
433
+              transform: "translateY(-50%)",
434
+            }}
435
+            onClick={swipeToRight}
436
+          />
437
+        </Box>
438
+
265
       </AppBar>
439
       </AppBar>
266
 
440
 
267
       <MobileNav open={open} menu={navItem} onClose={() => { setOpen(false) }} />
441
       <MobileNav open={open} menu={navItem} onClose={() => { setOpen(false) }} />

+ 35
- 36
src/components/ProductDetails/ProductDetails.jsx View File

38
   const [alert, setAlert] = useState({ open: false, severity: '', message: '' });
38
   const [alert, setAlert] = useState({ open: false, severity: '', message: '' });
39
 
39
 
40
   useEffect(() => {
40
   useEffect(() => {
41
-    if (product) {
41
+    if (!isEmptyObject(product)) {
42
 
42
 
43
       console.log("Product: ", product)
43
       console.log("Product: ", product)
44
+      debugger
44
 
45
 
45
       let productVariants = product?.variants?.nodes
46
       let productVariants = product?.variants?.nodes
46
 
47
 
79
         // find variant price if it all match initial variant selection
80
         // find variant price if it all match initial variant selection
80
         for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
81
         for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
81
           let { amount, currencyCode } = price;
82
           let { amount, currencyCode } = price;
82
-        
83
+
83
           // Convert array to object
84
           // Convert array to object
84
           const optionsObject = selectedOptions.reduce(
85
           const optionsObject = selectedOptions.reduce(
85
             (a, { name, value }) => ({ ...a, [name]: value }),
86
             (a, { name, value }) => ({ ...a, [name]: value }),
86
             {}
87
             {}
87
           );
88
           );
88
-        
89
+
89
           if (hasMatchingProperties(newVariantSelection, optionsObject)) {
90
           if (hasMatchingProperties(newVariantSelection, optionsObject)) {
90
             newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
91
             newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
91
             break; // Exit the loop when condition is met
92
             break; // Exit the loop when condition is met
116
       // find variant price if it all match initial variant selection
117
       // find variant price if it all match initial variant selection
117
       for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
118
       for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
118
         let { amount, currencyCode } = price;
119
         let { amount, currencyCode } = price;
119
-      
120
+
120
         // Convert array to object
121
         // Convert array to object
121
         const optionsObject = selectedOptions.reduce(
122
         const optionsObject = selectedOptions.reduce(
122
           (a, { name, value }) => ({ ...a, [name]: value }),
123
           (a, { name, value }) => ({ ...a, [name]: value }),
123
           {}
124
           {}
124
         );
125
         );
125
-      
126
+
126
         if (hasMatchingProperties(newVariantSelection, optionsObject)) {
127
         if (hasMatchingProperties(newVariantSelection, optionsObject)) {
127
           newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
128
           newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
128
-          if(quantityAvailable == 0) setQuantity(0)
129
+          if (quantityAvailable == 0) setQuantity(0)
129
           else setQuantity(1)
130
           else setQuantity(1)
130
           break; // Exit the loop when condition is met
131
           break; // Exit the loop when condition is met
131
         }
132
         }
149
     if (isEmptyObject(cart) || isEmptyObject(cartHistory)) {
150
     if (isEmptyObject(cart) || isEmptyObject(cartHistory)) {
150
 
151
 
151
       dispatch(createCart())
152
       dispatch(createCart())
152
-      .then(() => {
153
-        showAlert('success', 'Cart created successfully!');
154
-      })
155
-      .catch(() => {
156
-        showAlert('error', 'Failed to create cart. Please try again.');
157
-      })
158
-      .finally(() => setShowLoader(false));
153
+        .then(() => {
154
+          showAlert('success', 'Cart created successfully!');
155
+        })
156
+        .catch(() => {
157
+          showAlert('error', 'Failed to create cart. Please try again.');
158
+        })
159
+        .finally(() => setShowLoader(false));
159
 
160
 
160
     } else {
161
     } else {
161
 
162
 
162
       console.log("ADD ITEM:", variantSelection)
163
       console.log("ADD ITEM:", variantSelection)
163
-      
164
+
164
       dispatch(addItemToCart({
165
       dispatch(addItemToCart({
165
         cartId: cartHistory.id,
166
         cartId: cartHistory.id,
166
-        lines:[
167
+        lines: [
167
           {
168
           {
168
-            merchandiseId:variantSelection.id,
169
+            merchandiseId: variantSelection.id,
169
             quantity
170
             quantity
170
           }
171
           }
171
         ]
172
         ]
172
       }))
173
       }))
173
-      .then(() => {
174
-        showAlert('success', 'Item added to cart successfully!');
175
-      })
176
-      .catch(() => {
177
-        showAlert('error', 'Failed to add item to cart. Please try again.');
178
-      })
179
-      .finally(() => setShowLoader(false));
174
+        .then(() => {
175
+          showAlert('success', 'Item added to cart successfully!');
176
+        })
177
+        .catch(() => {
178
+          showAlert('error', 'Failed to add item to cart. Please try again.');
179
+        })
180
+        .finally(() => setShowLoader(false));
180
     }
181
     }
181
 
182
 
182
   }
183
   }
206
         {/* Section 1: Product Info */}
207
         {/* Section 1: Product Info */}
207
         <Box>
208
         <Box>
208
           <Typography variant="h5" sx={{ fontWeight: "bold", mb: 2 }}>
209
           <Typography variant="h5" sx={{ fontWeight: "bold", mb: 2 }}>
209
-            {product.title}
210
+            {product?.title}
210
           </Typography>
211
           </Typography>
211
           <Typography variant="body2" color="text.secondary">
212
           <Typography variant="body2" color="text.secondary">
212
             {product?.collections?.nodes[0]?.title}
213
             {product?.collections?.nodes[0]?.title}
213
           </Typography>
214
           </Typography>
215
+
216
+
214
           <Typography
217
           <Typography
215
             variant="body1"
218
             variant="body1"
216
             sx={{ fontWeight: "bold" }}
219
             sx={{ fontWeight: "bold" }}
217
           >
220
           >
218
-            {`${variantSelection.currencyCode} ${parseFloat(variantSelection.amount).toFixed(2)}`}
219
-          </Typography>
220
-          <Typography
221
-            variant="body1"
222
-          >
223
-            {(variantSelection?.quantityAvailable == 0) ? <span style={{color:"red", fontWeight:"bolder"}}>{`OUT OF STOCK`}</span> : `IN STOCK: ${variantSelection?.quantityAvailable}`}
221
+            {(variantSelection?.quantityAvailable == 0) ? <span style={{ color: "red", fontWeight: "bolder" }}>{`OUT OF STOCK`}</span> : `${variantSelection.currencyCode} ${parseFloat(variantSelection.amount).toFixed(2)}`}
224
           </Typography>
222
           </Typography>
223
+
225
         </Box>
224
         </Box>
226
 
225
 
227
         {/* Section 2: Variants */}
226
         {/* Section 2: Variants */}
230
           {variants.map(({ name, options }, index) => {
229
           {variants.map(({ name, options }, index) => {
231
 
230
 
232
             return (
231
             return (
233
-              <>
234
-                <Typography variant="body1" sx={{ fontWeight: "bold", color: "#000", mb:1 }}>
232
+              <Box sx={{ display: (name == "Title") ? "none" : "block" }}>
233
+                <Typography variant="body1" sx={{ fontWeight: "bold", color: "#000", mb: 1 }}>
235
                   {name}
234
                   {name}
236
                 </Typography>
235
                 </Typography>
237
 
236
 
248
                     </Button>
247
                     </Button>
249
                   ))}
248
                   ))}
250
                 </Box>
249
                 </Box>
251
-              </>
250
+              </Box>
252
             )
251
             )
253
 
252
 
254
           })}
253
           })}
298
             Description
297
             Description
299
           </Typography>
298
           </Typography>
300
           <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
299
           <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
301
-            <div dangerouslySetInnerHTML={{ __html: product.descriptionHtml }}></div>
300
+            <div dangerouslySetInnerHTML={{ __html: product?.descriptionHtml }}></div>
302
           </Typography>
301
           </Typography>
303
         </Box>
302
         </Box>
304
 
303
 
318
             },
317
             },
319
           }}
318
           }}
320
         >
319
         >
321
-          ADD TO CART {showLoader && <CircularProgress sx={{ml:1}} color="white" size={20} />} 
320
+          ADD TO CART {showLoader && <CircularProgress sx={{ ml: 1 }} color="white" size={20} />}
322
         </Button>
321
         </Button>
323
 
322
 
324
-        { cart?.lines?.nodes?.length > 0 && <Button
323
+        {cart?.lines?.nodes?.length > 0 && <Button
325
           onClick={() => { window.location.href = '/cart' }}
324
           onClick={() => { window.location.href = '/cart' }}
326
           variant="contained"
325
           variant="contained"
327
           color="common.black"
326
           color="common.black"

+ 85
- 87
src/components/ProductList/ProductList.jsx View File

4
 import { styled } from "@mui/material";
4
 import { styled } from "@mui/material";
5
 //REDUX
5
 //REDUX
6
 import { useSelector, useDispatch } from 'react-redux';
6
 import { useSelector, useDispatch } from 'react-redux';
7
-import { fetchProductsByTags, fetchProductsByCollection } from '../../redux/slices/productSlice';
7
+import { fetchProducts } from '../../redux/slices/productSlice';
8
 
8
 
9
 //UTIL FUNCTION
9
 //UTIL FUNCTION
10
 function getAllTags(data) {
10
 function getAllTags(data) {
16
 
16
 
17
 function getAllCollection(data) {
17
 function getAllCollection(data) {
18
   const products = data || [];
18
   const products = data || [];
19
-  const allCollection = products.flatMap(product => product.collections.nodes);
19
+  const allCollection = products.flatMap(product => product.collections);
20
   const uniqueCollection = Array.from(
20
   const uniqueCollection = Array.from(
21
     new Map(allCollection.map(item => [item.title, item])).values()
21
     new Map(allCollection.map(item => [item.title, item])).values()
22
   );
22
   );
52
 
52
 
53
   const products = useSelector((state) => state.products.products.data) // only used as referenced
53
   const products = useSelector((state) => state.products.products.data) // only used as referenced
54
   const [filteredProducts, setFilteredProducts] = useState([]) // this one is the actual data to be rendered
54
   const [filteredProducts, setFilteredProducts] = useState([]) // this one is the actual data to be rendered
55
-  const [categoryFilterOption, setCategoryFilterOption] = useState([])
55
+  const [tagFilterOption, setTagFilterOption] = useState([])
56
   const [collectionFilterOption, setCollectionFilterOption] = useState([])
56
   const [collectionFilterOption, setCollectionFilterOption] = useState([])
57
   const dispatch = useDispatch();
57
   const dispatch = useDispatch();
58
 
58
 
59
   //filter
59
   //filter
60
-  const [category, setCategory] = useState('all');
60
+  const [tags, setTags] = useState('all');
61
   const [collection, setCollection] = useState('all');
61
   const [collection, setCollection] = useState('all');
62
   const [sort, setSort] = useState('title')
62
   const [sort, setSort] = useState('title')
63
 
63
 
64
   useEffect(() => {
64
   useEffect(() => {
65
 
65
 
66
-    // if user come from select collection
67
-    if (sessionStorage.getItem('amber-select-collection')) {
66
+    dispatch(fetchProducts())
68
 
67
 
69
-      let { id } = JSON.parse(sessionStorage.getItem('amber-select-collection'))
70
-      dispatch(fetchProductsByCollection({ collectionId: id }))
68
+  }, [])
71
 
69
 
72
-    } else if (sessionStorage.getItem('amber-select-category')) {
73
 
70
 
74
-      let tag = sessionStorage.getItem('amber-select-category')
75
-      dispatch(fetchProductsByTags({ tag }))
71
+  useEffect(() => {
76
 
72
 
77
-    }
73
+    console.log("Products: ", products)
74
+    if (products.length > 0) {
75
+      let productType = sessionStorage.getItem('amber-select-product-type')
76
+      let newFilteredProducts = products.filter(
77
+        (product) => product.productType === productType
78
+      );
78
 
79
 
79
-  }, [])
80
+      setFilteredProducts(newFilteredProducts)
80
 
81
 
81
-  useEffect(() => {
82
+      const tagList = getAllTags(newFilteredProducts);
83
+      setTagFilterOption(tagList);
82
 
84
 
83
-    console.log(products)
84
-    let productType = sessionStorage.getItem('amber-select-product-type')
85
-    let newFilteredProducts = products.filter(
86
-      (product) => product.productType === productType
87
-    );
88
 
85
 
89
-    setFilteredProducts(newFilteredProducts)
86
+      // Filter will only exist if the user haven't click on collection
87
+      if (!sessionStorage.getItem('amber-select-collection')) {
88
+        const collectionList = getAllCollection(newFilteredProducts);
89
+        setCollectionFilterOption(collectionList);
90
+      }else{
91
+        setCollection(sessionStorage.getItem('amber-select-collection'))
92
+      }
90
 
93
 
91
-    if (sessionStorage.getItem('amber-select-collection')){
92
-      const categoryList = getAllTags(newFilteredProducts) // yes we use tags as category, while I was coding this, this is the only way to implement category
93
-      setCategoryFilterOption(categoryList)
94
-    } else if (sessionStorage.getItem('amber-select-category')) {
95
-      const collectionList = getAllCollection(newFilteredProducts)
96
-      setCollectionFilterOption(collectionList)
97
     }
94
     }
98
-    
95
+
99
 
96
 
100
   }, [products])
97
   }, [products])
101
 
98
 
106
 
103
 
107
       let productType = sessionStorage.getItem('amber-select-product-type')
104
       let productType = sessionStorage.getItem('amber-select-product-type')
108
 
105
 
109
-      // Category
110
       let newFilteredProducts = products.filter(
106
       let newFilteredProducts = products.filter(
111
-        (product) => {
107
+        (product) => product.productType === productType
108
+      );
112
 
109
 
113
-          if(category == 'all'){
110
+      // Tags
111
+      newFilteredProducts = newFilteredProducts.filter(
112
+        (product) => {
113
+          if (tags == 'all') {
114
             return product.productType === productType
114
             return product.productType === productType
115
           } else {
115
           } else {
116
-            return product.productType === productType && product.tags.includes(category)
116
+            return product.productType === productType && product.tags.includes(tags)
117
           }
117
           }
118
 
118
 
119
         }
119
         }
120
       );
120
       );
121
-
121
+      
122
       // Collection
122
       // Collection
123
       newFilteredProducts = newFilteredProducts.filter(
123
       newFilteredProducts = newFilteredProducts.filter(
124
         (product) => {
124
         (product) => {
125
 
125
 
126
-          if(collection == 'all'){
127
-            
126
+          if (collection == 'all') {
128
             return product.productType === productType
127
             return product.productType === productType
129
           } else {
128
           } else {
130
-            
131
-            return product.productType === productType && product.collections.nodes.some(data => data.title === collection)
129
+            return product.productType === productType && product.collections.some(data => data.title === collection)
132
           }
130
           }
133
 
131
 
134
         }
132
         }
135
       );
133
       );
134
+      
136
 
135
 
137
       if (sort === "title") {
136
       if (sort === "title") {
138
         newFilteredProducts = newFilteredProducts.sort((a, b) => a.title.localeCompare(b.title));
137
         newFilteredProducts = newFilteredProducts.sort((a, b) => a.title.localeCompare(b.title));
146
 
145
 
147
     }
146
     }
148
 
147
 
149
-  }, [category, collection, sort])
150
-
151
-  useEffect(() => {
152
-    if (products) console.log(products)
153
-  }, [products])
154
-
155
-
156
-
148
+  }, [tags, collection, sort])
157
 
149
 
158
   const handleChange = (event) => {
150
   const handleChange = (event) => {
159
     //setInput({ ...input, [event.target.name]: event.target.value });
151
     //setInput({ ...input, [event.target.name]: event.target.value });
160
   };
152
   };
161
 
153
 
162
-  const renderProduct = (id, img_url, title, collection, price, currency, extra_desc) => {
154
+  const renderProduct = (id, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, extra_desc) => {
163
 
155
 
164
     return (
156
     return (
165
-      <Grid item size={{ xs: 6, sm: 6, md: 4 }}>
157
+      <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
158
+
166
         <Box
159
         <Box
167
           onClick={() => { window.location.href = `/products/${id}` }}
160
           onClick={() => { window.location.href = `/products/${id}` }}
168
           sx={{
161
           sx={{
187
             NEW
180
             NEW
188
           </Button> */}
181
           </Button> */}
189
 
182
 
190
-          <Box sx={{ py: 5 }}>
191
-            <Typography variant="body1" sx={{fontWeight:"400", mb:1}}>
192
-              {collection}
183
+          <Box sx={{ pb: 5, pt: 3, width: "80%" }}>
184
+            <Typography variant="body1" sx={{ fontWeight: "400", mb: 1 }}>
185
+              {collection_name}
193
             </Typography>
186
             </Typography>
194
-            <Typography variant="h4" sx={{ fontWeight: "bolder", mb:1 }}>
187
+            <Typography variant="h5" sx={{ fontWeight: "bolder", mb: 1 }}>
195
               {title}
188
               {title}
196
             </Typography>
189
             </Typography>
197
-            <Typography variant="body1" sx={{fontWeight:"400", mb:1}}>
198
-              {`${currency} ${parseFloat(price).toFixed(2)}`}
190
+            <Typography variant="body1" sx={{ fontWeight: "400" }}>
191
+              {`${minPriceCurrency} ${parseFloat(minPrice).toFixed(2)}`}
199
             </Typography>
192
             </Typography>
200
             <Typography variant="body1" sx={{ mt: 2 }}>
193
             <Typography variant="body1" sx={{ mt: 2 }}>
201
               {extra_desc}
194
               {extra_desc}
214
         sx={{
207
         sx={{
215
           display: "flex",
208
           display: "flex",
216
           justifyContent: "space-between",
209
           justifyContent: "space-between",
217
-          py:0,
218
-          flexDirection:{
219
-            xs:"column",
220
-            sm:"row",
221
-            md:"row",
222
-            lg:"row"
210
+          py: 0,
211
+          flexDirection: {
212
+            xs: "column",
213
+            sm: "row",
214
+            md: "row",
215
+            lg: "row"
223
           },
216
           },
224
           alignItems: "center",
217
           alignItems: "center",
225
           backgroundColor: "background.black",
218
           backgroundColor: "background.black",
229
         }}
222
         }}
230
       >
223
       >
231
         {/* Left Side: Page Title */}
224
         {/* Left Side: Page Title */}
232
-        <Typography variant="body2" sx={{fontSize:10, mt:{xs:1, sm:1, md:0}}}>
225
+        <Typography variant="body2" sx={{ fontSize: 10, mt: { xs: 1, sm: 1, md: 0 } }}>
233
           {`${filteredProducts.length} Item`}
226
           {`${filteredProducts.length} Item`}
234
         </Typography>
227
         </Typography>
235
 
228
 
236
         {/* Right Side: Option Inputs */}
229
         {/* Right Side: Option Inputs */}
237
-        <Box sx={{ display: "flex", gap: 2, flexDirection:{
238
-            xs:"column",
239
-            sm:"row",
240
-            md:"row",
241
-            lg:"row"
242
-          }, }}>
243
-
244
-          {/* length 1, cause we know upon selecting by tags, there would be only 1 category, which mean no need to view this option */}
245
-          {(categoryFilterOption.length > 0) && <FormControl sx={{ m: 1, display: "flex", flexDirection: "row" }} variant="standard">
246
-            <Typography variant="body2" sx={{ mr: 1, my: "auto" }}>Category</Typography>
230
+        <Box sx={{
231
+          display: "flex", gap: 2, flexDirection: {
232
+            xs: "column",
233
+            sm: "row",
234
+            md: "row",
235
+            lg: "row"
236
+          },
237
+        }}>
238
+
239
+          {(tagFilterOption.length > 0) && <FormControl sx={{ m: 1, display: "flex", flexDirection: "row" }} variant="standard">
240
+            <Typography variant="body2" sx={{ mr: 1, my: "auto" }}>Tag</Typography>
247
             <Select
241
             <Select
248
-              value={category}
242
+              value={tags}
249
               onChange={(event) => {
243
               onChange={(event) => {
250
-                setCategory(event.target.value);
244
+                setTags(event.target.value);
251
               }}
245
               }}
252
               sx={{
246
               sx={{
253
-                '& .MuiSelect-select':{
254
-                  border:"none"
247
+                '& .MuiSelect-select': {
248
+                  border: "none"
255
                 }
249
                 }
256
               }}
250
               }}
257
-              input={<BootstrapInput  />}
251
+              input={<BootstrapInput />}
258
               name="type"
252
               name="type"
259
             >
253
             >
260
               <MenuItem value={'all'}>All</MenuItem>
254
               <MenuItem value={'all'}>All</MenuItem>
261
-              {categoryFilterOption?.map((data) => (<MenuItem value={data}>{data}</MenuItem>))}
255
+              {tagFilterOption?.map((data) => (<MenuItem value={data}>{data}</MenuItem>))}
262
             </Select>
256
             </Select>
263
           </FormControl>}
257
           </FormControl>}
264
 
258
 
270
                 setCollection(event.target.value);
264
                 setCollection(event.target.value);
271
               }}
265
               }}
272
               sx={{
266
               sx={{
273
-                '& .MuiSelect-select':{
274
-                  border:"none"
267
+                '& .MuiSelect-select': {
268
+                  border: "none"
275
                 }
269
                 }
276
               }}
270
               }}
277
               input={<BootstrapInput />}
271
               input={<BootstrapInput />}
278
               name="type"
272
               name="type"
279
             >
273
             >
280
               <MenuItem value={'all'}>All</MenuItem>
274
               <MenuItem value={'all'}>All</MenuItem>
281
-              {collectionFilterOption?.map(({title}) => (<MenuItem value={title}>{title}</MenuItem>))}
275
+              {collectionFilterOption?.map(({ title }) => (<MenuItem value={title}>{title}</MenuItem>))}
282
             </Select>
276
             </Select>
283
           </FormControl>}
277
           </FormControl>}
284
 
278
 
290
                 setSort(event.target.value);
284
                 setSort(event.target.value);
291
               }}
285
               }}
292
               sx={{
286
               sx={{
293
-                '& .MuiSelect-select':{
294
-                  border:"none"
287
+                '& .MuiSelect-select': {
288
+                  border: "none"
295
                 }
289
                 }
296
               }}
290
               }}
297
               input={<BootstrapInput />}
291
               input={<BootstrapInput />}
308
 
302
 
309
       {/* LIST */}
303
       {/* LIST */}
310
       <Box sx={{ mb: 5 }}>
304
       <Box sx={{ mb: 5 }}>
311
-        <Grid container spacing={0.3} columns={12}>
305
+        <Grid container spacing={0.5} columns={12}>
312
           {filteredProducts.map((product, index) => {
306
           {filteredProducts.map((product, index) => {
313
 
307
 
314
-            let { id, title, compareAtPriceRange, images, collections } = product
315
-            let price = compareAtPriceRange.maxVariantPrice.amount
316
-            let currency = compareAtPriceRange.maxVariantPrice.currencyCode
317
-            let img_url = images.nodes[0].url
318
-            let collection_name = collections.nodes[0]?.title
308
+            let { id, title, images, collections, minVariantPrice, maxVariantPrice, productType, variants } = product
309
+
310
+            let minPrice = minVariantPrice.amount
311
+            let minPriceCurrency = minVariantPrice.currencyCode
312
+            let maxPrice = maxVariantPrice.amount
313
+            let maxPriceCurrency = maxVariantPrice.currencyCode
314
+
315
+            let img_url = images[0].url
316
+            let collection_name = collections[0].title
319
 
317
 
320
             // ID
318
             // ID
321
             const parts = id.split('/');
319
             const parts = id.split('/');
323
 
321
 
324
 
322
 
325
             if (index < size) {
323
             if (index < size) {
326
-              return renderProduct(prodID, img_url, title, collection_name, price, currency, "")
324
+              return renderProduct(prodID, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, "")
327
             }
325
             }
328
 
326
 
329
           })}
327
           })}

+ 95
- 24
src/components/ProductSelected/ProductSelected.jsx View File

1
-import { useEffect } from 'react';
1
+import { useEffect, useState, useRef } from 'react';
2
 import { Box, Typography, Button, useMediaQuery } from '@mui/material';
2
 import { Box, Typography, Button, useMediaQuery } from '@mui/material';
3
 import Grid from '@mui/material/Grid2';
3
 import Grid from '@mui/material/Grid2';
4
 import { useSelector, useDispatch } from 'react-redux';
4
 import { useSelector, useDispatch } from 'react-redux';
5
 import { fetchProducts } from '../../redux/slices/productSlice';
5
 import { fetchProducts } from '../../redux/slices/productSlice';
6
 
6
 
7
+import { Swiper, SwiperSlide } from 'swiper/react';
8
+import { Scrollbar, A11y } from 'swiper/modules';
9
+
10
+import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
11
+import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
12
+
7
 const ProductSelected = () => {
13
 const ProductSelected = () => {
8
 
14
 
15
+  const swiperRef = useRef(null); // Create a ref for the Swiper instance
9
   const products = useSelector((state) => state.products.products.data)
16
   const products = useSelector((state) => state.products.products.data)
10
   const dispatch = useDispatch();
17
   const dispatch = useDispatch();
11
 
18
 
19
+  const [filterProducts, setFilterProducts] = useState([])
20
+
12
   useEffect(() => {
21
   useEffect(() => {
13
 
22
 
14
-    dispatch(fetchProducts({maxResults: 4, sortBy:'TITLE', customQuery:"tag:new"}))
23
+    dispatch(fetchProducts())
15
 
24
 
16
   }, [])
25
   }, [])
17
 
26
 
18
-  const renderProduct = (id, img_url, title, collection, price, currency, extra_desc) => {
27
+  useEffect(() => {
28
+
29
+    if (products.length > 0) {
30
+      setFilterProducts(products.filter(({ selected }) => selected))
31
+    }
32
+
33
+  }, [products])
34
+
35
+  const swipeToLeft = () => {
36
+    if (swiperRef.current) {
37
+      swiperRef.current.slidePrev(); // Move to the previous slide
38
+    }
39
+  };
40
+
41
+  const swipeToRight = () => {
42
+    if (swiperRef.current) {
43
+      swiperRef.current.slideNext(); // Move to the previous slide
44
+    }
45
+  };
46
+
47
+
48
+  const renderProduct = (id, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, extra_desc) => {
19
 
49
 
20
     return (
50
     return (
21
       <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
51
       <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
52
+
22
         <Box
53
         <Box
23
           onClick={() => { window.location.href = `/products/${id}` }}
54
           onClick={() => { window.location.href = `/products/${id}` }}
24
           sx={{
55
           sx={{
43
             NEW
74
             NEW
44
           </Button> */}
75
           </Button> */}
45
 
76
 
46
-          <Box sx={{ pb: 5, pt:3 }}>
47
-            <Typography variant="body1" sx={{fontWeight:"400", mb:1}}>
48
-              {collection}
77
+          <Box sx={{ pb: 5, pt: 3 }}>
78
+            <Typography variant="body1" sx={{ fontWeight: "400", mb: 1 }}>
79
+              {collection_name}
49
             </Typography>
80
             </Typography>
50
-            <Typography variant="h5" sx={{ fontWeight: "bolder", mb:1}}>
81
+            <Typography variant="h5" sx={{ fontWeight: "bolder", mb: 1 }}>
51
               {title}
82
               {title}
52
             </Typography>
83
             </Typography>
53
-            <Typography variant="body1" sx={{fontWeight:"400"}}>
54
-              {`${currency} ${parseFloat(price).toFixed(2)}`}
84
+            <Typography variant="body1" sx={{ fontWeight: "400" }}>
85
+              {`${minPriceCurrency} ${parseFloat(minPrice).toFixed(2)}`}
55
             </Typography>
86
             </Typography>
56
             <Typography variant="body1" sx={{ mt: 2 }}>
87
             <Typography variant="body1" sx={{ mt: 2 }}>
57
               {extra_desc}
88
               {extra_desc}
64
   }
95
   }
65
 
96
 
66
 
97
 
67
-  const isMobileScreen = useMediaQuery((theme) => theme.breakpoints.down('sm'))
68
-
69
-  const displayedImages = isMobileScreen ? products.slice(0, 2) : products.slice(0, 4);
70
-
71
   return (
98
   return (
72
-    <Box sx={{ mb: 5 }}>
73
-      <Grid container spacing={1} columns={12}>
74
-        {displayedImages.map((product, index) => {
99
+    <Box sx={{
100
+      mb: 5,
101
+    }}>
102
+
103
+      {/* OFFSET PURPOSE */}
104
+      <Grid size={{ xs: 12, md: 4 }} sx={{ mx: "auto", display: "flex", alignItems: "center", justifyContent: "center", my: 4 }}>
105
+        <ChevronLeftIcon
106
+          style={{
107
+            cursor:"pointer",
108
+            color:"#B7B7B7"
109
+          }}
110
+          onClick={swipeToLeft}
111
+        />
112
+        <Typography variant="h4" sx={{mx:2}}>
113
+          NEW IN
114
+        </Typography>
115
+
116
+        <KeyboardArrowRightIcon
117
+          style={{
118
+            cursor:"pointer",
119
+            color:"#B7B7B7"
120
+          }}
121
+          onClick={swipeToRight}
122
+        />
123
+      </Grid>
75
 
124
 
76
-          let { id, title, compareAtPriceRange, images, collections } = product
77
-          let price = compareAtPriceRange.maxVariantPrice.amount
78
-          let currency = compareAtPriceRange.maxVariantPrice.currencyCode
79
-          let img_url = images.nodes[0].url
80
-          let collection_name = collections.nodes[0]?.title
125
+      <Swiper
126
+        modules={[Scrollbar, A11y]}
127
+        spaceBetween={20}
128
+        breakpoints={{
129
+          0: { slidesPerView: 2 }, // For very small screens
130
+          640: { slidesPerView: 2 }, // For screens >= 640px
131
+          1024: { slidesPerView: 4 }, // For screens >= 1024px
132
+        }}
133
+        pagination={{ clickable: true }}
134
+        scrollbar={{ draggable: true }}
135
+        onSwiper={(swiper) => (swiperRef.current = swiper)}
136
+      >
137
+        {filterProducts.map((product, index) => {
138
+
139
+          let { id, title, images, collections, minVariantPrice, maxVariantPrice, productType, variants } = product
140
+
141
+          let minPrice = minVariantPrice.amount
142
+          let minPriceCurrency = minVariantPrice.currencyCode
143
+          let maxPrice = maxVariantPrice.amount
144
+          let maxPriceCurrency = maxVariantPrice.currencyCode
145
+
146
+          let img_url = images[0].url
147
+          let collection_name = collections[0].title
81
 
148
 
82
           // ID
149
           // ID
83
           const parts = id.split('/');
150
           const parts = id.split('/');
84
           let prodID = parts[parts.length - 1];
151
           let prodID = parts[parts.length - 1];
85
 
152
 
86
-
87
-          return renderProduct(prodID, img_url, title, collection_name, price, currency, "")
153
+          return (
154
+            <SwiperSlide>
155
+              {renderProduct(prodID, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, "")}
156
+            </SwiperSlide>
157
+          )
88
 
158
 
89
         })}
159
         })}
90
-      </Grid>
160
+
161
+      </Swiper>
91
     </Box>
162
     </Box>
92
   );
163
   );
93
 };
164
 };

+ 6
- 0
src/index.js View File

4
 import App from './App';
4
 import App from './App';
5
 import { Provider } from 'react-redux';
5
 import { Provider } from 'react-redux';
6
 import store from './redux/store';
6
 import store from './redux/store';
7
+
8
+// CSS STUFF
9
+import 'swiper/css';
10
+import 'swiper/css/navigation';
11
+import 'swiper/css/pagination';
12
+import 'animate.css';
7
 import '@fontsource/roboto/300.css';
13
 import '@fontsource/roboto/300.css';
8
 import '@fontsource/roboto/400.css';
14
 import '@fontsource/roboto/400.css';
9
 import '@fontsource/roboto/500.css';
15
 import '@fontsource/roboto/500.css';

+ 0
- 26
src/pages/Home.jsx View File

7
 import Feature from '../components/Feature'
7
 import Feature from '../components/Feature'
8
 import { Link } from '@mui/material'
8
 import { Link } from '@mui/material'
9
 import VideoAds from '../components/VideoAds'
9
 import VideoAds from '../components/VideoAds'
10
-import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
11
-import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
12
 import ProductType from '../components/ProductType';
10
 import ProductType from '../components/ProductType';
13
 import NewsLetter from '../components/NewsLetter';
11
 import NewsLetter from '../components/NewsLetter';
14
-
15
 import ProductService from "../services/ProductService"
12
 import ProductService from "../services/ProductService"
16
-
17
 import AmberHomeWallpaper from "../assets/images/titleBg.jpg";
13
 import AmberHomeWallpaper from "../assets/images/titleBg.jpg";
18
 import AmberBeautyWallpaper from "../assets/images/amberBeautyWallpaper.jpg";
14
 import AmberBeautyWallpaper from "../assets/images/amberBeautyWallpaper.jpg";
19
 import AmberClothing from "../assets/images/amberClothing.jpg"
15
 import AmberClothing from "../assets/images/amberClothing.jpg"
40
         }
36
         }
41
       }}>
37
       }}>
42
 
38
 
43
-        <Grid container sx={{
44
-          py:{
45
-            xs:3,
46
-            md:3,
47
-            lg:6
48
-          }
49
-        }}>
50
-
51
-          {/* OFFSET PURPOSE */}
52
-
53
-          <Grid size={{xs:12, md:4}} sx={{mx:"auto", display:"flex", alignItems:"center", justifyContent:"center" }}>
54
-            {/* Icon at the start */}
55
-            <KeyboardArrowLeftIcon sx={{ mr: 3, color: "#B7B7B7" }} />
56
-            <Typography variant="h4">
57
-              NEW IN
58
-            </Typography>
59
-            {/* Icon at the end */}
60
-            <KeyboardArrowRightIcon sx={{ ml: 3, color: "#B7B7B7" }} />
61
-          </Grid>
62
-
63
-        </Grid>
64
-
65
         <ProductSelected />
39
         <ProductSelected />
66
         
40
         
67
       </Box>
41
       </Box>

+ 1
- 1
src/pages/Products/Product.jsx View File

71
         <Grid size={{ xs: 12, md: 6 }}>
71
         <Grid size={{ xs: 12, md: 6 }}>
72
           <Box sx={{ paddingRight: 1 }}>
72
           <Box sx={{ paddingRight: 1 }}>
73
             <Box>
73
             <Box>
74
-              <ImageView />
74
+              {/* <ImageView /> */}
75
             </Box>
75
             </Box>
76
           </Box>
76
           </Box>
77
         </Grid>
77
         </Grid>

+ 3
- 13
src/pages/Products/index.jsx View File

8
 
8
 
9
 const Products = () => {
9
 const Products = () => {
10
 
10
 
11
-  const [title, setTitle] = useState('')
12
-  const [image, setImage] = useState('')
13
-
14
   useEffect(()=>{
11
   useEffect(()=>{
15
     
12
     
16
     // if user come from select collection
13
     // if user come from select collection
17
-    if(sessionStorage.getItem('amber-select-collection')){
18
-
19
-      let { id, name, src } = JSON.parse(sessionStorage.getItem('amber-select-collection'))
20
-      setTitle(name)
21
-      setImage(src)
22
-
23
-    }else if(sessionStorage.getItem('amber-select-category')){
14
+    if(sessionStorage.getItem('amber-select-product-type')){
24
 
15
 
25
-      let category = sessionStorage.getItem('amber-select-category')
26
-      setTitle(category)
16
+      let selectProductType = sessionStorage.getItem('amber-select-product-type')
27
 
17
 
28
     }else{
18
     }else{
29
       window.location.href = '/'
19
       window.location.href = '/'
33
 
23
 
34
   return (
24
   return (
35
     <>
25
     <>
36
-      <PageTitle title={title} image={image} />
37
       <Box sx={{
26
       <Box sx={{
27
+        mt:15,
38
         px: {
28
         px: {
39
           xs: 2,
29
           xs: 2,
40
           md: 12,
30
           md: 12,

+ 2
- 2
src/redux/slices/productSlice.js View File

33
 
33
 
34
 export const fetchProducts = createAsyncThunk(
34
 export const fetchProducts = createAsyncThunk(
35
   'product/fetchProducts',
35
   'product/fetchProducts',
36
-  async ({maxResults, sortBy, customQuery}) => {
36
+  async () => {
37
 
37
 
38
-    const response = await ProductService.getProducts(maxResults, sortBy, customQuery)
38
+    const response = await ProductService.getProducts()
39
     return response
39
     return response
40
 
40
 
41
   }
41
   }

+ 84
- 261
src/services/ProductService.js View File

1
 import { createStorefrontApiClient } from '@shopify/storefront-api-client';
1
 import { createStorefrontApiClient } from '@shopify/storefront-api-client';
2
 import { REACT_APP_ACCESS_TOKEN, REACT_APP_SHOP_NAME } from '../utils/httpCommon'
2
 import { REACT_APP_ACCESS_TOKEN, REACT_APP_SHOP_NAME } from '../utils/httpCommon'
3
-
4
-const defaultQuery = 'status:active, product_type:clothing, product_categroy:dresses';
5
-const defaultResult = 250;
6
-const defaultSortBy = 'TITLE';
3
+import { formatProductData } from '../utils/helpers'
7
 
4
 
8
 const client = createStorefrontApiClient({
5
 const client = createStorefrontApiClient({
9
   storeDomain: `https://${REACT_APP_SHOP_NAME}/api/2024-10/graphql.json`,
6
   storeDomain: `https://${REACT_APP_SHOP_NAME}/api/2024-10/graphql.json`,
11
   publicAccessToken: REACT_APP_ACCESS_TOKEN,
8
   publicAccessToken: REACT_APP_ACCESS_TOKEN,
12
 });
9
 });
13
 
10
 
14
-// getProduct(5, CREATED_AT, 'product_type:clothing'); 
15
-const getProducts = async (maxResults = defaultResult, sortBy = defaultSortBy, customQuery = defaultQuery) => {
16
-
17
-  const query = `{
18
-    products(first: ${maxResults}, query: "${customQuery}", sortKey:${sortBy}) {
19
-      nodes {
20
-        id
21
-        title
22
-        productType
23
-        compareAtPriceRange {
24
-          maxVariantPrice {
25
-            amount
26
-            currencyCode
27
-          }
28
-          minVariantPrice {
29
-            amount
30
-            currencyCode
31
-          }
32
-        }
33
-        images(first: 4) {
34
-          nodes {
35
-            src
36
-            url
37
-          }
38
-        }
39
-        collections(first: 55) {
40
-          nodes {
41
-            title
42
-          }
43
-        }
44
-        variants(first: 200) {
45
-          nodes {
46
-            id
47
-            title
48
-            price {
49
-              amount
50
-              currencyCode
51
-            }
52
-          }
53
-        }
54
-      }
55
-      pageInfo {
56
-        hasNextPage
57
-        hasPreviousPage
58
-      }
59
-    }
60
-  }`
61
-
62
-  const { data, errors, extensions } = await client.request(query, {
63
-    variables: {
64
-      handle: 'sample-product',
65
-    },
66
-  });
67
-
68
-  return data.products.nodes
69
-
70
-}
71
-
72
-const getProduct = async (id = "") => {
73
-
74
-  const query = `{
75
-    product(id: "gid://shopify/Product/${id}") {
76
-      id
77
-      title
78
-      descriptionHtml
79
-      productType
80
-      compareAtPriceRange {
81
-        maxVariantPrice {
82
-          currencyCode
83
-          amount
84
-        }
85
-        minVariantPrice {
86
-          amount
87
-          currencyCode
88
-        }
89
-      }
90
-      images(first: 4) {
91
-        nodes {
92
-          src
93
-          url
94
-        }
95
-      }
96
-      variants(first: 200) {
97
-        nodes {
98
-          id
99
-          title
100
-          price {
101
-            amount
102
-            currencyCode
103
-          }
104
-          selectedOptions {
105
-            name
106
-            value
107
-          }
108
-          quantityAvailable
109
-        }
110
-      }
111
-      collections(first: 20) {
112
-        nodes {
113
-          title
114
-        }
115
-      }
116
-    }
117
-  }`
118
-
119
-  const { data, errors, extensions } = await client.request(query, {
120
-    variables: {
121
-      handle: 'sample-product',
122
-    },
123
-  });
124
-
125
-  return data.product
11
+const getProducts = async (maxResults = 2) => {
126
 
12
 
127
-}
13
+  let hasNextCursor = true
14
+  let products = []; 
15
+  let cursor = ``
128
 
16
 
129
-const getProductsByCollection = async (collectionId) => {
17
+  while (hasNextCursor) {
130
 
18
 
131
-  const query = `{
132
-    collection(id: "${collectionId}") {
133
-      products(first: 250) {
19
+    const query = `{
20
+      products(first: ${2}, ${cursor},  sortKey: CREATED_AT) {
134
         nodes {
21
         nodes {
135
           id
22
           id
136
           title
23
           title
137
           createdAt
24
           createdAt
138
           productType
25
           productType
139
           tags
26
           tags
140
-          compareAtPriceRange {
141
-            maxVariantPrice {
27
+          priceRange {
28
+            minVariantPrice {
142
               amount
29
               amount
143
               currencyCode
30
               currencyCode
144
             }
31
             }
145
-            minVariantPrice {
32
+            maxVariantPrice {
146
               amount
33
               amount
147
               currencyCode
34
               currencyCode
148
             }
35
             }
149
           }
36
           }
150
           images(first: 4) {
37
           images(first: 4) {
151
             nodes {
38
             nodes {
152
-              src
153
               url
39
               url
154
             }
40
             }
155
           }
41
           }
156
-          collections(first: 55) {
42
+          collections(first: 10) {
157
             nodes {
43
             nodes {
158
               title
44
               title
45
+              image {
46
+                url
47
+              }
159
             }
48
             }
160
           }
49
           }
161
-        }
162
-      }
163
-    }
164
-  }`
165
-
166
-  const { data, errors, extensions } = await client.request(query, {
167
-    variables: {},
168
-  });
169
-
170
-  return data.collection.products.nodes
171
-}
172
-
173
-const getProductsByTags = async (tag) => {
174
-
175
-  let customQuery = `query: "tag:${tag}"`
176
-  if(tag == 'all') customQuery = ''
177
-
178
-  const query = `{
179
-    products(first: 250, ${customQuery}) {
180
-      nodes {
181
-        id
182
-        title
183
-        createdAt
184
-        productType
185
-        tags
186
-        compareAtPriceRange {
187
-          maxVariantPrice {
188
-            amount
189
-            currencyCode
190
-          }
191
-          minVariantPrice {
192
-            amount
193
-            currencyCode
50
+          variants(first: 200) {
51
+            nodes {
52
+              id
53
+              title
54
+              price {
55
+                amount
56
+                currencyCode
57
+              }
58
+            }
194
           }
59
           }
195
-        }
196
-        images(first: 4) {
197
-          nodes {
198
-            src
199
-            url
60
+          metafield(key: "selected", namespace: "custom") {
61
+            key
62
+            value
200
           }
63
           }
201
         }
64
         }
202
-        collections(first: 5) {
203
-          nodes {
204
-            title
205
-          }
65
+        pageInfo {
66
+          hasNextPage
67
+          hasPreviousPage
68
+          endCursor
69
+          startCursor
206
         }
70
         }
207
       }
71
       }
208
-    }
209
-  }`
210
-
211
-  const { data, errors, extensions } = await client.request(query, {
212
-    variables: {},
213
-  });
214
-
215
-  return data.products.nodes
216
-}
217
-
218
-const getProductTypes = async () => {
219
-  const query = `{
220
-    productTypes(first: 250) {
221
-      edges {
222
-        node
223
-      }
224
-    }  
225
-  }`
226
-
227
-  const { data, errors, extensions } = await client.request(query, {
228
-    variables: {},
229
-  });
72
+    }`
230
 
73
 
231
-  // make sure clothing to be always the first
232
-  const edges = data.productTypes.edges
233
-  const reorderedEdges = [
234
-    ...edges.filter(edge => edge.node === "clothing"),
235
-    ...edges.filter(edge => edge.node !== "clothing")
236
-  ];
74
+    const { data, errors, extensions } = await client.request(query, {
75
+      variables: {},
76
+    });
237
 
77
 
238
-  return reorderedEdges
239
-}
78
+    let { pageInfo, nodes } = data?.products
240
 
79
 
241
-const getCollections = async (name) => {
80
+    products = [...products, ...nodes]
242
 
81
 
243
-  const query = `{
244
-    products(first: 250, query: "product_type:${name}") {
245
-      nodes {
246
-        createdAt
247
-        productType
248
-        collections(first: 1) {
249
-          nodes {
250
-            title
251
-            id
252
-            image {
253
-              src
254
-            }
255
-          }
256
-        }
257
-      }
82
+    if(pageInfo?.hasNextPage) {
83
+      cursor = `after:"${pageInfo?.endCursor}"`
84
+    }else{
85
+      cursor = ``
86
+      hasNextCursor = false
258
     }
87
     }
259
-  }`
260
 
88
 
261
-  const { data, errors, extensions } = await client.request(query, {
262
-    variables: {},
263
-  });
89
+  }
264
 
90
 
265
-  const uniqueCollections = new Map();
91
+  products = products.map((product) => formatProductData(product))
266
 
92
 
267
-  // Iterate through all products
268
-  data.products.nodes.forEach((product) => {
269
-    // Iterate through each collection within a product
270
-    product.collections.nodes.forEach((collection) => {
271
-      const { title, id, image } = collection;
272
-
273
-      // Use the collection ID as a unique key to avoid duplicates
274
-      if (!uniqueCollections.has(id)) {
275
-        uniqueCollections.set(id, {
276
-          name: title,
277
-          id,
278
-          src: image ? image.src : null, // Handle null image gracefully
279
-        });
280
-      }
281
-    });
282
-  });
283
-
284
-  return Array.from(uniqueCollections.values());
93
+  return products
285
 
94
 
286
 }
95
 }
287
 
96
 
288
-const getTags = async (productType) => {
97
+const getProduct = async (id = "") => {
289
 
98
 
290
   const query = `{
99
   const query = `{
291
-  products(first: 250, query: "product_type:${productType}") {
292
-      nodes {
293
-        createdAt
294
-        tags
100
+    product(id: "gid://shopify/Product/${id}") {
101
+      id
102
+      title
103
+      productType
104
+      priceRange {
105
+        minVariantPrice {
106
+          amount
107
+          currencyCode
108
+        }
109
+        maxVariantPrice {
110
+          amount
111
+          currencyCode
112
+        }
113
+      }
114
+      images(first: 4) {
115
+        nodes {
116
+          url
117
+        }
118
+      }
119
+      collections(first: 55) {
120
+        nodes {
121
+          title
122
+        }
123
+      }
124
+      variants(first: 200) {
125
+        nodes {
126
+          id
127
+          title
128
+          price {
129
+            amount
130
+            currencyCode
131
+          }
132
+        }
295
       }
133
       }
296
     }
134
     }
297
   }`
135
   }`
298
 
136
 
299
   const { data, errors, extensions } = await client.request(query, {
137
   const { data, errors, extensions } = await client.request(query, {
300
-    variables: {},
301
-  });
302
-
303
-  const tagSet = new Set();
304
-
305
-  // Iterate through each item in the data
306
-  data.products.nodes.forEach(item => {
307
-    if (item.tags && Array.isArray(item.tags)) {
308
-      item.tags.forEach(tag => tagSet.add(tag));
309
-    }
138
+    variables: {
139
+    },
310
   });
140
   });
311
 
141
 
312
-  // Convert the Set back into an array and return
313
-  return Array.from(tagSet);
142
+  return formatProductData(data.product)
314
 
143
 
315
 }
144
 }
316
 
145
 
317
-
318
 const ProductService = {
146
 const ProductService = {
319
   getProducts,
147
   getProducts,
320
-  getProduct,
321
-  getProductsByCollection,
322
-  getProductsByTags,
323
-  getProductTypes,
324
-  getCollections,
325
-  getTags
148
+  getProduct
326
 }
149
 }
327
 
150
 
328
 export default ProductService
151
 export default ProductService

+ 22
- 0
src/utils/helpers.js View File

1
+// Cause I want to have a properly standardize product data cause I hate those "nodes" thinggy
2
+// and to prevent render error in case some data doesn't exist, I want a default value
3
+
4
+export const formatProductData = (product) => {
5
+
6
+    if(!product) return
7
+
8
+    return {
9
+        id: product?.id || null,
10
+        title: product?.title || "",
11
+        createdAt: product?.createdAt,
12
+        collections: product?.collections?.nodes || [],
13
+        tags:product?.tags || [],
14
+        images: product?.images?.nodes || [],
15
+        selected: (product?.metafield?.key == "selected") ? product?.metafield?.value == "true" : false, // cause I want to have a true false value, somehow BE return text value "true", thus == used to convert it to proper boolean value
16
+        minVariantPrice: product?.priceRange?.minVariantPrice || {amount:0 , currencyCode:''},
17
+        maxVariantPrice: product?.priceRange?.maxVariantPrice || {amount:0 , currencyCode:''},
18
+        productType: product?.productType || null,
19
+        variants: product?.variants?.nodes || []
20
+    }
21
+
22
+}

Loading…
Cancel
Save