2
\$\begingroup\$

I was working on Product Page with its nested children. On the page we can see a specific product data, add item to the cart and set quantity which we want to add. The codebase is nasty as I only finished, but at least seems everything is working properly, so I'm looking for potential improvements including SOLID and other common patterns which I can use for ProductPage and QuantitySelector.

Screen: enter image description here

ProductPage:

export const ProductPage = () => {
  const user = auth?.currentUser;
  const theme = useTheme();
  const { category, name } = useParams();
  const { data, isLoading } = useGetProductsQuery(category);
  const [selectedQ, setSelectedQ] = useState<number>();
  const [totalPrice, setTotalPrice] = useState<number>();
  const dispatch = useDispatch();

  const selectedProduct = useMemo(
    () =>
      Array.isArray(data?.data) &&
      data.data.find((product: Product) => product.name === name),
    [data, name]
  );

  const handleQuantityChange = (q: number, totalPrice: number) => {
    setSelectedQ(q);
    setTotalPrice(totalPrice);
  };

  const handleAddItemToCart = async () => {
    if (user) {
      const cartsRef = doc(db, "carts", user.uid);
      const docSnap = await getDoc(cartsRef);

      let cartItems = [];
      if (docSnap.exists()) {
        cartItems = docSnap.data().items || [];
      }

      const itemIndex = cartItems.findIndex(
        (item: any) => item.name === selectedProduct.name
      );

      if (itemIndex !== -1) {
        cartItems[itemIndex].selectedQuantity =
          cartItems[itemIndex].selectedQuantity + selectedQ;
        cartItems[itemIndex].totalPrice =
          cartItems[itemIndex].totalPrice + totalPrice;
      } else {
        cartItems.push({
          name: selectedProduct.name,
          imgURL: selectedProduct.imgURL,
          quantity: selectedProduct.quantity,
          selectedQuantity: selectedQ,
          totalPrice: totalPrice,
        });
      }

      await updateDoc(cartsRef, { items: cartItems });
      dispatch(
        setNotification({
          open: true,
          title: `${selectedProduct.name} added to cart`,
          color: theme.palette.primary.main,
        })
      );
      setTimeout(
        () =>
          dispatch(
            setNotification({
              open: false,
            })
          ),
        2000
      );
    }
  };

  return (
    <>
      {!isLoading ? (
        <>
          <Notification />
          <Box
            display="flex"
            flexDirection={{ xs: "column", sm: "column", md: "row" }}
            justifyContent="space-between"
            marginTop={10}
            padding={{
              xs: "0 16px 48px 16px",
              sm: "0 24px 48px 24px",
              md: "0 48px 48px 48px",
            }}
          >
            <Box
              width={{ xs: "100%", sm: "100%", md: "50%" }}
              display="flex"
              alignItems="center"
              justifyContent="center"
            >
              <Box
                component="img"
                src={selectedProduct.imgURL}
                alt={selectedProduct.name}
                width={{ xs: "15rem", sm: "18rem", md: "20rem" }}
                height={{ xs: "15rem", sm: "18rem", md: "20rem" }}
              />
            </Box>
            <Box
              height="100%"
              width={{ xs: "100%", sm: "100%", md: "50%" }}
              display="flex"
              flexDirection="column"
              alignItems={{ xs: "flex-start", sm: "flex-start", md: "center" }}
              justifyContent="center"
              gap="2rem"
            >
              <Box
                width="100%"
                display="flex"
                alignItems="center"
                justifyContent="space-between"
              >
                <Typography variant="h1">{selectedProduct.name}</Typography>
                <QuantitySelector
                  quantity={selectedProduct.quantity}
                  units={selectedProduct.units}
                  price={selectedProduct.price}
                  onQuantityChange={handleQuantityChange}
                />
              </Box>
              <Typography variant="subtitle1">
                {selectedProduct.about}
              </Typography>
              <Box
                width="100%"
                display="flex"
                alignItems="center"
                justifyContent="space-between"
                fontWeight={600}
              >
                <Box display="flex" alignItems="center" gap="1rem">
                  <span>⭐️</span>
                  <span>4.5</span>
                </Box>
                <Box display="flex" alignItems="center" gap="1rem">
                  <span>🔥</span>
                  <span>{selectedProduct.calories} Kcal</span>
                </Box>
                <Box display="flex" alignItems="center" gap="1rem">
                  <span>⏰</span>
                  <span>10-15 Min</span>
                </Box>
              </Box>
              <Categories title="Related Items" />
              <Button sx={{ width: "100%" }} onClick={handleAddItemToCart}>
                Add To Cart
              </Button>
            </Box>
          </Box>
        </>
      ) : (
        <PageLoader />
      )}
    </>
  );
};

QuantitySelector:

import { useState, useEffect } from "react";
import { Box, Button, styled, useTheme } from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";

const SelectButton = styled(Button)(({ theme }) => ({
  minWidth: "1.8rem",
  maxWidth: "1.8rem",
  height: "1.8rem",
  borderRadius: "0.3rem",
  backgroundColor: "transparent",
  color: theme.palette.primary.main,
  border: `1px solid ${theme.palette.primary.main}`,
  transitionDuration: "0.2s",
  "&:hover": {
    backgroundColor: "rgba(46, 171, 92, 0.1)",
  },
}));

type QuantitySelectorProps = {
  units: string;
  quantity: number;
  price: number;
  onQuantityChange: (q: number) => void;
};

export const QuantitySelector = ({
  quantity,
  units,
  price,
  onQuantityChange,
}: QuantitySelectorProps) => {
  const theme = useTheme();
  const [q, setQ] = useState(quantity);
  const [totalPrice, setTotalPrice] = useState(price * (quantity / 1000));

  useEffect(() => {
    setTotalPrice((q / 1000) * price);
    onQuantityChange(q, totalPrice);
  }, [q, price, onQuantityChange]);

  const handleFormatQ = (q: number) => {
    if (units === "g-kg") {
      return q && q >= 1000 ? `${(q / 1000).toFixed(1)}kg` : `${q}g`;
    }

    return `${q}pcs`;
  };

  const handleIncrementQ = () =>
    units === "g-kg"
      ? setQ((prevQ) => prevQ + 200)
      : setQ((prefQ) => prefQ + 1);

  const handleDecrementQ = () => {
    if (units === "g-kg" && q > 200) {
      setQ((prevQ) => prevQ - 200);
    } else if (units !== "g-kg" && q > 1) {
      setQ((prevQ) => prevQ - 1);
    }
  };

  return (
    <Box display="flex" alignItems="center" justifyContent="center" gap="1rem">
      <SelectButton onClick={handleIncrementQ}>
        <AddIcon sx={{ fontSize: "1.2rem" }} />
      </SelectButton>
      <Box
        width="3.5rem"
        height="3rem"
        color={theme.palette.primary.main}
        bgcolor="rgba(46, 171, 92, 0.1)"
        borderRadius="0.3rem"
        display="flex"
        alignItems="center"
        justifyContent="center"
        fontWeight={600}
      >
        {handleFormatQ(q)}
      </Box>
      <SelectButton onClick={handleDecrementQ}>
        <RemoveIcon sx={{ fontSize: "1.2rem" }} />
      </SelectButton>
    </Box>
  );
};

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.