Browse Source

done product section

master
azri 2 days ago
parent
commit
546641bddd

+ 0
- 21
API/shopify/storefront/authentication.js View File

@@ -1,21 +0,0 @@
1
-// import {createStorefrontApiClient} from '@shopify/storefront-api-client';
2
-
3
-// const client = createStorefrontApiClient({
4
-//   storeDomain: 'http://amberdevstore.myshopify.com',
5
-//   apiVersion: '2024-10',
6
-//   publicAccessToken: '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',
7
-// });
8
-
9
-const adminApiClient = new shopify.clients.Rest({session});
10
-const storefrontTokenResponse = await adminApiClient.post({
11
-  path: 'storefront_access_tokens',
12
-  type: DataType.JSON,
13
-  data: {
14
-    storefront_access_token: {
15
-      title: 'This is my test access token',
16
-    },
17
-  },
18
-});
19
-
20
-const storefrontAccessToken =
21
-  storefrontTokenResponse.body['b3e24842a76e27e1d87206daededdab0']['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'];

+ 0
- 57
API/shopify/storefront/product/getProduct.js View File

@@ -1,57 +0,0 @@
1
-const storefrontClient = require('../storefrontClient');
2
-
3
-const defaultQuery = 'status:active, product_type:clothing, product_categroy:dresses'; // Replace to see custom result
4
-
5
-const defaultResult = 10; // Replace to change the number of result
6
-
7
-const defaultSortBy = 'TITLE'; // Replace with CREATED_AT if need to sort by date, current value is for name 
8
-
9
-const getProduct = async ( maxResults = defaultResult, sortBy = defaultSortBy, customQuery = defaultQuery ) => {
10
-    try {
11
- 
12
-        const query = `{
13
-            products (first: ${maxResults}, sortKey: ${sortBy}, query: ${customQuery}) {
14
-                edges {
15
-                    node {
16
-                        id
17
-                        title
18
-                        status
19
-                        productType
20
-                        comparisonAtPriceRange {
21
-                            minVariantPrice {
22
-                                amount
23
-                                currencyCode
24
-                            }
25
-                            maxVariantPrice {
26
-                                amount
27
-                                currencyCode
28
-                            }
29
-                        }
30
-                        variants(first: 5) {
31
-                            edges {
32
-                                node {
33
-                                    id
34
-                                    title
35
-                                    price
36
-                                }
37
-                            }
38
-                        }
39
-                    }
40
-                }
41
-                pageInfo {
42
-                        hasNextPage
43
-                        hasPreviousPage
44
-                }
45
-            }
46
-        }`;
47
-
48
-        const products = await storefrontClient.query({ data: query });
49
-
50
-        return products;
51
-    } catch (error) {
52
-        throw error;
53
-    }
54
-};
55
-
56
-// Example
57
-// getProduct(5, CREATED_AT, 'product_type:clothing'); 

+ 0
- 13
API/shopify/storefront/storefrontClient.js View File

@@ -1,13 +0,0 @@
1
-const shopify = require('@shopify/shopify-api'); // Adjust import based on your environment
2
-
3
-// Load the access token and shop
4
-const storefrontAccessToken = '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'; // Replace with your actual access token
5
-const shop = 'amberdevstore.myshopify.com'; // Replace with your actual shop domain
6
-
7
-// Create and export the storefront client
8
-const storefrontClient = new shopify.clients.Storefront({
9
-  domain: shop,
10
-  storefrontAccessToken,
11
-});
12
-
13
-module.exports = storefrontClient;

+ 0
- 4
API/shopify/storefront/to_do_list.txt View File

@@ -1,4 +0,0 @@
1
-1. product tag
2
-2. product sort by date and name
3
-3. product suggested ( tagging?? )
4
-4. buang product replace with new product

+ 4
- 16
src/components/Filter/Filter.jsx View File

@@ -7,6 +7,7 @@ import MenuItem from "@mui/material/MenuItem";
7 7
 import InputLabel from "@mui/material/InputLabel";
8 8
 import { InputBase } from "@mui/material";
9 9
 import { styled } from "@mui/material";
10
+import { useSelector } from "react-redux";
10 11
 
11 12
 const BootstrapInput = styled(InputBase)(({ theme }) => ({
12 13
     'label + &': {
@@ -35,6 +36,8 @@ const BootstrapInput = styled(InputBase)(({ theme }) => ({
35 36
 
36 37
 const Filter = () => {
37 38
 
39
+    const products = useSelector((state) => state.products.products.data)
40
+
38 41
     const [input, setInput] = useState({
39 42
         type:0,
40 43
         sort:0,
@@ -62,7 +65,7 @@ const Filter = () => {
62 65
         >
63 66
             {/* Left Side: Page Title */}
64 67
             <Typography variant="body2">
65
-                13 Item
68
+                {`${products.length} Item`}
66 69
             </Typography>
67 70
 
68 71
             {/* Right Side: Option Inputs */}
@@ -99,21 +102,6 @@ const Filter = () => {
99 102
                     </Select>
100 103
                 </FormControl>
101 104
 
102
-                <FormControl sx={{ m: 1, display:{xs:"none", sm:"none", md:"flex" }, flexDirection:"row" }} variant="standard">
103
-                    <Typography variant="body2" sx={{mr:1, my:"auto"}}>Show</Typography>
104
-                    <Select
105
-                        labelId="demo-customized-select-label"
106
-                        id="demo-customized-select"
107
-                        value={input.show}
108
-                        onChange={handleChange}
109
-                        input={<BootstrapInput />}
110
-                        name="show"
111
-                    >
112
-                        <MenuItem value={0}>All</MenuItem>
113
-                        <MenuItem value={1}>Red</MenuItem>
114
-                    </Select>
115
-                </FormControl>
116
-
117 105
             </Box>
118 106
         </Box>
119 107
     );

+ 73
- 90
src/components/ProductHistoryList/ProductHistoryList.jsx View File

@@ -1,104 +1,87 @@
1
-import {useState} from 'react';
1
+import { useState, useEffect } from 'react';
2 2
 import { Box, Typography, IconButton, Pagination } from '@mui/material';
3
+import { useSelector, useDispatch } from 'react-redux';
4
+import { fetchProductsHistory } from '../../redux/slices/productSlice';
3 5
 
4 6
 import Grid from '@mui/material/Grid2';
5 7
 
6 8
 const ProductHistoryList = () => {
7 9
 
8
-  const [currentPage, setCurrentPage] = useState(1);
9
-  const itemsPerPage = 10;
10
-  const totalPages = 5;
11
-
12
-  const handleChangePage = (event, value) => {
13
-    setCurrentPage(value);
14
-  };
15
-
16
-  const handlePrevious = () => {
17
-    setCurrentPage((prev) => (prev > 1 ? prev - 1 : 1));
18
-  };
19
-
20
-  const handleNext = () => {
21
-    setCurrentPage((prev) => (prev < totalPages ? prev + 1 : totalPages));
22
-  };
23
-
24
-  const images = [
25
-    {
26
-      url: 'https://via.placeholder.com/300',
27
-      title: 'KEMBOJA IN BEIGE',
28
-      price: 123.90,
29
-      description: "Atma Sari Kurta Raya Collection 2024",
30
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
31
-    },
32
-    {
33
-      url: 'https://via.placeholder.com/300',
34
-      title: 'KEMBOJA IN BEIGE',
35
-      price: 123.90,
36
-      description: "Atma Sari Kurta Raya Collection 2024",
37
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
38
-    },
39
-    {
40
-      url: 'https://via.placeholder.com/300',
41
-      title: 'KEMBOJA IN BEIGE',
42
-      price: 123.90,
43
-      description: "Atma Sari Kurta Raya Collection 2024",
44
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
45
-    },
46
-    {
47
-      url: 'https://via.placeholder.com/300',
48
-      title: 'KEMBOJA IN BEIGE',
49
-      price: 123.90,
50
-      description: "Atma Sari Kurta Raya Collection 2024",
51
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
52
-    },
53
-    {
54
-      url: 'https://via.placeholder.com/300',
55
-      title: 'KEMBOJA IN BEIGE',
56
-      price: 123.90,
57
-      description: "Atma Sari Kurta Raya Collection 2024",
58
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
59
-    },
60
-    {
61
-      url: 'https://via.placeholder.com/300',
62
-      title: 'KEMBOJA IN BEIGE',
63
-      price: 123.90,
64
-      description: "Atma Sari Kurta Raya Collection 2024",
65
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
10
+  const products = useSelector((state) => state.products.productsHistory.data) // state.store.initialState.data
11
+  const dispatch = useDispatch()
12
+
13
+  useEffect(() => {
14
+
15
+    if (localStorage.getItem('amber-product-history')) {
16
+
17
+      let productIDList = JSON.parse(localStorage.getItem('amber-product-history'))
18
+
19
+      dispatch(fetchProductsHistory(productIDList))
20
+
66 21
     }
67
-  ];
22
+
23
+  }, [])
24
+
25
+  useEffect(()=>{ console.log(products) }, [products])
26
+
27
+  const renderProduct = (id, img_url, title, price, currency) => {
28
+
29
+    return (
30
+      <Grid item size={{ xs: 12, md: 2 }}>
31
+        <Box
32
+          onClick={()=>{ window.location.href = `/products/${id}` }}
33
+          sx={{
34
+            overflow: 'hidden',
35
+            position: 'relative',
36
+            cursor: 'pointer'
37
+          }}
38
+        >
39
+          <img
40
+            src={img_url}
41
+            alt={title}
42
+            style={{
43
+              width: '100%',
44
+              aspectRatio: '4 / 4',
45
+              objectPosition:"top center",
46
+              objectFit: 'cover',
47
+            }}
48
+          />
49
+
50
+          <Box sx={{ py: 5 }}>
51
+            <Typography variant="h5" sx={{ fontWeight: "bolder" }}>
52
+              {title}
53
+            </Typography>
54
+            <Typography variant="h6" >
55
+              {`${currency} ${parseFloat(price).toFixed(2)}`}
56
+            </Typography>
57
+          </Box>
58
+        </Box>
59
+      </Grid>
60
+    )
61
+
62
+  }
63
+
68 64
 
69 65
   return (
70 66
     <Box sx={{ mb: 5 }}>
71 67
       <Grid container spacing={1} columns={12}>
72
-        {images.map((image, index) => (<Grid item size={{xs:12, md:2}} key={index}>
73
-          <Box
74
-            sx={{
75
-              overflow: 'hidden',
76
-              position: 'relative'
77
-            }}
78
-          >
79
-            <img
80
-              src={image.url}
81
-              alt={image.title}
82
-              style={{
83
-                width: '100%',
84
-                aspectRatio: '4 / 4',
85
-                objectFit: 'cover',
86
-              }}
87
-            />
88
-
89
-            <Box sx={{ py: 5 }}>
90
-              <Typography variant="h5" sx={{ fontWeight: "bolder" }}>
91
-                {image.title}
92
-              </Typography>
93
-              <Typography variant="h6" >
94
-                {`RM ${image.price}`}
95
-              </Typography>
96
-            </Box>
97
-          </Box>
98
-        </Grid>))}
68
+        {products.map(( product, index) => {
69
+
70
+            let {id, title, compareAtPriceRange, images, collections} = product
71
+            let price = compareAtPriceRange.maxVariantPrice.amount
72
+            let currency = compareAtPriceRange.maxVariantPrice.currencyCode
73
+            let img_url = images.nodes[0].url
74
+
75
+            // ID
76
+            const parts = id.split('/');
77
+            let prodID = parts[parts.length - 1];
78
+
79
+            return renderProduct(prodID, img_url, title, price, currency)
80
+
81
+        })}
99 82
       </Grid>
100
-      {/* Pagination Controls */}
101
-      <Box
83
+      {/* Pagination Controls - DON'T REMOVE, CAUSE IT IS STILL HAVEN'T BEEN DECIDED IF WE NEED TO HAVA PAGINATION OR NOT*/}
84
+      {/* <Box
102 85
         sx={{
103 86
           display: 'flex',
104 87
           justifyContent: 'center',
@@ -112,7 +95,7 @@ const ProductHistoryList = () => {
112 95
           onChange={handleChangePage}
113 96
           sx={{ mx: 2 }}
114 97
         />
115
-      </Box>
98
+      </Box> */}
116 99
     </Box>
117 100
   );
118 101
 };

+ 62
- 56
src/components/ProductSuggestion/ProductSuggestion.jsx View File

@@ -1,71 +1,77 @@
1
-import React from 'react';
1
+import {useState, useEffect} from 'react';
2 2
 import { Box, Typography, Button } from '@mui/material';
3
+import { useSelector } from 'react-redux';
3 4
 
4 5
 import Grid from '@mui/material/Grid2';
5 6
 
6 7
 const ProductSuggestion = () => {
7 8
 
8
-  const images = [
9
-    {
10
-      url: 'https://via.placeholder.com/300',
11
-      title: 'KEMBOJA IN BEIGE',
12
-      price: 123.90,
13
-      description: "Atma Sari Kurta Raya Collection 2024",
14
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
15
-    },
16
-    {
17
-      url: 'https://via.placeholder.com/300',
18
-      title: 'KEMBOJA IN BEIGE',
19
-      price: 123.90,
20
-      description: "Atma Sari Kurta Raya Collection 2024",
21
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
22
-    },
23
-    {
24
-      url: 'https://via.placeholder.com/300',
25
-      title: 'KEMBOJA IN BEIGE',
26
-      price: 123.90,
27
-      description: "Atma Sari Kurta Raya Collection 2024",
28
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
29
-    },
30
-    {
31
-      url: 'https://via.placeholder.com/300',
32
-      title: 'KEMBOJA IN BEIGE',
33
-      price: 123.90,
34
-      description: "Atma Sari Kurta Raya Collection 2024",
35
-      extra_desc: "or 3 payments of 68.50 MYR with Atome"
9
+  const [suggestProducts, setSuggestProducts] = useState([]);
10
+  const products = useSelector((state) => state.products.productsHistory.data);
11
+
12
+  useEffect(() => {
13
+    if (products && products.length > 0) {
14
+      const getRandomProducts = (arr, num) => {
15
+        const shuffled = [...arr].sort(() => 0.5 - Math.random());
16
+        return shuffled.slice(0, num);
17
+      };
18
+
19
+      const randomProducts = getRandomProducts(products, 4); // Select 4 random elements
20
+      setSuggestProducts(randomProducts);
36 21
     }
37
-  ];
22
+  }, [products]);
23
+
24
+  const renderProduct = (id, img_url, title, price, currency) => {
25
+    return (
26
+      <Grid item size={{ xs: 12, md: 3 }}>
27
+        <Box
28
+          onClick={() => { window.location.href = `/products/${id}` }}
29
+          sx={{
30
+            overflow: 'hidden',
31
+            position: 'relative'
32
+          }}
33
+        >
34
+          <img
35
+            src={img_url}
36
+            alt={title}
37
+            style={{
38
+              width: '100%',
39
+              aspectRatio: '4 / 4',
40
+              objectPosition:"top center",
41
+              objectFit: 'cover',
42
+            }}
43
+          />
44
+
45
+          <Box sx={{ py: 5 }}>
46
+            <Typography variant="h5" sx={{ fontWeight: "bolder" }}>
47
+              {title}
48
+            </Typography>
49
+            <Typography variant="h6" >
50
+              {`${currency} ${parseFloat(price).toFixed(2)}`}
51
+            </Typography>
52
+          </Box>
53
+        </Box>
54
+      </Grid>
55
+    )
56
+  }
57
+
38 58
 
39 59
   return (
40 60
     <Box sx={{ mb: 5 }}>
41 61
       <Grid container spacing={1} columns={12}>
42
-        {images.map((image, index) => (<Grid item size={{xs:12, md:3}} key={index}>
43
-          <Box
44
-            sx={{
45
-              overflow: 'hidden',
46
-              position: 'relative'
47
-            }}
48
-          >
49
-            <img
50
-              src={image.url}
51
-              alt={image.title}
52
-              style={{
53
-                width: '100%',
54
-                aspectRatio: '4 / 4',
55
-                objectFit: 'cover',
56
-              }}
57
-            />
62
+        {suggestProducts.map((product, index) => {
63
+ 
64
+          let {id, title, compareAtPriceRange, images, collections} = product
65
+          let price = compareAtPriceRange.maxVariantPrice.amount
66
+          let currency = compareAtPriceRange.maxVariantPrice.currencyCode
67
+          let img_url = images.nodes[0].url
58 68
 
59
-            <Box sx={{ py: 5 }}>
60
-              <Typography variant="h5" sx={{ fontWeight: "bolder" }}>
61
-                {image.title}
62
-              </Typography>
63
-              <Typography variant="h6" >
64
-                {`RM ${image.price}`}
65
-              </Typography>
66
-            </Box>
67
-          </Box>
68
-        </Grid>))}
69
+          // ID
70
+          const parts = id.split('/');
71
+          let prodID = parts[parts.length - 1];
72
+
73
+          return renderProduct(prodID, img_url, title, price, currency)
74
+        })}
69 75
       </Grid>
70 76
     </Box>
71 77
   );

+ 22
- 0
src/pages/Products/Product.jsx View File

@@ -17,11 +17,33 @@ const Product = () => {
17 17
   let { pid } = useParams();
18 18
   const product = useSelector((state) => state.products.product.data)
19 19
   const dispatch = useDispatch();
20
+  
20 21
 
21 22
   useEffect(() => {
22 23
 
23 24
     dispatch(fetchProduct(pid))
24 25
 
26
+    if(localStorage.getItem('amber-product-history')){
27
+
28
+      let productHistory = JSON.parse(localStorage.getItem('amber-product-history'))
29
+      
30
+      if(productHistory.includes(pid)) return
31
+
32
+      productHistory = [pid, ...productHistory]
33
+      
34
+      productHistory = productHistory.slice(0, 6)
35
+  
36
+      productHistory = JSON.stringify(productHistory)
37
+
38
+      localStorage.setItem('amber-product-history', productHistory)
39
+    } else {
40
+
41
+      let productHistory = [pid]
42
+      productHistory = JSON.stringify(productHistory)
43
+      localStorage.setItem('amber-product-history', productHistory)
44
+
45
+    }
46
+
25 47
   }, [])
26 48
 
27 49
   return (

+ 29
- 1
src/redux/slices/productSlice.js View File

@@ -12,7 +12,11 @@ const initialState = {
12 12
       status: 'idle',
13 13
       error: null,
14 14
     },
15
-    productsHistory:[],
15
+    productsHistory:{
16
+      data: [],
17
+      status: 'idle',
18
+      error: null,
19
+    },
16 20
     productsSelected:[],
17 21
     ProductsSuggestion:[]
18 22
 };
@@ -36,6 +40,17 @@ export const fetchProducts = createAsyncThunk(
36 40
   }
37 41
 )
38 42
 
43
+export const fetchProductsHistory = createAsyncThunk(
44
+  'product/fetchProductsHistory',
45
+  async (productIDList) => {
46
+
47
+    const idsQuery = productIDList.map(id => `id:${id}`).join(" OR ");
48
+    const response = await ProductService.getProducts(6, "TITLE", idsQuery)
49
+    return response
50
+
51
+  }
52
+)
53
+
39 54
 export const productSlice = createSlice({
40 55
   name: 'product',
41 56
   initialState,
@@ -67,6 +82,19 @@ export const productSlice = createSlice({
67 82
         state.product.data = {};
68 83
         state.product.error = action.error.message;
69 84
       })
85
+      .addCase(fetchProductsHistory.pending, (state) => {
86
+        state.productsHistory.status = 'loading';
87
+        state.productsHistory.error = null;
88
+      })
89
+      .addCase(fetchProductsHistory.fulfilled, (state, action) => {
90
+        state.productsHistory.status = 'succeeded';
91
+        state.productsHistory.data = action.payload
92
+      })
93
+      .addCase(fetchProductsHistory.rejected, (state, action) => {
94
+        state.productsHistory.status = 'failed';
95
+        state.productsHistory.data = [];
96
+        state.productsHistory.error = action.error.message;
97
+      })
70 98
   }
71 99
 });
72 100
 

Loading…
Cancel
Save