Selaa lähdekoodia

new filter

master
azri 2 viikkoa sitten
vanhempi
commit
5a1a0509b8

+ 70
- 0
package-lock.json Näytä tiedosto

@@ -19,14 +19,17 @@
19 19
         "@testing-library/jest-dom": "^5.17.0",
20 20
         "@testing-library/react": "^13.4.0",
21 21
         "@testing-library/user-event": "^13.5.0",
22
+        "animate.css": "^4.1.1",
22 23
         "axios": "^1.7.9",
23 24
         "dotenv": "^16.4.7",
25
+        "framer-motion": "^11.16.0",
24 26
         "react": "^18.3.1",
25 27
         "react-dom": "^18.3.1",
26 28
         "react-redux": "^9.2.0",
27 29
         "react-router-dom": "^7.0.2",
28 30
         "react-scripts": "5.0.1",
29 31
         "shopify-api-node": "^3.14.0",
32
+        "swiper": "^11.2.0",
30 33
         "web-vitals": "^2.1.4"
31 34
       }
32 35
     },
@@ -5399,6 +5402,12 @@
5399 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 5411
     "node_modules/ansi-escapes": {
5403 5412
       "version": "4.3.2",
5404 5413
       "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -9419,6 +9428,33 @@
9419 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 9458
     "node_modules/fresh": {
9423 9459
       "version": "0.5.2",
9424 9460
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -12666,6 +12702,21 @@
12666 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 12720
     "node_modules/ms": {
12670 12721
       "version": "2.1.3",
12671 12722
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -17202,6 +17253,25 @@
17202 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 17275
     "node_modules/symbol-tree": {
17206 17276
       "version": "3.2.4",
17207 17277
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",

+ 3
- 0
package.json Näytä tiedosto

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

+ 10
- 0
src/components/Carousel/Carousel.jsx Näytä tiedosto

@@ -37,6 +37,16 @@ const Carousel = () => {
37 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 50
       {items.map((item, index) => (
41 51
         <Box
42 52
           key={index}

+ 216
- 42
src/components/Navbar/Navbar.jsx Näytä tiedosto

@@ -1,4 +1,4 @@
1
-import React, { useState, useEffect } from "react";
1
+import { useState, useEffect, useRef } from "react";
2 2
 import AppBar from "@mui/material/AppBar";
3 3
 import Toolbar from "@mui/material/Toolbar";
4 4
 import Button from "@mui/material/Button";
@@ -21,6 +21,12 @@ import Grid from '@mui/material/Grid2';
21 21
 import { useSelector } from 'react-redux';
22 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 30
 function calculateTotalQuantity(cartItems) {
25 31
   return cartItems.reduce((total, item) => total + item.quantity, 0);
26 32
 }
@@ -56,40 +62,65 @@ const LanguageSelectItem = styled(MenuItem)(() => ({
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 106
 const Navbar = () => {
60 107
 
108
+  const swiperRef = useRef(null); // Create a ref for the Swiper instance
61 109
   const [showHeader, setShowHeader] = useState(true);
62 110
   const [lastScrollPos, setLastScrollPos] = useState(0);
63 111
   const [language, setLanguage] = useState('English');
64 112
 
65 113
   const cart = useSelector((state) => state.cart.cart)
114
+  const products = useSelector((state) => state.products.products.data)
66 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 125
   useEffect(() => {
95 126
 
@@ -99,6 +130,19 @@ const Navbar = () => {
99 130
 
100 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 146
   const handleChange = (event) => {
103 147
     setLanguage(event.target.value);
104 148
   };
@@ -120,21 +164,89 @@ const Navbar = () => {
120 164
     };
121 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 233
   return (
124 234
     <>
125 235
       <AppBar position="fixed"
126 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 252
         {/* Conditionally render the Header */}
@@ -151,7 +263,7 @@ const Navbar = () => {
151 263
 
152 264
           {/* Left Section: Logo */}
153 265
           <Box sx={{
154
-            mr:5,
266
+            mr: 5,
155 267
             display: {
156 268
               xs: "none",
157 269
               md: "none",
@@ -200,20 +312,36 @@ const Navbar = () => {
200 312
                 md: "none",
201 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 327
               color="inherit"
209 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 345
             </Button>))}
218 346
 
219 347
           </Box>
@@ -262,6 +390,52 @@ const Navbar = () => {
262 390
           </Box>
263 391
 
264 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 439
       </AppBar>
266 440
 
267 441
       <MobileNav open={open} menu={navItem} onClose={() => { setOpen(false) }} />

+ 35
- 36
src/components/ProductDetails/ProductDetails.jsx Näytä tiedosto

@@ -38,9 +38,10 @@ const ProductDetails = () => {
38 38
   const [alert, setAlert] = useState({ open: false, severity: '', message: '' });
39 39
 
40 40
   useEffect(() => {
41
-    if (product) {
41
+    if (!isEmptyObject(product)) {
42 42
 
43 43
       console.log("Product: ", product)
44
+      debugger
44 45
 
45 46
       let productVariants = product?.variants?.nodes
46 47
 
@@ -79,13 +80,13 @@ const ProductDetails = () => {
79 80
         // find variant price if it all match initial variant selection
80 81
         for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
81 82
           let { amount, currencyCode } = price;
82
-        
83
+
83 84
           // Convert array to object
84 85
           const optionsObject = selectedOptions.reduce(
85 86
             (a, { name, value }) => ({ ...a, [name]: value }),
86 87
             {}
87 88
           );
88
-        
89
+
89 90
           if (hasMatchingProperties(newVariantSelection, optionsObject)) {
90 91
             newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
91 92
             break; // Exit the loop when condition is met
@@ -116,16 +117,16 @@ const ProductDetails = () => {
116 117
       // find variant price if it all match initial variant selection
117 118
       for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
118 119
         let { amount, currencyCode } = price;
119
-      
120
+
120 121
         // Convert array to object
121 122
         const optionsObject = selectedOptions.reduce(
122 123
           (a, { name, value }) => ({ ...a, [name]: value }),
123 124
           {}
124 125
         );
125
-      
126
+
126 127
         if (hasMatchingProperties(newVariantSelection, optionsObject)) {
127 128
           newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
128
-          if(quantityAvailable == 0) setQuantity(0)
129
+          if (quantityAvailable == 0) setQuantity(0)
129 130
           else setQuantity(1)
130 131
           break; // Exit the loop when condition is met
131 132
         }
@@ -149,34 +150,34 @@ const ProductDetails = () => {
149 150
     if (isEmptyObject(cart) || isEmptyObject(cartHistory)) {
150 151
 
151 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 161
     } else {
161 162
 
162 163
       console.log("ADD ITEM:", variantSelection)
163
-      
164
+
164 165
       dispatch(addItemToCart({
165 166
         cartId: cartHistory.id,
166
-        lines:[
167
+        lines: [
167 168
           {
168
-            merchandiseId:variantSelection.id,
169
+            merchandiseId: variantSelection.id,
169 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,22 +207,20 @@ const ProductDetails = () => {
206 207
         {/* Section 1: Product Info */}
207 208
         <Box>
208 209
           <Typography variant="h5" sx={{ fontWeight: "bold", mb: 2 }}>
209
-            {product.title}
210
+            {product?.title}
210 211
           </Typography>
211 212
           <Typography variant="body2" color="text.secondary">
212 213
             {product?.collections?.nodes[0]?.title}
213 214
           </Typography>
215
+
216
+
214 217
           <Typography
215 218
             variant="body1"
216 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 222
           </Typography>
223
+
225 224
         </Box>
226 225
 
227 226
         {/* Section 2: Variants */}
@@ -230,8 +229,8 @@ const ProductDetails = () => {
230 229
           {variants.map(({ name, options }, index) => {
231 230
 
232 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 234
                   {name}
236 235
                 </Typography>
237 236
 
@@ -248,7 +247,7 @@ const ProductDetails = () => {
248 247
                     </Button>
249 248
                   ))}
250 249
                 </Box>
251
-              </>
250
+              </Box>
252 251
             )
253 252
 
254 253
           })}
@@ -298,7 +297,7 @@ const ProductDetails = () => {
298 297
             Description
299 298
           </Typography>
300 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 301
           </Typography>
303 302
         </Box>
304 303
 
@@ -318,10 +317,10 @@ const ProductDetails = () => {
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 321
         </Button>
323 322
 
324
-        { cart?.lines?.nodes?.length > 0 && <Button
323
+        {cart?.lines?.nodes?.length > 0 && <Button
325 324
           onClick={() => { window.location.href = '/cart' }}
326 325
           variant="contained"
327 326
           color="common.black"

+ 85
- 87
src/components/ProductList/ProductList.jsx Näytä tiedosto

@@ -4,7 +4,7 @@ import Grid from '@mui/material/Grid2';
4 4
 import { styled } from "@mui/material";
5 5
 //REDUX
6 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 9
 //UTIL FUNCTION
10 10
 function getAllTags(data) {
@@ -16,7 +16,7 @@ function getAllTags(data) {
16 16
 
17 17
 function getAllCollection(data) {
18 18
   const products = data || [];
19
-  const allCollection = products.flatMap(product => product.collections.nodes);
19
+  const allCollection = products.flatMap(product => product.collections);
20 20
   const uniqueCollection = Array.from(
21 21
     new Map(allCollection.map(item => [item.title, item])).values()
22 22
   );
@@ -52,50 +52,47 @@ const ProductList = ({ size = 99999 }) => {
52 52
 
53 53
   const products = useSelector((state) => state.products.products.data) // only used as referenced
54 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 56
   const [collectionFilterOption, setCollectionFilterOption] = useState([])
57 57
   const dispatch = useDispatch();
58 58
 
59 59
   //filter
60
-  const [category, setCategory] = useState('all');
60
+  const [tags, setTags] = useState('all');
61 61
   const [collection, setCollection] = useState('all');
62 62
   const [sort, setSort] = useState('title')
63 63
 
64 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 97
   }, [products])
101 98
 
@@ -106,33 +103,35 @@ const ProductList = ({ size = 99999 }) => {
106 103
 
107 104
       let productType = sessionStorage.getItem('amber-select-product-type')
108 105
 
109
-      // Category
110 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 114
             return product.productType === productType
115 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 122
       // Collection
123 123
       newFilteredProducts = newFilteredProducts.filter(
124 124
         (product) => {
125 125
 
126
-          if(collection == 'all'){
127
-            
126
+          if (collection == 'all') {
128 127
             return product.productType === productType
129 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 136
       if (sort === "title") {
138 137
         newFilteredProducts = newFilteredProducts.sort((a, b) => a.title.localeCompare(b.title));
@@ -146,23 +145,17 @@ const ProductList = ({ size = 99999 }) => {
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 150
   const handleChange = (event) => {
159 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 156
     return (
165
-      <Grid item size={{ xs: 6, sm: 6, md: 4 }}>
157
+      <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
158
+
166 159
         <Box
167 160
           onClick={() => { window.location.href = `/products/${id}` }}
168 161
           sx={{
@@ -187,15 +180,15 @@ const ProductList = ({ size = 99999 }) => {
187 180
             NEW
188 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 186
             </Typography>
194
-            <Typography variant="h4" sx={{ fontWeight: "bolder", mb:1 }}>
187
+            <Typography variant="h5" sx={{ fontWeight: "bolder", mb: 1 }}>
195 188
               {title}
196 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 192
             </Typography>
200 193
             <Typography variant="body1" sx={{ mt: 2 }}>
201 194
               {extra_desc}
@@ -214,12 +207,12 @@ const ProductList = ({ size = 99999 }) => {
214 207
         sx={{
215 208
           display: "flex",
216 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 217
           alignItems: "center",
225 218
           backgroundColor: "background.black",
@@ -229,36 +222,37 @@ const ProductList = ({ size = 99999 }) => {
229 222
         }}
230 223
       >
231 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 226
           {`${filteredProducts.length} Item`}
234 227
         </Typography>
235 228
 
236 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 241
             <Select
248
-              value={category}
242
+              value={tags}
249 243
               onChange={(event) => {
250
-                setCategory(event.target.value);
244
+                setTags(event.target.value);
251 245
               }}
252 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 252
               name="type"
259 253
             >
260 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 256
             </Select>
263 257
           </FormControl>}
264 258
 
@@ -270,15 +264,15 @@ const ProductList = ({ size = 99999 }) => {
270 264
                 setCollection(event.target.value);
271 265
               }}
272 266
               sx={{
273
-                '& .MuiSelect-select':{
274
-                  border:"none"
267
+                '& .MuiSelect-select': {
268
+                  border: "none"
275 269
                 }
276 270
               }}
277 271
               input={<BootstrapInput />}
278 272
               name="type"
279 273
             >
280 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 276
             </Select>
283 277
           </FormControl>}
284 278
 
@@ -290,8 +284,8 @@ const ProductList = ({ size = 99999 }) => {
290 284
                 setSort(event.target.value);
291 285
               }}
292 286
               sx={{
293
-                '& .MuiSelect-select':{
294
-                  border:"none"
287
+                '& .MuiSelect-select': {
288
+                  border: "none"
295 289
                 }
296 290
               }}
297 291
               input={<BootstrapInput />}
@@ -308,14 +302,18 @@ const ProductList = ({ size = 99999 }) => {
308 302
 
309 303
       {/* LIST */}
310 304
       <Box sx={{ mb: 5 }}>
311
-        <Grid container spacing={0.3} columns={12}>
305
+        <Grid container spacing={0.5} columns={12}>
312 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 318
             // ID
321 319
             const parts = id.split('/');
@@ -323,7 +321,7 @@ const ProductList = ({ size = 99999 }) => {
323 321
 
324 322
 
325 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 Näytä tiedosto

@@ -1,24 +1,55 @@
1
-import { useEffect } from 'react';
1
+import { useEffect, useState, useRef } from 'react';
2 2
 import { Box, Typography, Button, useMediaQuery } from '@mui/material';
3 3
 import Grid from '@mui/material/Grid2';
4 4
 import { useSelector, useDispatch } from 'react-redux';
5 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 13
 const ProductSelected = () => {
8 14
 
15
+  const swiperRef = useRef(null); // Create a ref for the Swiper instance
9 16
   const products = useSelector((state) => state.products.products.data)
10 17
   const dispatch = useDispatch();
11 18
 
19
+  const [filterProducts, setFilterProducts] = useState([])
20
+
12 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 50
     return (
21 51
       <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
52
+
22 53
         <Box
23 54
           onClick={() => { window.location.href = `/products/${id}` }}
24 55
           sx={{
@@ -43,15 +74,15 @@ const ProductSelected = () => {
43 74
             NEW
44 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 80
             </Typography>
50
-            <Typography variant="h5" sx={{ fontWeight: "bolder", mb:1}}>
81
+            <Typography variant="h5" sx={{ fontWeight: "bolder", mb: 1 }}>
51 82
               {title}
52 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 86
             </Typography>
56 87
             <Typography variant="body1" sx={{ mt: 2 }}>
57 88
               {extra_desc}
@@ -64,30 +95,70 @@ const ProductSelected = () => {
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 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 149
           // ID
83 150
           const parts = id.split('/');
84 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 162
     </Box>
92 163
   );
93 164
 };

+ 6
- 0
src/index.js Näytä tiedosto

@@ -4,6 +4,12 @@ import './index.css';
4 4
 import App from './App';
5 5
 import { Provider } from 'react-redux';
6 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 13
 import '@fontsource/roboto/300.css';
8 14
 import '@fontsource/roboto/400.css';
9 15
 import '@fontsource/roboto/500.css';

+ 0
- 26
src/pages/Home.jsx Näytä tiedosto

@@ -7,13 +7,9 @@ import SocialMedia from '../components/SocialMedia'
7 7
 import Feature from '../components/Feature'
8 8
 import { Link } from '@mui/material'
9 9
 import VideoAds from '../components/VideoAds'
10
-import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
11
-import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
12 10
 import ProductType from '../components/ProductType';
13 11
 import NewsLetter from '../components/NewsLetter';
14
-
15 12
 import ProductService from "../services/ProductService"
16
-
17 13
 import AmberHomeWallpaper from "../assets/images/titleBg.jpg";
18 14
 import AmberBeautyWallpaper from "../assets/images/amberBeautyWallpaper.jpg";
19 15
 import AmberClothing from "../assets/images/amberClothing.jpg"
@@ -40,28 +36,6 @@ const Home = () => {
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 39
         <ProductSelected />
66 40
         
67 41
       </Box>

+ 1
- 1
src/pages/Products/Product.jsx Näytä tiedosto

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

+ 3
- 13
src/pages/Products/index.jsx Näytä tiedosto

@@ -8,22 +8,12 @@ import Feature from '../../components/Feature'
8 8
 
9 9
 const Products = () => {
10 10
 
11
-  const [title, setTitle] = useState('')
12
-  const [image, setImage] = useState('')
13
-
14 11
   useEffect(()=>{
15 12
     
16 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 18
     }else{
29 19
       window.location.href = '/'
@@ -33,8 +23,8 @@ const Products = () => {
33 23
 
34 24
   return (
35 25
     <>
36
-      <PageTitle title={title} image={image} />
37 26
       <Box sx={{
27
+        mt:15,
38 28
         px: {
39 29
           xs: 2,
40 30
           md: 12,

+ 2
- 2
src/redux/slices/productSlice.js Näytä tiedosto

@@ -33,9 +33,9 @@ export const fetchProduct = createAsyncThunk(
33 33
 
34 34
 export const fetchProducts = createAsyncThunk(
35 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 39
     return response
40 40
 
41 41
   }

+ 84
- 261
src/services/ProductService.js Näytä tiedosto

@@ -1,9 +1,6 @@
1 1
 import { createStorefrontApiClient } from '@shopify/storefront-api-client';
2 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 5
 const client = createStorefrontApiClient({
9 6
   storeDomain: `https://${REACT_APP_SHOP_NAME}/api/2024-10/graphql.json`,
@@ -11,318 +8,144 @@ const client = createStorefrontApiClient({
11 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 21
         nodes {
135 22
           id
136 23
           title
137 24
           createdAt
138 25
           productType
139 26
           tags
140
-          compareAtPriceRange {
141
-            maxVariantPrice {
27
+          priceRange {
28
+            minVariantPrice {
142 29
               amount
143 30
               currencyCode
144 31
             }
145
-            minVariantPrice {
32
+            maxVariantPrice {
146 33
               amount
147 34
               currencyCode
148 35
             }
149 36
           }
150 37
           images(first: 4) {
151 38
             nodes {
152
-              src
153 39
               url
154 40
             }
155 41
           }
156
-          collections(first: 55) {
42
+          collections(first: 10) {
157 43
             nodes {
158 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 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 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 146
 const ProductService = {
319 147
   getProducts,
320
-  getProduct,
321
-  getProductsByCollection,
322
-  getProductsByTags,
323
-  getProductTypes,
324
-  getCollections,
325
-  getTags
148
+  getProduct
326 149
 }
327 150
 
328 151
 export default ProductService

+ 22
- 0
src/utils/helpers.js Näytä tiedosto

@@ -0,0 +1,22 @@
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…
Peruuta
Tallenna