Просмотр исходного кода

feat module 6, 7 : change product details UI, add atome payment UI

master
nadia 2 дней назад
Родитель
Сommit
30cebe6a3a
1 измененных файлов: 380 добавлений и 87 удалений
  1. 380
    87
      src/components/ProductDetails/ProductDetails.jsx

+ 380
- 87
src/components/ProductDetails/ProductDetails.jsx Просмотреть файл

@@ -4,14 +4,24 @@ import {
4 4
   Typography,
5 5
   Button,
6 6
   TextField,
7
-  CircularProgress
7
+  CircularProgress,
8
+  Dialog,
9
+  IconButton
8 10
 } from "@mui/material";
9 11
 import AddIcon from '@mui/icons-material/Add';
10 12
 import RemoveIcon from '@mui/icons-material/Remove';
13
+import StraightenIcon from '@mui/icons-material/Straighten';
14
+import StorefrontIcon from '@mui/icons-material/Storefront';
15
+import CloseIcon from '@mui/icons-material/Close';
16
+import AccountBalanceWalletOutlinedIcon from '@mui/icons-material/AccountBalanceWalletOutlined';
17
+import LayersOutlinedIcon from '@mui/icons-material/LayersOutlined';
18
+import ShoppingBagOutlinedIcon from '@mui/icons-material/ShoppingBagOutlined';
19
+import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart';
11 20
 import { useSelector, useDispatch } from "react-redux";
12 21
 import { createCart, addItemToCart } from "../../redux/slices/cartSlice";
13 22
 import Alert from '@mui/material/Alert';
14 23
 import AlertTitle from '@mui/material/AlertTitle';
24
+import atomeLogo from "../../assets/images/atome.webp";
15 25
 
16 26
 // Utility function to check if an object is empty
17 27
 const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
@@ -34,6 +44,7 @@ const ProductDetails = () => {
34 44
   })
35 45
   const [variants, setVariants] = useState([])
36 46
   const [showLoader, setShowLoader] = useState(false)
47
+  const [atomeOpen, setAtomeOpen] = useState(false)
37 48
   const [alert, setAlert] = useState({ open: false, severity: '', message: '' });
38 49
 
39 50
   useEffect(() => {
@@ -45,7 +56,7 @@ const ProductDetails = () => {
45 56
       let productVariants = product?.variants
46 57
 
47 58
       // get all variant type
48
-      if (!productVariants || productVariants?.length == 0) return
59
+      if (!productVariants || productVariants?.length === 0) return
49 60
 
50 61
       // we want to get the title for each variant
51 62
       const uniqueOptions = {};
@@ -213,51 +224,98 @@ const ProductDetails = () => {
213 224
     setQuantity((prevQuantity) => (prevQuantity > 1 ? prevQuantity - 1 : 1));
214 225
   };
215 226
 
227
+  const amount = parseFloat(variantSelection.amount || 0)
228
+  const currencyCode = variantSelection.currencyCode || ""
229
+  const formattedPrice = `${currencyCode} ${amount.toFixed(2)}`
230
+  const installmentPrice = `${(amount / 3).toFixed(2)} ${currencyCode}`
231
+  // const shortDescriptionHtml = getFirstDescriptionBlock(product?.descriptionHtml)
232
+
216 233
 
217 234
   return (
218 235
     <Box sx={{ position: "relative" }}>
219
-      {/* Flex Container */}
220
-      <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
221
-        {/* Section 1: Product Info */}
236
+      <Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
222 237
         <Box>
223
-          <Typography variant="body2" sx={{
224
-            fontWeight: "400", fontSize: {
225
-              xs: "0.875rem",
226
-              sm: "0.875rem",
227
-              md: "1.1rem",
228
-            }
229
-          }}>
238
+          <Typography
239
+            variant="body2"
240
+            sx={{
241
+              fontWeight: "400",
242
+              fontSize: {
243
+                xs: "0.875rem",
244
+                sm: "0.875rem",
245
+                md: "1.1rem"
246
+              },
247
+              mb: 0.5,
248
+              textTransform: "uppercase"
249
+            }}
250
+          >
230 251
             {product?.title}
231 252
           </Typography>
232
-          {/* <Typography variant="body2" color="text.secondary">
233
-            {product?.collections?.nodes[0]?.title}
234
-          </Typography> */}
235
-
236
-
237
-          <Typography variant="body2" sx={{
238
-            fontWeight: "100", fontSize: {
239
-              xs: "0.875rem",
240
-              sm: "0.875rem",
241
-              md: "1.1rem",
242
-            }
243
-          }}>
244
-            {(variantSelection?.quantityAvailable == 0) ? <span style={{ color: "red", fontWeight: "bolder" }}>{`OUT OF STOCK`}</span> : `${variantSelection.currencyCode} ${parseFloat(variantSelection.amount).toFixed(2)}`}
253
+
254
+          <Box sx={{ mb: 3 }}>
255
+            {/*
256
+            <Typography variant="body1" sx={{ fontWeight: "400", color: "#000" }}>
257
+              Description
258
+            </Typography>
259
+            */}
260
+            <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
261
+              <div dangerouslySetInnerHTML={{ __html: product?.descriptionHtml }}></div>
262
+            </Typography>
263
+          </Box>
264
+
265
+          <Typography
266
+            variant="body2"
267
+            sx={{
268
+              fontWeight: "100",
269
+              fontSize: {
270
+                xs: "0.875rem",
271
+                sm: "0.875rem",
272
+                md: "1.1rem"
273
+              },
274
+              mb: 0.75
275
+            }}
276
+          >
277
+            {(variantSelection?.quantityAvailable === 0) ? <span style={{ color: "red", fontWeight: "bolder" }}>OUT OF STOCK</span> : formattedPrice}
245 278
           </Typography>
246 279
 
280
+          {/* Atome installment option */}
281
+          {amount > 0 && (
282
+            <Box
283
+              component="button"
284
+              type="button"
285
+              onClick={() => setAtomeOpen(true)}
286
+              sx={{
287
+                display: "inline-flex",
288
+                alignItems: "center",
289
+                gap: 0.5,
290
+                border: 0,
291
+                p: 0,
292
+                backgroundColor: "transparent",
293
+                cursor: "pointer",
294
+                fontWeight: "400",
295
+                color: "#000",
296
+                fontFamily: "inherit",
297
+                fontSize: {
298
+                  xs: "0.75rem",
299
+                  md: "0.875rem"
300
+                }
301
+              }}
302
+            >
303
+              or 3 payments of <strong>{installmentPrice}</strong> with <strong>Atome <img style={{ marginLeft: 5, verticalAlign: "middle" }} width="15" src={atomeLogo} alt="Atome" /></strong>
304
+            </Box>
305
+          )}
247 306
         </Box>
248 307
 
249
-        {/* Section 2: Variants */}
250
-        <Box >
251
-
308
+        <Box>
252 309
           {variants.map(({ name, options }, index) => {
253 310
 
254 311
             return (
255
-              <Box sx={{ display: (name == "Title") ? "none" : "block" }}>
312
+              <Box key={name || index} sx={{ display: (name === "Title") ? "none" : "block" }}>
256 313
                 <Typography variant="body2" sx={{
257
-                  fontWeight: "400", fontSize: {
314
+                  fontWeight: "400",
315
+                  fontSize: {
258 316
                     xs: "0.875rem",
259 317
                     sm: "0.875rem",
260
-                    md: "1.1rem",
318
+                    md: "1.1rem"
261 319
                   },
262 320
                   mb: 1
263 321
                 }}>
@@ -267,15 +325,31 @@ const ProductDetails = () => {
267 325
                 <Box sx={{
268 326
                   display: "flex",
269 327
                   flexWrap: "wrap",
270
-
328
+                  gap: 1.25,
271 329
                   mb: 2
272
-                }} gap={2}>
330
+                }}>
273 331
                   {options?.map((value) => (
274 332
                     <Button
275 333
                       key={value}
276
-                      variant={variantSelection[name] === value ? "contained" : "outlined"}
277
-                      color={variantSelection[name] === value ? "primary" : "primary"}
278
-                      sx={{ color: variantSelection[name] === value ? "#FFF" : "#000" }}
334
+                      variant="outlined"
335
+                      sx={{
336
+                        minWidth: 54,
337
+                        width: {
338
+                          xs: 92,
339
+                          md: 110
340
+                        },
341
+                        height: 46,
342
+                        px: 0,
343
+                        borderRadius: 0,
344
+                        borderColor: variantSelection[name] === value ? "primary.main" : "#DDD",
345
+                        backgroundColor: variantSelection[name] === value ? "primary.main" : "#FFF",
346
+                        color: variantSelection[name] === value ? "#FFF" : "#000",
347
+                        fontSize: "0.875rem",
348
+                        "&:hover": {
349
+                          borderColor: "primary.main",
350
+                          backgroundColor: variantSelection[name] === value ? "primary.main" : "#FFF",
351
+                        }
352
+                      }}
279 353
                       onClick={() => handleVariantClick(name, value)}
280 354
                     >
281 355
                       {value}
@@ -287,27 +361,31 @@ const ProductDetails = () => {
287 361
 
288 362
           })}
289 363
 
364
+          <Box
365
+            component="a"
366
+            href="#"
367
+            sx={{
368
+              display: "inline-flex",
369
+              alignItems: "center",
370
+              color: "inherit",
371
+              textDecoration: "underline",
372
+              textTransform: "uppercase",
373
+              fontSize: "0.875rem",
374
+              mb: 1
375
+            }}
376
+          >
377
+            <StraightenIcon sx={{ mr: 0.75, fontSize: 20 }} />
378
+            Size Chart
379
+          </Box>
290 380
         </Box>
291 381
 
292 382
 
293
-        {/* Section 3: Quantity */}
294
-        <Box sx={{ mb: 5 }}>
295
-          <Typography variant="body2" sx={{
296
-            fontWeight: "400", fontSize: {
297
-              xs: "0.875rem",
298
-              sm: "0.875rem",
299
-              md: "1.1rem",
300
-            },
301
-            mb: 1
302
-          }}>
303
-            Quantity
304
-          </Typography>
305
-          <Box display="flex" alignItems="center" gap={2}>
383
+        <Box sx={{ display: "flex", alignItems: "stretch", gap: 2 }}>
384
+          <Box display="flex" alignItems="stretch" sx={{ backgroundColor: "#F4F4F4", height: 48 }}>
306 385
             <Button
307
-              variant="contained"
308
-              color="primary"
309
-              sx={{ width: "35px" }}
310
-              disabled={variantSelection?.quantityAvailable <= 0 || quantity == 1}
386
+              variant="text"
387
+              sx={{ minWidth: 80, color: "#000", backgroundColor: "#F4F4F4" }}
388
+              disabled={variantSelection?.quantityAvailable <= 0 || quantity === 1}
311 389
               onClick={handleDecrement}
312 390
             >
313 391
               <RemoveIcon />
@@ -316,52 +394,52 @@ const ProductDetails = () => {
316 394
             <TextField
317 395
               value={quantity}
318 396
               inputProps={{ readOnly: true, style: { textAlign: 'center' } }}
319
-              size="small"
320
-              sx={{ width: "100px" }}
397
+              sx={{
398
+                width: 58,
399
+                "& .MuiOutlinedInput-root": {
400
+                  height: 48,
401
+                  borderRadius: 0,
402
+                  backgroundColor: "#F4F4F4",
403
+                  "& fieldset": {
404
+                    border: "none"
405
+                  }
406
+                }
407
+              }}
321 408
               variant="outlined"
322 409
             />
323 410
 
324 411
             <Button
325
-              variant="contained"
326
-              color="primary"
327
-              sx={{ width: "35px" }}
328
-              disabled={variantSelection?.quantityAvailable <= 0 || quantity == variantSelection?.quantityAvailable}
412
+              variant="text"
413
+              sx={{ minWidth: 80, color: "#000", backgroundColor: "#F4F4F4" }}
414
+              disabled={variantSelection?.quantityAvailable <= 0 || quantity === variantSelection?.quantityAvailable}
329 415
               onClick={handleIncrement}
330 416
             >
331 417
               <AddIcon />
332 418
             </Button>
333 419
           </Box>
334
-        </Box>
335 420
 
336
-        {/* Section 4: Description */}
337
-        <Box sx={{ mb: 5 }}>
338
-          <Typography variant="body1" sx={{ fontWeight: "400", color: "#000" }}>
339
-            Description
340
-          </Typography>
341
-          <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
342
-            <div dangerouslySetInnerHTML={{ __html: product?.descriptionHtml }}></div>
343
-          </Typography>
421
+          <Button
422
+            onClick={() => { handleCart() }}
423
+            variant="contained"
424
+            color="primary"
425
+            fullWidth
426
+            disabled={variantSelection?.quantityAvailable <= 0 || showLoader}
427
+            sx={{
428
+              backgroundColor: (theme) => theme.palette.primary.main,
429
+              color: "white",
430
+              textTransform: "uppercase",
431
+              borderRadius: 0,
432
+              fontSize: "0.875rem",
433
+              "&:hover": {
434
+                backgroundColor: (theme) => theme.palette.primary.dark,
435
+              },
436
+            }}
437
+          >
438
+            <AddShoppingCartIcon sx={{ mr: 1, fontSize: 18 }} />
439
+            ADD TO CART {showLoader && <CircularProgress sx={{ ml: 1 }} color="white" size={20} />}
440
+          </Button>
344 441
         </Box>
345 442
 
346
-        <Button
347
-          onClick={() => { handleCart() }}
348
-          variant="contained"
349
-          color="common.black"
350
-          fullWidth
351
-          disabled={variantSelection?.quantityAvailable <= 0 || showLoader}
352
-          sx={{
353
-            backgroundColor: (theme) => theme.palette.common.black,
354
-            color: "white",
355
-            textTransform: "none",
356
-            mt: 2,
357
-            "&:hover": {
358
-              backgroundColor: (theme) => theme.palette.grey[900],
359
-            },
360
-          }}
361
-        >
362
-          ADD TO CART {showLoader && <CircularProgress sx={{ ml: 1 }} color="white" size={20} />}
363
-        </Button>
364
-
365 443
         {cart?.lines?.nodes?.length > 0 && <Button
366 444
           onClick={() => { window.location.href = '/cart' }}
367 445
           variant="contained"
@@ -379,7 +457,79 @@ const ProductDetails = () => {
379 457
         >
380 458
           PAY NOW
381 459
         </Button>}
460
+
461
+        {/*
462
+        {/*
463
+        <Box sx={{ mb: 5 }}>
464
+          <Typography variant="body1" sx={{ fontWeight: "400", color: "#000" }}>
465
+            Description
466
+          </Typography>
467
+          <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
468
+            <div dangerouslySetInnerHTML={{ __html: product?.descriptionHtml }}></div>
469
+          </Typography>
470
+        </Box>
471
+        */}
472
+
473
+        {/* <Box sx={{ mb: 5 }}>
474
+          <Typography variant="body1" sx={{ fontWeight: "400", color: "#000" }}>
475
+            Description
476
+          </Typography>
477
+          <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
478
+            <div dangerouslySetInnerHTML={{ __html: product?.descriptionHtml }}></div>
479
+          </Typography>
480
+        </Box> */}
481
+
482
+        {/* Product information rows: store availability, details, material, care, shipping, and returns */}
483
+        <Box sx={{ mt: 4, borderTop: "1px solid #DDD" }}>
484
+          {/* Store Availability */}
485
+          <Box sx={{ display: "flex", alignItems: "center", py: 2, borderBottom: "1px solid #DDD" }}>
486
+            <StorefrontIcon sx={{ mr: 1, fontSize: 20 }} />
487
+            <Typography variant="body2" sx={{ textTransform: "uppercase", fontWeight: "400", fontSize: { xs: "0.875rem", md: "1.1rem" } }}>
488
+              Store Availability
489
+            </Typography>
490
+          </Box>
491
+
492
+          {/* Product Details */}
493
+          <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", py: 2, borderBottom: "1px solid #DDD" }}>
494
+            <Typography variant="body2" sx={{ textTransform: "uppercase", fontWeight: "400", fontSize: { xs: "0.875rem", md: "1.1rem" } }}>
495
+              Product Details
496
+            </Typography>
497
+            <AddIcon sx={{ fontSize: 18 }} />
498
+          </Box>
499
+
500
+          {/* Material */}
501
+          <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", py: 2, borderBottom: "1px solid #DDD" }}>
502
+            <Typography variant="body2" sx={{ textTransform: "uppercase", fontWeight: "400", fontSize: { xs: "0.875rem", md: "1.1rem" } }}>
503
+              Material
504
+            </Typography>
505
+            <AddIcon sx={{ fontSize: 18 }} />
506
+          </Box>
507
+
508
+          {/* Care Instruction */}
509
+          <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", py: 2, borderBottom: "1px solid #DDD" }}>
510
+            <Typography variant="body2" sx={{ textTransform: "uppercase", fontWeight: "400", fontSize: { xs: "0.875rem", md: "1.1rem" } }}>
511
+              Care Instruction
512
+            </Typography>
513
+            <AddIcon sx={{ fontSize: 18 }} />
514
+          </Box>
515
+          {/* Shipping Information */}
516
+          <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", py: 2, borderBottom: "1px solid #DDD" }}>
517
+            <Typography variant="body2" sx={{ textTransform: "uppercase", fontWeight: "400", fontSize: { xs: "0.875rem", md: "1.1rem" } }}>
518
+              Shipping Information
519
+            </Typography>
520
+            <AddIcon sx={{ fontSize: 18 }} />
521
+          </Box>
522
+
523
+          {/* Return & Exchanges */}
524
+          <Box sx={{ display: "flex", alignItems: "center", justifyContent: "space-between", py: 2, borderBottom: "1px solid #DDD" }}>
525
+            <Typography variant="body2" sx={{ textTransform: "uppercase", fontWeight: "400", fontSize: { xs: "0.875rem", md: "1.1rem" } }}>
526
+              Return & Exchanges
527
+            </Typography>
528
+            <AddIcon sx={{ fontSize: 18 }} />
529
+          </Box>
530
+        </Box>
382 531
       </Box>
532
+      
383 533
       {alert.open && (
384 534
         <Alert
385 535
           severity={alert.severity}
@@ -390,6 +540,149 @@ const ProductDetails = () => {
390 540
           {alert.message}
391 541
         </Alert>
392 542
       )}
543
+
544
+      <Dialog
545
+        open={atomeOpen}
546
+        onClose={() => setAtomeOpen(false)}
547
+        maxWidth="md"
548
+        fullWidth
549
+        PaperProps={{
550
+          sx: {
551
+            borderRadius: 0,
552
+            backgroundColor: "#FFF",
553
+            px: { xs: 3, md: 8 },
554
+            py: { xs: 6, md: 7 },
555
+            position: "relative"
556
+          }
557
+        }}
558
+      >
559
+        <IconButton
560
+          aria-label="Close Atome popup"
561
+          onClick={() => setAtomeOpen(false)}
562
+          sx={{
563
+            position: "absolute",
564
+            right: 16,
565
+            top: 12,
566
+            color: "#000"
567
+          }}
568
+        >
569
+          <CloseIcon sx={{ fontSize: 34 }} />
570
+        </IconButton>
571
+
572
+        <Box sx={{ textAlign: "center" }}>
573
+          <Box sx={{ display: "inline-flex", alignItems: "center", gap: 1, mb: 4 }}>
574
+            <Typography
575
+              component="span"
576
+              sx={{
577
+                fontSize: { xs: "2.25rem", md: "3rem" },
578
+                fontWeight: 700,
579
+                lineHeight: 1,
580
+                color: "#000"
581
+              }}
582
+            >
583
+              atome
584
+            </Typography>
585
+            <Box
586
+              component="span"
587
+              sx={{
588
+                display: "inline-flex",
589
+                alignItems: "center",
590
+                justifyContent: "center",
591
+                width: { xs: 42, md: 50 },
592
+                height: { xs: 42, md: 50 },
593
+                borderRadius: "9px",
594
+                backgroundColor: "#F0FF4F",
595
+                color: "#000",
596
+                fontWeight: 700,
597
+                fontSize: { xs: "2rem", md: "2.35rem" },
598
+                lineHeight: 1
599
+              }}
600
+            >
601
+              A
602
+            </Box>
603
+          </Box>
604
+
605
+          <Typography sx={{ fontSize: "1.35rem", fontWeight: 500, mb: 1 }}>
606
+            Time to own it
607
+          </Typography>
608
+          <Typography sx={{ color: "#7D8799", fontSize: "1.1rem", mb: { xs: 6, md: 9 } }}>
609
+            Why wait? Shop today, 3 easy payments.
610
+          </Typography>
611
+
612
+          <Box
613
+            sx={{
614
+              display: "grid",
615
+              gridTemplateColumns: { xs: "1fr", md: "repeat(3, 1fr)" },
616
+              gap: { xs: 5, md: 6 },
617
+              mb: { xs: 6, md: 9 }
618
+            }}
619
+          >
620
+            {[
621
+              {
622
+                icon: <AccountBalanceWalletOutlinedIcon />,
623
+                title: "3 easy payments",
624
+                text: "100% transparent"
625
+              },
626
+              {
627
+                icon: <LayersOutlinedIcon />,
628
+                title: "Split bill into 3 payments",
629
+                text: "Hassle-free experience"
630
+              },
631
+              {
632
+                icon: <ShoppingBagOutlinedIcon />,
633
+                title: "Hundreds of merchants",
634
+                text: "Vast variety"
635
+              }
636
+            ].map((item) => (
637
+              <Box key={item.title} sx={{ textAlign: "center" }}>
638
+                <Box
639
+                  sx={{
640
+                    color: "#111827",
641
+                    "& svg": {
642
+                      fontSize: 72,
643
+                      stroke: "#111827",
644
+                      strokeWidth: 0.6
645
+                    },
646
+                    mb: 3
647
+                  }}
648
+                >
649
+                  {item.icon}
650
+                </Box>
651
+                <Typography sx={{ fontWeight: 700, fontSize: "1.05rem", mb: 1 }}>
652
+                  {item.title}
653
+                </Typography>
654
+                <Typography sx={{ color: "#7D8799", fontSize: "1rem" }}>
655
+                  {item.text}
656
+                </Typography>
657
+              </Box>
658
+            ))}
659
+          </Box>
660
+
661
+          <Typography sx={{ fontWeight: 700, mb: 2 }}>
662
+            Terms & Conditions
663
+          </Typography>
664
+          <Button
665
+            component="a"
666
+            href="https://www.atome.sg/how-it-works"
667
+            target="_blank"
668
+            rel="noopener noreferrer"
669
+            variant="contained"
670
+            sx={{
671
+              backgroundColor: "#F0FF4F",
672
+              color: "#071021",
673
+              minWidth: 260,
674
+              fontWeight: 700,
675
+              fontSize: "1.35rem",
676
+              textTransform: "none",
677
+              "&:hover": {
678
+                backgroundColor: "#E5F33F"
679
+              }
680
+            }}
681
+          >
682
+            How it works
683
+          </Button>
684
+        </Box>
685
+      </Dialog>
393 686
     </Box>
394 687
   );
395 688
 };

Загрузка…
Отмена
Сохранить