Merge version_2 into main #1

Merged
bender merged 1 commits from version_2 into main 2026-03-23 16:19:59 +00:00

View File

@@ -23,6 +23,7 @@ type ProductCard = Product & {
onQuantityChange?: (quantity: number) => void;
initialQuantity?: number;
priceButtonProps?: Partial<ButtonPropsForVariant<CTAButtonVariant>>;
ingredients?: string[]; // Added for flip card back
};
interface ProductCardThreeProps {
@@ -62,6 +63,30 @@ interface ProductCardThreeProps {
textBoxButtonTextClassName?: string;
}
// Helper function to generate ingredients based on product name
const generateIngredients = (productName: string): string[] => {
productName = productName.toLowerCase();
if (productName.includes("croissant")) {
return ["Wheat Flour", "Butter", "Water", "Sugar", "Yeast", "Salt", "Milk"];
}
if (productName.includes("cupcake")) {
return ["All-Purpose Flour", "Sugar", "Eggs", "Milk", "Butter", "Baking Powder", "Vanilla Extract", "Salt", "Icing Sugar"];
}
if (productName.includes("sourdough")) {
return ["Bread Flour", "Water", "Sourdough Starter", "Salt"];
}
if (productName.includes("cake")) {
return ["All-Purpose Flour", "Sugar", "Eggs", "Butter", "Milk", "Cocoa Powder", "Baking Soda", "Vanilla Extract", "Cream Cheese Frosting"];
}
if (productName.includes("bread")) {
return ["Wheat Flour", "Water", "Yeast", "Salt", "Sugar"];
}
if (productName.includes("pastry")) {
return ["Flour", "Butter", "Sugar", "Eggs", "Milk", "Fruit Filling"];
}
// Default ingredients for other products
return ["Flour", "Sugar", "Eggs", "Butter", "Milk", "Baking Powder", "Vanilla Extract"];
};
interface ProductCardItemProps {
product: ProductCard;
@@ -86,6 +111,7 @@ const ProductCardItem = memo(({
}: ProductCardItemProps) => {
const theme = useTheme();
const [quantity, setQuantity] = useState(product.initialQuantity || 1);
const ingredients = product.ingredients || generateIngredients(product.name);
const handleIncrement = useCallback((e: React.MouseEvent) => {
e.stopPropagation();
@@ -103,60 +129,95 @@ const ProductCardItem = memo(({
}
}, [quantity, product]);
const handleClick = useCallback(() => {
const handleViewDetailsClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation(); // Prevent any parent click handlers
product.onProductClick?.(); // This will navigate to /shop/:id or trigger static product action
}, [product]);
const handleBuyClick = useCallback((e: React.MouseEvent) => {
e.stopPropagation(); // Prevent any parent click handlers
if (isFromApi && onBuyClick) {
onBuyClick(product.id, quantity);
} else {
product.onProductClick?.();
console.log(`Buying ${quantity} of ${product.name}`);
}
}, [isFromApi, onBuyClick, product, quantity]);
return (
<article
className={cls("card group relative h-full flex flex-col gap-4 cursor-pointer p-4 rounded-theme-capped", cardClassName)}
onClick={handleClick}
className={cls("relative h-full [perspective:1000px]", cardClassName)}
role="article"
aria-label={`${product.name} - ${product.price}`}
>
<ProductImage
imageSrc={product.imageSrc}
imageAlt={product.imageAlt || product.name}
isFavorited={product.isFavorited}
onFavoriteToggle={product.onFavorite}
imageClassName={imageClassName}
/>
<div className="relative w-full h-full [transform-style:preserve-3d] transition-transform duration-500 group-hover:[transform:rotateY(180deg)]">
{/* Front of the card */}
<div className="card absolute inset-0 [backface-visibility:hidden] flex flex-col gap-4 p-4 rounded-theme-capped">
<ProductImage
imageSrc={product.imageSrc}
imageAlt={product.imageAlt || product.name}
isFavorited={product.isFavorited}
onFavoriteToggle={product.onFavorite}
imageClassName={imageClassName}
/>
<div className="relative z-1 flex flex-col gap-3">
<h3 className={cls("text-xl font-medium leading-[1.15] truncate", shouldUseLightText ? "text-background" : "text-foreground", cardNameClassName)}>
{product.name}
</h3>
<div className="relative z-1 flex flex-col gap-3">
<h3 className={cls("text-xl font-medium leading-[1.15] truncate", shouldUseLightText ? "text-background" : "text-foreground", cardNameClassName)}>
{product.name}
</h3>
<div className="flex items-center justify-between gap-4">
<div className={cls("flex items-center gap-2", quantityControlsClassName)}>
<QuantityButton
onClick={handleDecrement}
ariaLabel="Decrease quantity"
Icon={Minus}
/>
<span className={cls("text-base font-medium min-w-[2ch] text-center leading-[1]", shouldUseLightText ? "text-background" : "text-foreground")}>
{quantity}
</span>
<QuantityButton
onClick={handleIncrement}
ariaLabel="Increase quantity"
Icon={Plus}
/>
<div className="flex items-center justify-between gap-4">
<div className={cls("flex items-center gap-2", quantityControlsClassName)}>
<QuantityButton
onClick={handleDecrement}
ariaLabel="Decrease quantity"
Icon={Minus}
/>
<span className={cls("text-base font-medium min-w-[2ch] text-center leading-[1]", shouldUseLightText ? "text-background" : "text-foreground")}>
{quantity}
</span>
<QuantityButton
onClick={handleIncrement}
ariaLabel="Increase quantity"
Icon={Plus}
/>
</div>
<Button
{...getButtonProps(
{
text: product.price,
props: product.priceButtonProps,
},
0,
theme.defaultButtonVariant
)}
onClick={handleBuyClick}
/>
</div>
</div>
</div>
{/* Back of the card */}
<div className={cls("card absolute inset-0 [backface-visibility:hidden] [transform:rotateY(180deg)] flex flex-col gap-2 p-4 rounded-theme-capped justify-between", shouldUseLightText ? "text-background" : "text-foreground")}>
<div>
<h3 className="text-xl font-medium leading-[1.15] mb-2">Ingredients:</h3>
<ul className="list-disc list-inside text-base max-h-30 overflow-y-auto">
{ingredients.map((ingredient, i) => (
<li key={i} className="mb-1">{ingredient}</li>
))}
</ul>
</div>
<Button
{...getButtonProps(
{
text: product.price,
props: product.priceButtonProps,
text: "View Details",
href: isFromApi ? `/shop/${product.id}` : "#",
},
0,
theme.defaultButtonVariant
)}
onClick={handleViewDetailsClick}
className="mt-4 w-full"
/>
</div>
</div>