Merge version_2 into main #1
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user