Bläddra i källkod

cart API

master
azri 1 dag sedan
förälder
incheckning
4642ba9b03

+ 19
- 2
src/App.js Visa fil

@@ -8,21 +8,38 @@ import Products from './pages/Products';
8 8
 import Product from './pages/Products/Product';
9 9
 import Checkout from './pages/Checkout';
10 10
 import Navbar from './components/Navbar';
11
+import Footer from './components/Footer/Footer';
11 12
 import Header from './components/Header';
12 13
 import theme from './theme/theme';
13 14
 import { ThemeProvider } from '@mui/material';
14
-import Footer from './components/Footer/Footer';
15
+import { fetchCart } from './redux/slices/cartSlice';
16
+import { useSelector, useDispatch } from 'react-redux';
17
+
18
+const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
15 19
 
16 20
 function App() {
17 21
 
22
+  const dispatch = useDispatch()
23
+
18 24
   useEffect(() => {
19 25
 
26
+    //Check if cart exist
27
+    let cartHistory = localStorage.getItem('amber-cart');
28
+    cartHistory = cartHistory ? JSON.parse(cartHistory) : {};
29
+
30
+    // if we got cart, then just fetch from BE based on ID
31
+    if (!isEmptyObject(cartHistory)) {
32
+
33
+      dispatch(fetchCart(cartHistory.id));
34
+
35
+    }
36
+
20 37
   }, [])
21 38
 
22 39
   return (
23 40
     <ThemeProvider theme={theme}>
24 41
       <Header />
25
-      <Navbar/>
42
+      <Navbar />
26 43
       <BrowserRouter>
27 44
         <Routes>
28 45
           <Route path='/' element={<Home />} />

+ 1
- 1
src/components/Navbar/Navbar.jsx Visa fil

@@ -228,7 +228,7 @@ const Navbar = () => {
228 228
               <SearchIcon sx={{ color: "white" }} />
229 229
             </IconButton>
230 230
 
231
-            <Badge badgeContent={1} color="primary">
231
+            <Badge sx={{cursor:"pointer"}} onClick={()=>{ window.location.href = "/cart" }} badgeContent={1} color="primary">
232 232
               <LocalMallIcon color="action" sx={{ color: "white" }} />
233 233
             </Badge>
234 234
 

+ 48
- 10
src/components/ProductDetails/ProductDetails.jsx Visa fil

@@ -1,5 +1,4 @@
1 1
 import { useState } from "react";
2
-import { useSelector } from "react-redux";
3 2
 import {
4 3
   Box,
5 4
   Typography,
@@ -8,12 +7,52 @@ import {
8 7
 } from "@mui/material";
9 8
 import AddIcon from '@mui/icons-material/Add';
10 9
 import RemoveIcon from '@mui/icons-material/Remove';
10
+import { useSelector, useDispatch } from "react-redux";
11
+import { createCart } from "../../redux/slices/cartSlice";
12
+
13
+// Utility function to check if an object is empty
14
+const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
11 15
 
12 16
 const ProductDetails = () => {
13 17
 
18
+  const dispatch = useDispatch()
14 19
   const product = useSelector((state) => state.products.product.data)
15
-  const [quantity, setQuantity] = useState(1);
16
-  const [selectedSize, setSelectedSize] = useState(null);
20
+  const cart = useSelector((state) => state.cart.cart)
21
+
22
+  const [quantity, setQuantity] = useState(1)
23
+  const [selectedSize, setSelectedSize] = useState(null)
24
+
25
+  const handleCart = () => {
26
+
27
+    let cartHistory = localStorage.getItem('amber-cart');
28
+    cartHistory = cartHistory ? JSON.parse(cartHistory) : {};
29
+
30
+    // if we got no cart, then create a new one
31
+    if (isEmptyObject(cart) || isEmptyObject(cartHistory)) {
32
+
33
+      dispatch(createCart());
34
+      
35
+    } else {
36
+
37
+      // // Update cart content
38
+      // const existingItemIndex = cartHistory.lines?.findIndex(
39
+      //   (line) => line.merchandiseId === item.merchandiseId
40
+      // );
41
+
42
+      // if (existingItemIndex !== -1) {
43
+      //   // If the item exists, update the quantity
44
+      //   cartHistory.lines[existingItemIndex].quantity += item.quantity;
45
+      // } else {
46
+      //   // If the item doesn't exist, add it
47
+      //   cartHistory.lines = [...(cartHistory.lines || []), item];
48
+      // }
49
+
50
+      // // Update Redux store and localStorage
51
+      // dispatch(updateCart(cartHistory)); // Redux action to update cart
52
+      // localStorage.setItem('amber-cart', JSON.stringify(cartHistory));
53
+    }
54
+
55
+  }
17 56
 
18 57
   const handleIncrement = () => {
19 58
     setQuantity((prevQuantity) => prevQuantity + 1);
@@ -25,7 +64,6 @@ const ProductDetails = () => {
25 64
 
26 65
   const handleSizeClick = (size) => {
27 66
     setSelectedSize(size);
28
-    // alert(`Selected size: ${size}`);
29 67
   };
30 68
 
31 69
   return (
@@ -60,7 +98,7 @@ const ProductDetails = () => {
60 98
                 key={size}
61 99
                 variant={selectedSize === size ? "contained" : "outlined"}
62 100
                 color={selectedSize === size ? "primary" : "primary"}
63
-                sx={{ color: "#000" }}
101
+                sx={{ color: selectedSize === size ? "#FFF" : "#000" }}
64 102
                 onClick={() => handleSizeClick(size)}
65 103
               >
66 104
                 {size}
@@ -79,7 +117,7 @@ const ProductDetails = () => {
79 117
             <Button
80 118
               variant="contained"
81 119
               color="primary"
82
-              sx={{width:"35px"}}
120
+              sx={{ width: "35px" }}
83 121
               onClick={handleDecrement}
84 122
             >
85 123
               <RemoveIcon />
@@ -89,14 +127,14 @@ const ProductDetails = () => {
89 127
               value={quantity}
90 128
               inputProps={{ readOnly: true, style: { textAlign: 'center' } }}
91 129
               size="small"
92
-              sx={{width:"100px"}}
130
+              sx={{ width: "100px" }}
93 131
               variant="outlined"
94 132
             />
95 133
 
96 134
             <Button
97 135
               variant="contained"
98 136
               color="primary"
99
-              sx={{width:"35px"}}
137
+              sx={{ width: "35px" }}
100 138
               onClick={handleIncrement}
101 139
             >
102 140
               <AddIcon />
@@ -115,7 +153,7 @@ const ProductDetails = () => {
115 153
         </Box>
116 154
 
117 155
         <Button
118
-          onClick={() => { window.location.href = "/cart" }}
156
+          onClick={() => { handleCart() }}
119 157
           variant="contained"
120 158
           color="common.black"
121 159
           fullWidth
@@ -129,7 +167,7 @@ const ProductDetails = () => {
129 167
             },
130 168
           }}
131 169
         >
132
-          BUY NOW
170
+          ADD TO CART
133 171
         </Button>
134 172
       </Box>
135 173
 

+ 3
- 1
src/components/ProductHistoryList/ProductHistoryList.jsx Visa fil

@@ -22,7 +22,9 @@ const ProductHistoryList = () => {
22 22
 
23 23
   }, [])
24 24
 
25
-  useEffect(()=>{ console.log(products) }, [products])
25
+  // useEffect(()=>{ 
26
+  //   console.log(products) 
27
+  // }, [products])
26 28
 
27 29
   const renderProduct = (id, img_url, title, price, currency) => {
28 30
 

+ 2
- 1
src/components/ProductSuggestion/ProductSuggestion.jsx Visa fil

@@ -28,7 +28,8 @@ const ProductSuggestion = () => {
28 28
           onClick={() => { window.location.href = `/products/${id}` }}
29 29
           sx={{
30 30
             overflow: 'hidden',
31
-            position: 'relative'
31
+            position: 'relative',
32
+            cursor: 'pointer'
32 33
           }}
33 34
         >
34 35
           <img

+ 1
- 2
src/pages/Products/Product.jsx Visa fil

@@ -30,12 +30,11 @@ const Product = () => {
30 30
       if(productHistory.includes(pid)) return
31 31
 
32 32
       productHistory = [pid, ...productHistory]
33
-      
34 33
       productHistory = productHistory.slice(0, 6)
35
-  
36 34
       productHistory = JSON.stringify(productHistory)
37 35
 
38 36
       localStorage.setItem('amber-product-history', productHistory)
37
+
39 38
     } else {
40 39
 
41 40
       let productHistory = [pid]

+ 148
- 0
src/redux/slices/cartSlice.js Visa fil

@@ -0,0 +1,148 @@
1
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2
+import CartService from '../../services/CartService';
3
+
4
+const initialState = {
5
+  cart: {},
6
+  status: null,
7
+  error: null,
8
+};
9
+
10
+// Async Thunks
11
+export const createCart = createAsyncThunk(
12
+  'cart/createCart',
13
+  async () => {
14
+    const response = await CartService.createCart();
15
+    return response;
16
+  }
17
+);
18
+
19
+export const addItemToCart = createAsyncThunk(
20
+  'cart/addItemToCart',
21
+  async ({ cartId, lines }, { rejectWithValue }) => {
22
+    try {
23
+      const response = await CartService.addItemToCart(cartId, lines);
24
+      return response;
25
+    } catch (error) {
26
+      return rejectWithValue(error.message);
27
+    }
28
+  }
29
+);
30
+
31
+export const fetchCart = createAsyncThunk(
32
+  'cart/fetchCart',
33
+  async (cartId, { rejectWithValue }) => {
34
+    try {
35
+      const response = await CartService.getCart(cartId);
36
+      return response;
37
+    } catch (error) {
38
+      return rejectWithValue(error.message);
39
+    }
40
+  }
41
+);
42
+
43
+export const updateItemQuantity = createAsyncThunk(
44
+  'cart/updateItemQuantity',
45
+  async ({ cartId, lineId, quantity }, { rejectWithValue }) => {
46
+    try {
47
+      const response = await CartService.updateItemQuantity(cartId, lineId, quantity);
48
+      return response;
49
+    } catch (error) {
50
+      return rejectWithValue(error.message);
51
+    }
52
+  }
53
+);
54
+
55
+export const fetchCheckoutUrl = createAsyncThunk(
56
+  'cart/fetchCheckoutUrl',
57
+  async (cartId, { rejectWithValue }) => {
58
+    try {
59
+      const response = await CartService.getCheckoutUrl(cartId);
60
+      return response;
61
+    } catch (error) {
62
+      return rejectWithValue(error.message);
63
+    }
64
+  }
65
+);
66
+
67
+// Slice
68
+export const cartSlice = createSlice({
69
+  name: 'cart',
70
+  initialState,
71
+  reducers: {},
72
+  extraReducers: (builder) => {
73
+    builder
74
+      // Create Cart
75
+      .addCase(createCart.pending, (state) => {
76
+        state.status = 'loading';
77
+        state.error = null;
78
+      })
79
+      .addCase(createCart.fulfilled, (state, action) => {
80
+        state.status = 'succeeded';
81
+        state.cart = action.payload;
82
+        localStorage.setItem('amber-cart', JSON.stringify(action.payload));
83
+      })
84
+      .addCase(createCart.rejected, (state, action) => {
85
+        state.status = 'failed';
86
+        state.cart = {};
87
+        state.error = action.payload || action.error.message;
88
+      })
89
+
90
+      // Add Item to Cart
91
+      .addCase(addItemToCart.pending, (state) => {
92
+        state.status = 'loading';
93
+        state.error = null;
94
+      })
95
+      .addCase(addItemToCart.fulfilled, (state, action) => {
96
+        state.status = 'succeeded';
97
+        state.cart = action.payload;
98
+      })
99
+      .addCase(addItemToCart.rejected, (state, action) => {
100
+        state.status = 'failed';
101
+        state.error = action.payload || action.error.message;
102
+      })
103
+
104
+      // Fetch Cart
105
+      .addCase(fetchCart.pending, (state) => {
106
+        state.status = 'loading';
107
+        state.error = null;
108
+      })
109
+      .addCase(fetchCart.fulfilled, (state, action) => {
110
+        state.status = 'succeeded';
111
+        state.cart = action.payload;
112
+      })
113
+      .addCase(fetchCart.rejected, (state, action) => {
114
+        state.status = 'failed';
115
+        state.error = action.payload || action.error.message;
116
+      })
117
+
118
+      // Update Item Quantity
119
+      .addCase(updateItemQuantity.pending, (state) => {
120
+        state.status = 'loading';
121
+        state.error = null;
122
+      })
123
+      .addCase(updateItemQuantity.fulfilled, (state, action) => {
124
+        state.status = 'succeeded';
125
+        state.cart = action.payload;
126
+      })
127
+      .addCase(updateItemQuantity.rejected, (state, action) => {
128
+        state.status = 'failed';
129
+        state.error = action.payload || action.error.message;
130
+      })
131
+
132
+      // Fetch Checkout URL
133
+      .addCase(fetchCheckoutUrl.pending, (state) => {
134
+        state.status = 'loading';
135
+        state.error = null;
136
+      })
137
+      .addCase(fetchCheckoutUrl.fulfilled, (state, action) => {
138
+        state.status = 'succeeded';
139
+        state.cart.checkoutUrl = action.payload; // Only update checkout URL
140
+      })
141
+      .addCase(fetchCheckoutUrl.rejected, (state, action) => {
142
+        state.status = 'failed';
143
+        state.error = action.payload || action.error.message;
144
+      });
145
+  },
146
+});
147
+
148
+export default cartSlice.reducer;

+ 2
- 0
src/redux/store.js Visa fil

@@ -1,9 +1,11 @@
1 1
 import { configureStore } from '@reduxjs/toolkit';
2 2
 import productsReducer from './slices/productSlice';
3
+import cartReducer from './slices/cartSlice'
3 4
 
4 5
 const store = configureStore({
5 6
   reducer: {
6 7
     products: productsReducer,
8
+    cart: cartReducer
7 9
   },
8 10
 });
9 11
 

+ 171
- 0
src/services/CartService.js Visa fil

@@ -0,0 +1,171 @@
1
+import { createStorefrontApiClient } from '@shopify/storefront-api-client';
2
+import { REACT_APP_ACCESS_TOKEN, REACT_APP_SHOP_NAME } from '../utils/httpCommon';
3
+
4
+const client = createStorefrontApiClient({
5
+  storeDomain: `https://${REACT_APP_SHOP_NAME}/api/2024-10/graphql.json`,
6
+  apiVersion: '2024-10',
7
+  publicAccessToken: REACT_APP_ACCESS_TOKEN,
8
+});
9
+
10
+// Create a cart
11
+const createCart = async () => {
12
+  const query = `
13
+    mutation CreateCart {
14
+      cartCreate {
15
+        cart {
16
+          id
17
+          createdAt
18
+          updatedAt
19
+        }
20
+        userErrors {
21
+          field
22
+          message
23
+        }
24
+      }
25
+    }
26
+  `;
27
+  const { data } = await client.request(query);
28
+  return data.cartCreate.cart;
29
+};
30
+
31
+// Add a line item to the cart
32
+const addItemToCart = async (cartId, lines) => {
33
+  const query = `
34
+    mutation AddItemToCart($cartId: ID!, $lines: [CartLineInput!]!) {
35
+      cartLinesAdd(cartId: $cartId, lines: $lines) {
36
+        cart {
37
+          id
38
+          lines(first: 10) {
39
+            edges {
40
+              node {
41
+                id
42
+                quantity
43
+                merchandise {
44
+                  ... on ProductVariant {
45
+                    id
46
+                    title
47
+                  }
48
+                }
49
+              }
50
+            }
51
+          }
52
+        }
53
+        userErrors {
54
+          field
55
+          message
56
+        }
57
+      }
58
+    }
59
+  `;
60
+  const variables = { cartId, lines };
61
+  const { data } = await client.request(query, variables);
62
+  return data.cartLinesAdd.cart;
63
+};
64
+
65
+// Get cart details
66
+const getCart = async (cartId) => {
67
+  const query = `
68
+    query GetCart($cartId: ID!) {
69
+      cart(id: $cartId) {
70
+        id
71
+        createdAt
72
+        updatedAt
73
+        lines(first: 10) {
74
+          edges {
75
+            node {
76
+              id
77
+              quantity
78
+              merchandise {
79
+                ... on ProductVariant {
80
+                  id
81
+                  title
82
+                }
83
+              }
84
+            }
85
+          }
86
+        }
87
+        cost {
88
+          subtotalAmount {
89
+            amount
90
+            currencyCode
91
+          }
92
+          totalAmount {
93
+            amount
94
+            currencyCode
95
+          }
96
+          totalTaxAmount {
97
+            amount
98
+            currencyCode
99
+          }
100
+        }
101
+      }
102
+    }
103
+  `;
104
+
105
+   
106
+  const variables = { cartId };
107
+  const { data } = await client.request(query, variables);
108
+  debugger
109
+  return data.cart;
110
+};
111
+
112
+// Update a line item's quantity
113
+const updateItemQuantity = async (cartId, lineId, quantity) => {
114
+  const query = `
115
+    mutation UpdateItemQuantity($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
116
+      cartLinesUpdate(cartId: $cartId, lines: $lines) {
117
+        cart {
118
+          id
119
+          lines(first: 10) {
120
+            edges {
121
+              node {
122
+                id
123
+                quantity
124
+                merchandise {
125
+                  ... on ProductVariant {
126
+                    id
127
+                  }
128
+                }
129
+              }
130
+            }
131
+          }
132
+        }
133
+        userErrors {
134
+          field
135
+          message
136
+        }
137
+      }
138
+    }
139
+  `;
140
+  const variables = {
141
+    cartId,
142
+    lines: [{ id: lineId, quantity }],
143
+  };
144
+  const { data } = await client.request(query, variables);
145
+  return data.cartLinesUpdate.cart;
146
+};
147
+
148
+// Generate checkout URL
149
+const getCheckoutUrl = async (cartId) => {
150
+  const query = `
151
+    query CheckoutURL($cartId: ID!) {
152
+      cart(id: $cartId) {
153
+        checkoutUrl
154
+      }
155
+    }
156
+  `;
157
+  const variables = { cartId };
158
+  const { data } = await client.request(query, variables);
159
+  return data.cart.checkoutUrl;
160
+};
161
+
162
+// Export services
163
+const CartService = {
164
+  createCart,
165
+  addItemToCart,
166
+  getCart,
167
+  updateItemQuantity,
168
+  getCheckoutUrl,
169
+};
170
+
171
+export default CartService;

Laddar…
Avbryt
Spara