Initial commit

This commit is contained in:
2025-10-04 18:22:24 +03:00
commit 2852c2c054
291 changed files with 38109 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
import { PlusOutlined } from "@ant-design/icons";
import { Badge, Button, Card, Grid } from "antd";
import ArabicPrice from "components/ArabicPrice";
import HeartIcon from "components/Icons/HeartIcon";
import ImageWithFallback from "components/ImageWithFallback";
import { ItemDescriptionIcons } from "components/ItemDescriptionIcons/ItemDescriptionIcons";
import ProText from "components/ProText";
import { addItem, selectCartItems } from "features/order/orderSlice";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { Link, useParams } from "react-router-dom";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { colors } from "ThemeConstants";
import { Product } from "utils/types/appTypes";
import styles from "../MenuList/MenuList.module.css";
const { useBreakpoint } = Grid;
interface ProductCardProps {
products: Product[];
}
export function ProductCard({ products }: ProductCardProps) {
const { id } = useParams();
const { isRTL, locale } = useAppSelector((state) => state.locale);
const { sm, md } = useBreakpoint();
const isMobile = !sm;
const isTablet = sm && !md;
const { themeName } = useAppSelector((state) => state.theme);
const dispatch = useAppDispatch();
const cartItems = useAppSelector(selectCartItems);
const getItemQuantity = (id: number | string) => {
const item = cartItems.find((i) => i.id === id);
return item ? item.quantity : 0;
};
const { t } = useTranslation();
// Memoized handlers for better performance
const handleQuickAdd = useCallback(
(item: Product) => {
dispatch(
addItem({
item: {
id: item.id,
name: item.name,
price: item.price,
image: item.image,
description: item.description,
variant: "None",
extras: [],
extrasgroup: [],
},
quantity: 1,
})
);
},
[dispatch]
);
const getMenuItemCardStyle = useCallback(
(itemDescription: string) => {
if (isMobile) {
return {
height: itemDescription?.length > 0 ? 160 : 130,
padding: "12px 12px 12px 16px",
};
} else if (isTablet) {
return {
height: 160,
padding: "16px",
};
} else {
return {
height: 180,
padding: "20px",
};
}
},
[isMobile, isTablet]
);
const getMenuItemImageStyle = useCallback(() => {
if (isMobile) {
return {
width: 90,
height: 95,
};
} else if (isTablet) {
return {
width: 120,
height: 120,
};
} else {
return {
width: 140,
height: 140,
};
}
}, [isMobile, isTablet]);
return (
<>
{products?.map((item) => (
<Link
to={`/${id}/product/${item.id}`}
key={item.id}
className={styles.productLink}
>
<Card
key={item.id}
styles={{
body: {
...getMenuItemCardStyle(item.description),
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
borderRadius: 16,
},
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "100%",
gap: isMobile ? 10 : 16,
}}
>
<div
style={{
lineHeight: 1,
height: "100%",
flex: 1,
}}
>
<ProText
style={{
margin: 0,
marginBottom: isMobile ? 8 : isTablet ? 16 : 20,
display: "inline-block",
fontSize: isMobile ? "1rem" : isTablet ? 16 : 18,
fontWeight: 600,
letterSpacing: "-0.01em",
lineHeight: 1.2,
}}
>
{locale == "ar" ? item.nameOther : item.name}
</ProText>
<ProText
type="secondary"
className={styles.itemDescription}
style={{
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: isMobile ? 2 : 4,
overflow: "hidden",
textOverflow: "ellipsis",
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "1.5rem",
maxHeight: isMobile ? "3em" : "6.6em",
fontSize: isMobile ? "1rem" : isTablet ? 16 : 18,
letterSpacing: "0.01em",
}}
>
{item.description}
</ProText>
</div>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "start",
gap: isMobile ? 16 : 24,
position: "absolute",
bottom: isMobile ? 16 : 20,
[isRTL ? "right" : "left"]: isMobile ? 16 : 20,
}}
>
<ArabicPrice
price={item.price}
strong
style={{
fontSize: isMobile ? "1rem" : isTablet ? 18 : 22,
fontWeight: 700,
color: themeName === "dark" ? "#FFC600" : "#1a1a1a",
}}
/>
<ItemDescriptionIcons className={styles.itemDescriptionIcons} />
</div>
<div style={{ position: "relative" }}>
<ImageWithFallback
src={item.image_small || ""}
alt={item.name}
fallbackSrc="/default.png"
className={`${styles.popularMenuItemImage} ${
isMobile
? styles.popularMenuItemImageMobile
: isTablet
? styles.popularMenuItemImageTablet
: styles.popularMenuItemImageDesktop
}`}
{...getMenuItemImageStyle()}
/>
<Button
className={styles.heartButton}
icon={<HeartIcon />}
style={{ width: 24, height: 24 }}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
console.log("heart");
}}
/>
<Button
shape="round"
title="add"
iconPosition="start"
icon={
<PlusOutlined
title="add"
style={{
position: "relative",
top: "-1px",
}}
/>
}
size={isMobile ? "small" : "middle"}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleQuickAdd(item);
}}
style={{
position: "absolute",
bottom: -10,
[isRTL ? "right" : "left"]: isMobile ? "5%" : "15%",
zIndex: 1,
width: isMobile ? 82 : isTablet ? 90 : 100,
height: isMobile ? 32 : isTablet ? 40 : 44,
fontSize: isMobile ? "1rem" : isTablet ? 16 : 18,
fontWeight: 600,
border: 0,
color: themeName === "light" ? "#333333" : "#FFFFFF",
boxShadow:
themeName === "light"
? "0 2px 0 rgba(0,0,0,0.02)"
: "0 2px 0 #6b6b6b",
}}
>
{t("common.add")}
</Button>
{/* Cart quantity badge - shows current quantity in cart */}
{getItemQuantity(item.id) > 0 && (
<Badge
count={getItemQuantity(item.id)}
className={
styles.cartBadge +
" " +
(isRTL ? styles.cartBadgeRTL : styles.cartBadgeLTR)
}
style={{
backgroundColor: colors.primary,
}}
title={`${getItemQuantity(item.id)} in cart`}
/>
)}
</div>
</div>
</Card>
</Link>
))}
</>
);
}