123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- import { useState, useEffect } from "react";
- import {
- Box,
- Typography,
- Button,
- TextField,
- CircularProgress
- } from "@mui/material";
- import AddIcon from '@mui/icons-material/Add';
- import RemoveIcon from '@mui/icons-material/Remove';
- import { useSelector, useDispatch } from "react-redux";
- import { createCart, addItemToCart } from "../../redux/slices/cartSlice";
- import Alert from '@mui/material/Alert';
- import AlertTitle from '@mui/material/AlertTitle';
-
- // Utility function to check if an object is empty
- const isEmptyObject = (obj) => Object.keys(obj).length === 0 && obj.constructor === Object;
-
- // Check if every key in the target object matches the source object
- const hasMatchingProperties = (source, target) => {
- return Object.entries(target).every(([key, value]) => source[key] === value);
- };
-
- const ProductDetails = () => {
-
- const dispatch = useDispatch()
- const product = useSelector((state) => state.products.product.data)
- const cart = useSelector((state) => state.cart.cart)
-
- const [quantity, setQuantity] = useState(1)
- const [variantSelection, setVariantSelection] = useState({
- amount: "0",
- currencyCode: ""
- })
- const [variants, setVariants] = useState([])
- const [showLoader, setShowLoader] = useState(false)
- const [alert, setAlert] = useState({ open: false, severity: '', message: '' });
-
- useEffect(() => {
- if (!isEmptyObject(product)) {
-
- console.log("Product: ", product)
-
-
- let productVariants = product?.variants
-
- // get all variant type
- if (!productVariants || productVariants?.length == 0) return
-
- // we want to get the title for each variant
- const uniqueOptions = {};
- productVariants.forEach(variant => {
-
- variant.selectedOptions.forEach(option => {
- if (!uniqueOptions[option.name]) {
- uniqueOptions[option.name] = new Set();
- }
- uniqueOptions[option.name].add(option.value);
- });
- });
-
- const VariantsArr = Object.entries(uniqueOptions).map(([key, valueSet]) => ({
- name: key,
- options: Array.from(valueSet),
- }));
-
- // get variants value
- setVariants(VariantsArr)
-
- // setting Initial value for variants selection
- setVariantSelection((prev) => {
-
- let newVariantSelection = { ...prev }
-
- // setting inital selection
- VariantsArr.forEach(({ name, options }) => {
- newVariantSelection = { ...newVariantSelection, [name]: options[0] }
- })
-
- // find variant price if it all match initial variant selection
- for (const { selectedOptions, price, compareAtPrice, id, quantityAvailable } of productVariants) {
-
- let { amount, currencyCode } = price;
-
- if (compareAtPrice?.amount > 0) {
- amount = compareAtPrice?.amount
- currencyCode = compareAtPrice?.currencyCode
- }
-
- // Convert array to object
- const optionsObject = selectedOptions.reduce(
- (a, { name, value }) => ({ ...a, [name]: value }),
- {}
- );
-
- if (hasMatchingProperties(newVariantSelection, optionsObject)) {
- newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
- break; // Exit the loop when condition is met
- }
- }
-
- return newVariantSelection
-
- })
-
- }
-
- }, [product])
-
- useEffect(() => {
- console.log("variantSelection: ", variantSelection)
- }, [variantSelection])
-
- const handleVariantClick = (name, value) => {
-
- setVariantSelection({ ...variantSelection, [name]: value })
-
- setVariantSelection((prev) => {
-
- let newVariantSelection = { ...prev }
- newVariantSelection = { ...newVariantSelection, [name]: value }
- let productVariants = product?.variants
-
- // find variant price if it all match initial variant selection
- for (const { selectedOptions, price, compareAtPrice, id, quantityAvailable } of productVariants) {
-
- let { amount, currencyCode } = price;
-
- if (compareAtPrice?.amount > 0) {
- amount = compareAtPrice?.amount
- currencyCode = compareAtPrice?.currencyCode
- }
-
- // Convert array to object
- const optionsObject = selectedOptions.reduce(
- (a, { name, value }) => ({ ...a, [name]: value }),
- {}
- );
-
- if (hasMatchingProperties(newVariantSelection, optionsObject)) {
- newVariantSelection = { ...newVariantSelection, amount, currencyCode, id, quantityAvailable };
- if (quantityAvailable == 0) setQuantity(0)
- else setQuantity(1)
- break; // Exit the loop when condition is met
- }
- }
-
- return newVariantSelection
-
- })
-
- }
-
- const handleCart = () => {
-
- let cartHistory = localStorage.getItem('amber-cart');
- cartHistory = cartHistory ? JSON.parse(cartHistory) : {};
-
- setShowLoader(true) //cause I want to prevent user from mutiple clicking
-
- // if we got no cart, then create a new one
- if (isEmptyObject(cart) || isEmptyObject(cartHistory)) {
-
- dispatch(createCart())
- .then(() => {
- showAlert('success', 'Cart created successfully!');
- })
- .catch(() => {
- showAlert('error', 'Failed to create cart. Please try again.');
- })
- .finally(() => setShowLoader(false));
-
- } else {
-
- console.log("ADD ITEM:", variantSelection)
-
- dispatch(addItemToCart({
- cartId: cartHistory.id,
- lines: [
- {
- merchandiseId: variantSelection.id,
- quantity
- }
- ]
- }))
- .then(() => {
- showAlert('success', 'Item added to cart successfully!');
- })
- .catch(() => {
- showAlert('error', 'Failed to add item to cart. Please try again.');
- })
- .finally(() => setShowLoader(false));
- }
-
- }
-
- const showAlert = (severity, message) => {
- setAlert({ open: true, severity, message });
-
- // Auto-close the alert after 3 seconds
- setTimeout(() => {
- setAlert({ ...alert, open: false });
- }, 2000);
- };
-
- const handleIncrement = () => {
- setQuantity((prevQuantity) => (prevQuantity >= variantSelection.quantityAvailable ? variantSelection.quantityAvailable : prevQuantity + 1));
- };
-
- const handleDecrement = () => {
- setQuantity((prevQuantity) => (prevQuantity > 1 ? prevQuantity - 1 : 1));
- };
-
-
- return (
- <Box sx={{ position: "relative" }}>
- {/* Flex Container */}
- <Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
- {/* Section 1: Product Info */}
- <Box>
- <Typography variant="body2" sx={{
- fontWeight: "400", fontSize: {
- xs: "0.875rem",
- sm: "0.875rem",
- md: "1.1rem",
- }
- }}>
- {product?.title}
- </Typography>
- {/* <Typography variant="body2" color="text.secondary">
- {product?.collections?.nodes[0]?.title}
- </Typography> */}
-
-
- <Typography variant="body2" sx={{
- fontWeight: "100", fontSize: {
- xs: "0.875rem",
- sm: "0.875rem",
- md: "1.1rem",
- }
- }}>
- {(variantSelection?.quantityAvailable == 0) ? <span style={{ color: "red", fontWeight: "bolder" }}>{`OUT OF STOCK`}</span> : `${variantSelection.currencyCode} ${parseFloat(variantSelection.amount).toFixed(2)}`}
- </Typography>
-
- </Box>
-
- {/* Section 2: Variants */}
- <Box >
-
- {variants.map(({ name, options }, index) => {
-
- return (
- <Box sx={{ display: (name == "Title") ? "none" : "block" }}>
- <Typography variant="body2" sx={{
- fontWeight: "400", fontSize: {
- xs: "0.875rem",
- sm: "0.875rem",
- md: "1.1rem",
- },
- mb: 1
- }}>
- {name}
- </Typography>
-
- <Box sx={{
- display: "flex",
- flexWrap: "wrap",
-
- mb: 2
- }} gap={2}>
- {options?.map((value) => (
- <Button
- key={value}
- variant={variantSelection[name] === value ? "contained" : "outlined"}
- color={variantSelection[name] === value ? "primary" : "primary"}
- sx={{ color: variantSelection[name] === value ? "#FFF" : "#000" }}
- onClick={() => handleVariantClick(name, value)}
- >
- {value}
- </Button>
- ))}
- </Box>
- </Box>
- )
-
- })}
-
- </Box>
-
-
- {/* Section 3: Quantity */}
- <Box sx={{ mb: 5 }}>
- <Typography variant="body2" sx={{
- fontWeight: "400", fontSize: {
- xs: "0.875rem",
- sm: "0.875rem",
- md: "1.1rem",
- },
- mb: 1
- }}>
- Quantity
- </Typography>
- <Box display="flex" alignItems="center" gap={2}>
- <Button
- variant="contained"
- color="primary"
- sx={{ width: "35px" }}
- disabled={variantSelection?.quantityAvailable == 0 || quantity == 1}
- onClick={handleDecrement}
- >
- <RemoveIcon />
- </Button>
-
- <TextField
- value={quantity}
- inputProps={{ readOnly: true, style: { textAlign: 'center' } }}
- size="small"
- sx={{ width: "100px" }}
- variant="outlined"
- />
-
- <Button
- variant="contained"
- color="primary"
- sx={{ width: "35px" }}
- disabled={variantSelection?.quantityAvailable == 0 || quantity == variantSelection?.quantityAvailable}
- onClick={handleIncrement}
- >
- <AddIcon />
- </Button>
- </Box>
- </Box>
-
- {/* Section 4: Description */}
- <Box sx={{ mb: 5 }}>
- <Typography variant="body1" sx={{ fontWeight: "400", color: "#000" }}>
- Description
- </Typography>
- <Typography variant="body1" color="text.secondary" sx={{ fontWeight: "400" }}>
- <div dangerouslySetInnerHTML={{ __html: product?.descriptionHtml }}></div>
- </Typography>
- </Box>
-
- <Button
- onClick={() => { handleCart() }}
- variant="contained"
- color="common.black"
- fullWidth
- disabled={variantSelection?.quantityAvailable == 0 || showLoader}
- sx={{
- backgroundColor: (theme) => theme.palette.common.black,
- color: "white",
- textTransform: "none",
- mt: 2,
- "&:hover": {
- backgroundColor: (theme) => theme.palette.grey[900],
- },
- }}
- >
- ADD TO CART {showLoader && <CircularProgress sx={{ ml: 1 }} color="white" size={20} />}
- </Button>
-
- {cart?.lines?.nodes?.length > 0 && <Button
- onClick={() => { window.location.href = '/cart' }}
- variant="contained"
- color="common.black"
- fullWidth
- disabled={showLoader}
- sx={{
- backgroundColor: (theme) => theme.palette.primary.main,
- color: "white",
- textTransform: "none",
- "&:hover": {
- backgroundColor: (theme) => theme.palette.grey[900],
- },
- }}
- >
- PAY NOW
- </Button>}
- </Box>
- {alert.open && (
- <Alert
- severity={alert.severity}
- onClose={() => setAlert({ ...alert, open: false })}
- sx={{ marginTop: 2 }}
- >
- <AlertTitle>{alert.severity === 'success' ? 'Success' : 'Error'}</AlertTitle>
- {alert.message}
- </Alert>
- )}
- </Box>
- );
- };
-
- export default ProductDetails;
|