瀏覽代碼

fix product details

master
azri 2 週之前
父節點
當前提交
eb88b3e7c5

+ 14
- 9
src/components/ImageView/ImageView.jsx 查看文件

@@ -9,18 +9,23 @@ import {
9 9
 } from "@mui/material";
10 10
 import Grid from '@mui/material/Grid2';
11 11
 
12
+// Utility function to check if an object is empty
13
+const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
14
+
12 15
 const ImageView = () => {
13 16
 
14 17
   const product = useSelector((state) => state.products.product.data)
15
-  
18
+
16 19
   const [previewImage, setPreviewImage] = useState("");
17 20
   const [isZoomed, setIsZoomed] = useState(false);
18 21
 
19
-  useEffect(()=>{
22
+  useEffect(() => {
23
+
24
+    if (!isEmptyObject(product)) {
25
+      setPreviewImage(product?.images[0]?.url)
26
+    }
20 27
 
21
-    setPreviewImage(product?.images?.nodes[0]?.url)
22
-  
23
-  },[product])
28
+  }, [product])
24 29
 
25 30
   const handleThumbnailClick = (image) => {
26 31
     setPreviewImage(image);
@@ -49,7 +54,7 @@ const ImageView = () => {
49 54
                 width: "70%",
50 55
                 height: "auto",
51 56
                 display: "block",
52
-                margin:"auto auto"
57
+                margin: "auto auto"
53 58
               }}
54 59
             />
55 60
           </Box>
@@ -61,12 +66,12 @@ const ImageView = () => {
61 66
             sx={{
62 67
               display: "flex",
63 68
               flexDirection: "row",
64
-              justifyContent:"center",
69
+              justifyContent: "center",
65 70
               padding: 2,
66
-              gap:2
71
+              gap: 2
67 72
             }}
68 73
           >
69
-            {product?.images?.nodes?.map(({src, url}, index) => (
74
+            {product?.images?.map(({ src, url }, index) => (
70 75
               <IconButton
71 76
                 key={index}
72 77
                 onClick={() => handleThumbnailClick(url)}

+ 12
- 3
src/components/Navbar/Navbar.jsx 查看文件

@@ -18,7 +18,8 @@ import HomeIcon from '@mui/icons-material/Home';
18 18
 import BrushIcon from '@mui/icons-material/Brush';
19 19
 import LoyaltyIcon from '@mui/icons-material/Loyalty';
20 20
 import Grid from '@mui/material/Grid2';
21
-import { useSelector } from 'react-redux';
21
+import { useSelector, useDispatch } from 'react-redux';
22
+import { fetchProducts } from "../../redux/slices/productSlice";
22 23
 import ProductService from "../../services/ProductService";
23 24
 
24 25
 import { Swiper, SwiperSlide } from 'swiper/react';
@@ -109,6 +110,7 @@ const Navbar = () => {
109 110
   const [showHeader, setShowHeader] = useState(true);
110 111
   const [lastScrollPos, setLastScrollPos] = useState(0);
111 112
   const [language, setLanguage] = useState('English');
113
+  const dispatch = useDispatch();
112 114
 
113 115
   const cart = useSelector((state) => state.cart.cart)
114 116
   const products = useSelector((state) => state.products.products.data)
@@ -122,6 +124,12 @@ const Navbar = () => {
122 124
     list:[]
123 125
   })
124 126
 
127
+  useEffect(() => {
128
+
129
+    dispatch(fetchProducts())
130
+
131
+  }, [])
132
+
125 133
   useEffect(() => {
126 134
 
127 135
     if (!cart?.lines?.nodes) return // don't need to do anything if we have no cart data
@@ -327,6 +335,7 @@ const Navbar = () => {
327 335
               color="inherit"
328 336
               onClick={() => {
329 337
                 sessionStorage.setItem('amber-select-product-type', productType)
338
+                sessionStorage.removeItem('amber-select-collection')
330 339
                 window.location.href = `/products`;
331 340
               }}
332 341
               onMouseEnter={() => {
@@ -377,11 +386,11 @@ const Navbar = () => {
377 386
             </FormControl> */}
378 387
 
379 388
             <IconButton color="inherit">
380
-              <SearchIcon sx={{ color: "white" }} />
389
+              <SearchIcon/>
381 390
             </IconButton>
382 391
 
383 392
             <Badge sx={{ cursor: "pointer" }} onClick={() => { window.location.href = "/cart" }} badgeContent={cartAmount} color="primary">
384
-              <LocalMallIcon color="action" sx={{ color: "white" }} />
393
+              <LocalMallIcon color="action" />
385 394
             </Badge>
386 395
 
387 396
             {/* <IconButton color="inherit">

+ 14
- 9
src/components/ProductDetails/ProductDetails.jsx 查看文件

@@ -29,9 +29,8 @@ const ProductDetails = () => {
29 29
 
30 30
   const [quantity, setQuantity] = useState(1)
31 31
   const [variantSelection, setVariantSelection] = useState({
32
-    price: 0,
33
-    currencyCode: "",
34
-    quantityAvailable: 0
32
+    amount: "0",
33
+    currencyCode: ""
35 34
   })
36 35
   const [variants, setVariants] = useState([])
37 36
   const [showLoader, setShowLoader] = useState(false)
@@ -41,16 +40,17 @@ const ProductDetails = () => {
41 40
     if (!isEmptyObject(product)) {
42 41
 
43 42
       console.log("Product: ", product)
44
-      debugger
43
+      
45 44
 
46
-      let productVariants = product?.variants?.nodes
45
+      let productVariants = product?.variants
47 46
 
48 47
       // get all variant type
49 48
       if (!productVariants || productVariants?.length == 0) return
50
-
49
+      
51 50
       // we want to get the title for each variant
52 51
       const uniqueOptions = {};
53 52
       productVariants.forEach(variant => {
53
+        
54 54
         variant.selectedOptions.forEach(option => {
55 55
           if (!uniqueOptions[option.name]) {
56 56
             uniqueOptions[option.name] = new Set();
@@ -106,13 +106,16 @@ const ProductDetails = () => {
106 106
   }, [variantSelection])
107 107
 
108 108
   const handleVariantClick = (name, value) => {
109
+
110
+    
111
+
109 112
     setVariantSelection({ ...variantSelection, [name]: value })
110 113
 
111 114
     setVariantSelection((prev) => {
112 115
 
113 116
       let newVariantSelection = { ...prev }
114 117
       newVariantSelection = { ...newVariantSelection, [name]: value }
115
-      let productVariants = product?.variants?.nodes
118
+      let productVariants = product?.variants
116 119
 
117 120
       // find variant price if it all match initial variant selection
118 121
       for (const { selectedOptions, price, id, quantityAvailable } of productVariants) {
@@ -132,6 +135,8 @@ const ProductDetails = () => {
132 135
         }
133 136
       }
134 137
 
138
+      debugger
139
+
135 140
 
136 141
       return newVariantSelection
137 142
 
@@ -209,9 +214,9 @@ const ProductDetails = () => {
209 214
           <Typography variant="h5" sx={{ fontWeight: "bold", mb: 2 }}>
210 215
             {product?.title}
211 216
           </Typography>
212
-          <Typography variant="body2" color="text.secondary">
217
+          {/* <Typography variant="body2" color="text.secondary">
213 218
             {product?.collections?.nodes[0]?.title}
214
-          </Typography>
219
+          </Typography> */}
215 220
 
216 221
 
217 222
           <Typography

+ 42
- 46
src/components/ProductList/ProductList.jsx 查看文件

@@ -87,7 +87,7 @@ const ProductList = ({ size = 99999 }) => {
87 87
       if (!sessionStorage.getItem('amber-select-collection')) {
88 88
         const collectionList = getAllCollection(newFilteredProducts);
89 89
         setCollectionFilterOption(collectionList);
90
-      }else{
90
+      } else {
91 91
         setCollection(sessionStorage.getItem('amber-select-collection'))
92 92
       }
93 93
 
@@ -118,7 +118,7 @@ const ProductList = ({ size = 99999 }) => {
118 118
 
119 119
         }
120 120
       );
121
-      
121
+
122 122
       // Collection
123 123
       newFilteredProducts = newFilteredProducts.filter(
124 124
         (product) => {
@@ -131,7 +131,7 @@ const ProductList = ({ size = 99999 }) => {
131 131
 
132 132
         }
133 133
       );
134
-      
134
+
135 135
 
136 136
       if (sort === "title") {
137 137
         newFilteredProducts = newFilteredProducts.sort((a, b) => a.title.localeCompare(b.title));
@@ -151,50 +151,51 @@ const ProductList = ({ size = 99999 }) => {
151 151
     //setInput({ ...input, [event.target.name]: event.target.value });
152 152
   };
153 153
 
154
-  const renderProduct = (id, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, extra_desc) => {
154
+  const renderProduct = (handle, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, extra_desc) => {
155 155
 
156 156
     return (
157
-      <Grid item size={{ xs: 6, sm: 6, md: 3 }}>
158
-
159
-        <Box
160
-          onClick={() => { window.location.href = `/products/${id}` }}
161
-          sx={{
162
-            overflow: 'hidden',
163
-            position: 'relative',
164
-            cursor: 'pointer'
165
-          }}
166
-
167
-        >
168
-          <img
169
-            src={img_url}
170
-            alt={title}
171
-            style={{
172
-              width: '100%',
173
-              aspectRatio: '3 / 4',
174
-              objectFit: 'cover',
175
-              objectPosition: 'top center'
157
+      <Grid className="animate__animated animate__fadeIn" item size={{ xs: 6, sm: 6, md: 3 }}>
158
+
159
+        <a href={`/products/${handle}`} style={{textDecoration:"none",color:"#000"}}>
160
+          <Box
161
+            sx={{
162
+              overflow: 'hidden',
163
+              position: 'relative',
164
+              cursor: 'pointer'
176 165
             }}
177
-          />
178 166
 
179
-          {/* <Button sx={{ position: "absolute", top: 20, left: 20, boxShadow: 0 }} variant="contained">
167
+          >
168
+            <img
169
+              src={img_url}
170
+              alt={title}
171
+              style={{
172
+                width: '100%',
173
+                aspectRatio: '3 / 4',
174
+                objectFit: 'cover',
175
+                objectPosition: 'top center'
176
+              }}
177
+            />
178
+
179
+            {/* <Button sx={{ position: "absolute", top: 20, left: 20, boxShadow: 0 }} variant="contained">
180 180
             NEW
181 181
           </Button> */}
182 182
 
183
-          <Box sx={{ pb: 5, pt: 3, width: "80%" }}>
184
-            <Typography variant="body1" sx={{ fontWeight: "400", mb: 1 }}>
185
-              {collection_name}
186
-            </Typography>
187
-            <Typography variant="h5" sx={{ fontWeight: "bolder", mb: 1 }}>
188
-              {title}
189
-            </Typography>
190
-            <Typography variant="body1" sx={{ fontWeight: "400" }}>
191
-              {`${minPriceCurrency} ${parseFloat(minPrice).toFixed(2)}`}
192
-            </Typography>
193
-            <Typography variant="body1" sx={{ mt: 2 }}>
194
-              {extra_desc}
195
-            </Typography>
183
+            <Box sx={{ pb: 5, pt: 3, width: "80%" }}>
184
+              <Typography variant="body1" sx={{ fontWeight: "400", mb: 1 }}>
185
+                {collection_name}
186
+              </Typography>
187
+              <Typography variant="h5" sx={{ fontWeight: "bolder", mb: 1 }}>
188
+                {title}
189
+              </Typography>
190
+              <Typography variant="body1" sx={{ fontWeight: "400" }}>
191
+                {`${minPriceCurrency} ${parseFloat(minPrice).toFixed(2)}`}
192
+              </Typography>
193
+              <Typography variant="body1" sx={{ mt: 2 }}>
194
+                {extra_desc}
195
+              </Typography>
196
+            </Box>
196 197
           </Box>
197
-        </Box>
198
+        </a>
198 199
       </Grid>
199 200
     )
200 201
 
@@ -305,7 +306,7 @@ const ProductList = ({ size = 99999 }) => {
305 306
         <Grid container spacing={0.5} columns={12}>
306 307
           {filteredProducts.map((product, index) => {
307 308
 
308
-            let { id, title, images, collections, minVariantPrice, maxVariantPrice, productType, variants } = product
309
+            let { handle, title, images, collections, minVariantPrice, maxVariantPrice, productType, variants } = product
309 310
 
310 311
             let minPrice = minVariantPrice.amount
311 312
             let minPriceCurrency = minVariantPrice.currencyCode
@@ -315,13 +316,8 @@ const ProductList = ({ size = 99999 }) => {
315 316
             let img_url = images[0].url
316 317
             let collection_name = collections[0].title
317 318
 
318
-            // ID
319
-            const parts = id.split('/');
320
-            let prodID = parts[parts.length - 1];
321
-
322
-
323 319
             if (index < size) {
324
-              return renderProduct(prodID, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, "")
320
+              return renderProduct(handle, img_url, title, collection_name, minPrice, minPriceCurrency, maxPrice, maxPriceCurrency, "")
325 321
             }
326 322
 
327 323
           })}

+ 0
- 7
src/components/ProductSelected/ProductSelected.jsx 查看文件

@@ -14,16 +14,9 @@ const ProductSelected = () => {
14 14
 
15 15
   const swiperRef = useRef(null); // Create a ref for the Swiper instance
16 16
   const products = useSelector((state) => state.products.products.data)
17
-  const dispatch = useDispatch();
18 17
 
19 18
   const [filterProducts, setFilterProducts] = useState([])
20 19
 
21
-  useEffect(() => {
22
-
23
-    dispatch(fetchProducts())
24
-
25
-  }, [])
26
-
27 20
   useEffect(() => {
28 21
 
29 22
     if (products.length > 0) {

+ 4
- 4
src/pages/Products/Product.jsx 查看文件

@@ -21,7 +21,7 @@ const Product = () => {
21 21
   useEffect(() => {
22 22
 
23 23
     dispatch(fetchProduct(pid))
24
-
24
+    
25 25
     if(localStorage.getItem('amber-product-history')){
26 26
 
27 27
       let productHistory = JSON.parse(localStorage.getItem('amber-product-history'))
@@ -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>
@@ -95,7 +95,7 @@ const Product = () => {
95 95
           YOU MAY ALSO LIKE
96 96
         </Typography>
97 97
 
98
-        <ProductSuggestion />
98
+        {/* <ProductSuggestion /> */}
99 99
 
100 100
       </Box>
101 101
 
@@ -109,7 +109,7 @@ const Product = () => {
109 109
           RECENTLY VIEWED
110 110
         </Typography>
111 111
 
112
-        <ProductHistoryList />
112
+        {/* <ProductHistoryList /> */}
113 113
 
114 114
       </Box>
115 115
 

+ 22
- 5
src/services/ProductService.js 查看文件

@@ -8,7 +8,7 @@ const client = createStorefrontApiClient({
8 8
   publicAccessToken: REACT_APP_ACCESS_TOKEN,
9 9
 });
10 10
 
11
-const getProducts = async (maxResults = 2) => {
11
+const getProducts = async () => {
12 12
 
13 13
   let hasNextCursor = true
14 14
   let products = []; 
@@ -20,8 +20,9 @@ const getProducts = async (maxResults = 2) => {
20 20
       products(first: ${2}, ${cursor},  sortKey: CREATED_AT) {
21 21
         nodes {
22 22
           id
23
+          handle
23 24
           title
24
-          createdAt
25
+          createdAt 
25 26
           productType
26 27
           tags
27 28
           priceRange {
@@ -94,13 +95,17 @@ const getProducts = async (maxResults = 2) => {
94 95
 
95 96
 }
96 97
 
97
-const getProduct = async (id = "") => {
98
+const getProduct = async (handle = "") => {
98 99
 
99 100
   const query = `{
100
-    product(id: "gid://shopify/Product/${id}") {
101
+    product(handle: "${handle}") {
101 102
       id
103
+      handle
102 104
       title
105
+      createdAt 
103 106
       productType
107
+      tags
108
+      descriptionHtml
104 109
       priceRange {
105 110
         minVariantPrice {
106 111
           amount
@@ -116,9 +121,12 @@ const getProduct = async (id = "") => {
116 121
           url
117 122
         }
118 123
       }
119
-      collections(first: 55) {
124
+      collections(first: 10) {
120 125
         nodes {
121 126
           title
127
+          image {
128
+            url
129
+          }
122 130
         }
123 131
       }
124 132
       variants(first: 200) {
@@ -129,8 +137,17 @@ const getProduct = async (id = "") => {
129 137
             amount
130 138
             currencyCode
131 139
           }
140
+          selectedOptions {
141
+            name
142
+            value
143
+          }
144
+          quantityAvailable
132 145
         }
133 146
       }
147
+      metafield(key: "selected", namespace: "custom") {
148
+        key
149
+        value
150
+      }
134 151
     }
135 152
   }`
136 153
 

+ 6
- 4
src/utils/helpers.js 查看文件

@@ -7,16 +7,18 @@ export const formatProductData = (product) => {
7 7
 
8 8
     return {
9 9
         id: product?.id || null,
10
+        handle: product?.handle || "",
10 11
         title: product?.title || "",
11 12
         createdAt: product?.createdAt,
12
-        collections: product?.collections?.nodes || [],
13
-        tags:product?.tags || [],
14
-        images: product?.images?.nodes || [],
13
+        collections: product?.collections?.nodes || null,
14
+        descriptionHtml: product?.descriptionHtml || "",
15
+        tags:product?.tags || null,
16
+        images: product?.images?.nodes || null,
15 17
         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 18
         minVariantPrice: product?.priceRange?.minVariantPrice || {amount:0 , currencyCode:''},
17 19
         maxVariantPrice: product?.priceRange?.maxVariantPrice || {amount:0 , currencyCode:''},
18 20
         productType: product?.productType || null,
19
-        variants: product?.variants?.nodes || []
21
+        variants: product?.variants?.nodes || null
20 22
     }
21 23
 
22 24
 }

Loading…
取消
儲存