0
\$\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

Browse other questions tagged or ask your own question.