Browse Source

handle 0 cart

master
azri 2 weeks ago
parent
commit
9812f5b1be

+ 5
- 1
src/App.js View File

12
 import Header from './components/Header';
12
 import Header from './components/Header';
13
 import theme from './theme/theme';
13
 import theme from './theme/theme';
14
 import { ThemeProvider } from '@mui/material';
14
 import { ThemeProvider } from '@mui/material';
15
-import { fetchCart } from './redux/slices/cartSlice';
15
+import { fetchCart, createCart } from './redux/slices/cartSlice';
16
 import { useSelector, useDispatch } from 'react-redux';
16
 import { useSelector, useDispatch } from 'react-redux';
17
 
17
 
18
 const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
18
 const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
32
 
32
 
33
       dispatch(fetchCart(cartHistory.id));
33
       dispatch(fetchCart(cartHistory.id));
34
 
34
 
35
+    }else{
36
+
37
+      dispatch(createCart())
38
+
35
     }
39
     }
36
 
40
 
37
   }, [])
41
   }, [])

+ 10
- 9
src/components/Navbar/Navbar.jsx View File

10
 import AccountCircleIcon from "@mui/icons-material/AccountCircle";
10
 import AccountCircleIcon from "@mui/icons-material/AccountCircle";
11
 import logoSrc from "../../assets/svg/logo.svg";
11
 import logoSrc from "../../assets/svg/logo.svg";
12
 import { styled } from '@mui/material/styles';
12
 import { styled } from '@mui/material/styles';
13
-import { MenuItem, Select, FormControl, Badge } from '@mui/material';
13
+import { MenuItem, Select, FormControl, Badge, Typography } from '@mui/material';
14
 import MobileNav from "./components/MobileNav";
14
 import MobileNav from "./components/MobileNav";
15
 import MenuIcon from "@mui/icons-material/Menu";
15
 import MenuIcon from "@mui/icons-material/Menu";
16
 import CategoryIcon from '@mui/icons-material/Category';
16
 import CategoryIcon from '@mui/icons-material/Category';
17
 import HomeIcon from '@mui/icons-material/Home';
17
 import HomeIcon from '@mui/icons-material/Home';
18
 import BrushIcon from '@mui/icons-material/Brush';
18
 import BrushIcon from '@mui/icons-material/Brush';
19
 import LoyaltyIcon from '@mui/icons-material/Loyalty';
19
 import LoyaltyIcon from '@mui/icons-material/Loyalty';
20
+import Grid from '@mui/material/Grid2';
20
 import { useSelector } from 'react-redux';
21
 import { useSelector } from 'react-redux';
21
 
22
 
22
 function calculateTotalQuantity(cartItems) {
23
 function calculateTotalQuantity(cartItems) {
83
   const [showHeader, setShowHeader] = useState(true);
84
   const [showHeader, setShowHeader] = useState(true);
84
   const [lastScrollPos, setLastScrollPos] = useState(0);
85
   const [lastScrollPos, setLastScrollPos] = useState(0);
85
   const [language, setLanguage] = useState('English');
86
   const [language, setLanguage] = useState('English');
86
-  
87
+
87
   const cart = useSelector((state) => state.cart.cart)
88
   const cart = useSelector((state) => state.cart.cart)
88
   const [cartAmount, setCartAmount] = useState(0);
89
   const [cartAmount, setCartAmount] = useState(0);
89
 
90
 
90
   const [open, setOpen] = React.useState(false);
91
   const [open, setOpen] = React.useState(false);
91
-  
92
+
92
   useEffect(() => {
93
   useEffect(() => {
93
-    
94
-    if(!cart?.lines?.nodes) return // don't need to do anything if we have no cart data
94
+
95
+    if (!cart?.lines?.nodes) return // don't need to do anything if we have no cart data
95
 
96
 
96
     setCartAmount(calculateTotalQuantity(cart?.lines?.nodes))
97
     setCartAmount(calculateTotalQuantity(cart?.lines?.nodes))
97
 
98
 
98
   }, [cart])
99
   }, [cart])
99
-  
100
+
100
 
101
 
101
 
102
 
102
   const handleChange = (event) => {
103
   const handleChange = (event) => {
126
         sx={{
127
         sx={{
127
           backgroundColor: {
128
           backgroundColor: {
128
             xs: showHeader ? "rgba(0,0,0,0)" : "background.black",// somehow the logic for scrolling header is the same so I just gonna use showHeader state
129
             xs: showHeader ? "rgba(0,0,0,0)" : "background.black",// somehow the logic for scrolling header is the same so I just gonna use showHeader state
129
-            md: "rgba(0,0,0,0.9)", 
130
+            md: "rgba(0,0,0,0.9)",
130
             lg: "background.black"
131
             lg: "background.black"
131
           },
132
           },
132
           boxShadow: {
133
           boxShadow: {
246
               <SearchIcon sx={{ color: "white" }} />
247
               <SearchIcon sx={{ color: "white" }} />
247
             </IconButton>
248
             </IconButton>
248
 
249
 
249
-            <Badge sx={{cursor:"pointer"}} onClick={()=>{ window.location.href = "/cart" }} badgeContent={cartAmount} color="primary">
250
+            <Badge sx={{ cursor: "pointer" }} onClick={() => { window.location.href = "/cart" }} badgeContent={cartAmount} color="primary">
250
               <LocalMallIcon color="action" sx={{ color: "white" }} />
251
               <LocalMallIcon color="action" sx={{ color: "white" }} />
251
             </Badge>
252
             </Badge>
252
 
253
 
258
         </Toolbar>
259
         </Toolbar>
259
       </AppBar>
260
       </AppBar>
260
 
261
 
261
-      <MobileNav open={open} menu={navItem} onClose={()=>{setOpen(false)}} />
262
+      <MobileNav open={open} menu={navItem} onClose={() => { setOpen(false) }} />
262
     </>
263
     </>
263
   );
264
   );
264
 };
265
 };

+ 18
- 0
src/components/ProductDetails/ProductDetails.jsx View File

320
         >
320
         >
321
           ADD TO CART {showLoader && <CircularProgress sx={{ml:1}} color="white" size={20} />} 
321
           ADD TO CART {showLoader && <CircularProgress sx={{ml:1}} color="white" size={20} />} 
322
         </Button>
322
         </Button>
323
+
324
+        { cart?.lines?.nodes?.length > 0 && <Button
325
+          onClick={() => { window.location.href = '/cart' }}
326
+          variant="contained"
327
+          color="common.black"
328
+          fullWidth
329
+          disabled={showLoader}
330
+          sx={{
331
+            backgroundColor: (theme) => theme.palette.primary.main,
332
+            color: "white",
333
+            textTransform: "none",
334
+            "&:hover": {
335
+              backgroundColor: (theme) => theme.palette.grey[900],
336
+            },
337
+          }}
338
+        >
339
+          PAY NOW
340
+        </Button>}
323
       </Box>
341
       </Box>
324
       {alert.open && (
342
       {alert.open && (
325
         <Alert
343
         <Alert

+ 42
- 10
src/pages/Cart.jsx View File

12
 import TableRow from '@mui/material/TableRow';
12
 import TableRow from '@mui/material/TableRow';
13
 import AddIcon from '@mui/icons-material/Add';
13
 import AddIcon from '@mui/icons-material/Add';
14
 import RemoveIcon from '@mui/icons-material/Remove';
14
 import RemoveIcon from '@mui/icons-material/Remove';
15
+import { addItemToCart, updateItemQuantity } from '../redux/slices/cartSlice';
15
 import { useSelector, useDispatch } from 'react-redux';
16
 import { useSelector, useDispatch } from 'react-redux';
16
 
17
 
18
+
19
+// Utility function to check if an object is empty
20
+const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
21
+
17
 const Cart = () => {
22
 const Cart = () => {
18
 
23
 
19
   const dispatch = useDispatch()
24
   const dispatch = useDispatch()
27
   }, [])
32
   }, [])
28
 
33
 
29
   useEffect(() => {
34
   useEffect(() => {
35
+
36
+    if(cart?.lines?.nodes?.length == 0) window.location.href = "/"
37
+
30
     setCartProducts(cart?.lines?.nodes || [])
38
     setCartProducts(cart?.lines?.nodes || [])
39
+
31
   }, [cart])
40
   }, [cart])
32
 
41
 
42
+  const handleUpdateCart = ({ quantity,  lineId}) => {
43
+
44
+    let cartHistory = localStorage.getItem('amber-cart');
45
+    cartHistory = cartHistory ? JSON.parse(cartHistory) : {};
46
+
47
+    dispatch(updateItemQuantity({
48
+      cartId:cartHistory.id,
49
+      lineId,
50
+      quantity,
51
+    }))
52
+
53
+  }
54
+
33
 
55
 
34
   return (
56
   return (
35
     <>
57
     <>
83
               </TableRow>
105
               </TableRow>
84
             </TableHead>
106
             </TableHead>
85
             <TableBody>
107
             <TableBody>
86
-              {cartProducts?.map(({ cost, merchandise, quantity }) => {
87
-                
88
-                let {amount, currencyCode} = cost.totalAmount
89
-                let {id, image, product, title } = merchandise // the title here is actually the variant title name
108
+              {cartProducts?.map(({ id, cost, merchandise, quantity }) => {
109
+
110
+                let { amount, currencyCode } = cost.totalAmount
111
+                let { image, product, title } = merchandise
90
 
112
 
91
                 return (
113
                 return (
92
                   <TableRow
114
                   <TableRow
105
                           height: 100,
127
                           height: 100,
106
                           aspectRatio: '4 / 4',
128
                           aspectRatio: '4 / 4',
107
                           objectFit: 'cover',
129
                           objectFit: 'cover',
108
-                          objectPosition:"top center"
130
+                          objectPosition: "top center"
109
                         }}
131
                         }}
110
                       />
132
                       />
111
                     </TableCell>
133
                     </TableCell>
112
                     <TableCell>
134
                     <TableCell>
113
                       <Typography variant='body2' sx={{ fontWeight: "bold", mb: 1 }}>{product.title}</Typography>
135
                       <Typography variant='body2' sx={{ fontWeight: "bold", mb: 1 }}>{product.title}</Typography>
114
-                      
115
-                      {product?.collections?.nodes?.map(({title}) => (<Typography variant='body2'>{title}</Typography>) )}
136
+
137
+                      {product?.collections?.nodes?.map(({ title }) => (<Typography variant='body2'>{title}</Typography>))}
116
 
138
 
117
                     </TableCell>
139
                     </TableCell>
118
                     <TableCell align='center'>
140
                     <TableCell align='center'>
120
                     </TableCell>
142
                     </TableCell>
121
                     <TableCell align='center'>
143
                     <TableCell align='center'>
122
                       <Box sx={{ display: "flex", justifyContent: "center" }}>
144
                       <Box sx={{ display: "flex", justifyContent: "center" }}>
123
-                        <IconButton>
145
+
146
+                        <IconButton onClick={() => {
147
+
148
+                          handleUpdateCart({ quantity: quantity - 1, lineId:id })
149
+                        }}>
124
                           <RemoveIcon sx={{ fontSize: 16, margin: "0 15px" }} />
150
                           <RemoveIcon sx={{ fontSize: 16, margin: "0 15px" }} />
125
                         </IconButton>
151
                         </IconButton>
152
+
126
                         <p style={{ fontSize: 20, fontWeight: "bold" }}>{quantity}</p>
153
                         <p style={{ fontSize: 20, fontWeight: "bold" }}>{quantity}</p>
127
-                        <IconButton >
154
+
155
+                        <IconButton onClick={() => {
156
+
157
+                          handleUpdateCart({ quantity: quantity + 1, lineId:id })
158
+                        }}>
128
                           <AddIcon sx={{ fontSize: 16, margin: "0 15px" }} />
159
                           <AddIcon sx={{ fontSize: 16, margin: "0 15px" }} />
129
                         </IconButton>
160
                         </IconButton>
161
+
130
                       </Box>
162
                       </Box>
131
                     </TableCell>
163
                     </TableCell>
132
                     <TableCell align='center'>
164
                     <TableCell align='center'>
197
             <Button
229
             <Button
198
               variant="contained"
230
               variant="contained"
199
               color="common.black"
231
               color="common.black"
200
-              onClick={() => { window.location.href = "/checkout" }}
232
+              onClick={() => { window.location.href = cart.checkoutUrl }}
201
               sx={{
233
               sx={{
202
                 backgroundColor: (theme) => theme.palette.common.black,
234
                 backgroundColor: (theme) => theme.palette.common.black,
203
                 color: "white",
235
                 color: "white",

+ 4
- 1
src/redux/slices/cartSlice.js View File

108
       })
108
       })
109
       .addCase(fetchCart.fulfilled, (state, action) => {
109
       .addCase(fetchCart.fulfilled, (state, action) => {
110
         state.status = 'succeeded';
110
         state.status = 'succeeded';
111
-        localStorage.setItem('amber-cart', JSON.stringify(action.payload));
111
+        if(action.payload) localStorage.setItem('amber-cart', JSON.stringify(action.payload))
112
+        else localStorage.removeItem('amber-cart')
113
+        
112
         state.cart = action.payload;
114
         state.cart = action.payload;
113
       })
115
       })
114
       .addCase(fetchCart.rejected, (state, action) => {
116
       .addCase(fetchCart.rejected, (state, action) => {
123
       })
125
       })
124
       .addCase(updateItemQuantity.fulfilled, (state, action) => {
126
       .addCase(updateItemQuantity.fulfilled, (state, action) => {
125
         state.status = 'succeeded';
127
         state.status = 'succeeded';
128
+        localStorage.setItem('amber-cart', JSON.stringify(action.payload));
126
         state.cart = action.payload;
129
         state.cart = action.payload;
127
       })
130
       })
128
       .addCase(updateItemQuantity.rejected, (state, action) => {
131
       .addCase(updateItemQuantity.rejected, (state, action) => {

+ 56
- 14
src/services/CartService.js View File

32
       cartLinesAdd(cartId: $cartId, lines: $lines) {
32
       cartLinesAdd(cartId: $cartId, lines: $lines) {
33
         cart {
33
         cart {
34
           id
34
           id
35
+          checkoutUrl
35
           createdAt
36
           createdAt
36
           updatedAt
37
           updatedAt
37
           cost {
38
           cost {
48
               currencyCode
49
               currencyCode
49
             }
50
             }
50
           }
51
           }
51
-          lines(first: 10) {
52
+          lines(first: 99) {
52
             nodes {
53
             nodes {
53
               id
54
               id
54
               merchandise {
55
               merchandise {
104
     {
105
     {
105
       cart(id: "${cartId}") {
106
       cart(id: "${cartId}") {
106
         id
107
         id
108
+        checkoutUrl
107
         createdAt
109
         createdAt
108
         updatedAt
110
         updatedAt
109
         cost {
111
         cost {
165
 
167
 
166
 // Update a line item's quantity
168
 // Update a line item's quantity
167
 const updateItemQuantity = async (cartId, lineId, quantity) => {
169
 const updateItemQuantity = async (cartId, lineId, quantity) => {
170
+
171
+  
168
   const query = `
172
   const query = `
169
     mutation UpdateItemQuantity($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
173
     mutation UpdateItemQuantity($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
170
       cartLinesUpdate(cartId: $cartId, lines: $lines) {
174
       cartLinesUpdate(cartId: $cartId, lines: $lines) {
171
         cart {
175
         cart {
172
           id
176
           id
173
-          lines(first: 10) {
174
-            edges {
175
-              node {
176
-                id
177
-                quantity
178
-                merchandise {
179
-                  ... on ProductVariant {
180
-                    id
177
+          checkoutUrl
178
+          createdAt
179
+          updatedAt
180
+          cost {
181
+            totalAmount {
182
+              amount
183
+              currencyCode
184
+            }
185
+            subtotalAmount {
186
+              amount
187
+              currencyCode
188
+            }
189
+            totalTaxAmount {
190
+              amount
191
+              currencyCode
192
+            }
193
+          }
194
+          lines(first: 99) {
195
+            nodes {
196
+              id
197
+              merchandise {
198
+                ... on ProductVariant {
199
+                  id
200
+                  title
201
+                  selectedOptions {
202
+                    name
203
+                    value
204
+                  }
205
+                  image {
206
+                    src
207
+                  }
208
+                  product {
209
+                    title
210
+                    collections(first: 5) {
211
+                      nodes {
212
+                        title
213
+                      }
214
+                    }
181
                   }
215
                   }
182
                 }
216
                 }
183
               }
217
               }
218
+              quantity
219
+              cost {
220
+                totalAmount {
221
+                  amount
222
+                  currencyCode
223
+                }
224
+              }
184
             }
225
             }
185
           }
226
           }
186
         }
227
         }
187
-        userErrors {
188
-          field
189
-          message
190
-        }
191
       }
228
       }
192
     }
229
     }
193
   `;
230
   `;
195
     cartId,
232
     cartId,
196
     lines: [{ id: lineId, quantity }],
233
     lines: [{ id: lineId, quantity }],
197
   };
234
   };
198
-  const { data } = await client.request(query, variables);
235
+  const {data, errors, extensions} = await client.request(query,{
236
+    variables: {
237
+      cartId,
238
+      lines: [{ id: lineId, quantity }]
239
+    }
240
+  });
199
   return data.cartLinesUpdate.cart;
241
   return data.cartLinesUpdate.cart;
200
 };
242
 };
201
 
243
 

Loading…
Cancel
Save