From a5fc9943c4e2b63e08bc6b17cf8f22931dc103ff Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 4 Mar 2026 19:07:14 +0000 Subject: [PATCH] Switch to version 1: modified src/components/sections/product/ProductCardThree.tsx --- .../sections/product/ProductCardThree.tsx | 326 +++++++++++++++--- 1 file changed, 277 insertions(+), 49 deletions(-) diff --git a/src/components/sections/product/ProductCardThree.tsx b/src/components/sections/product/ProductCardThree.tsx index badc67e..f53d136 100644 --- a/src/components/sections/product/ProductCardThree.tsx +++ b/src/components/sections/product/ProductCardThree.tsx @@ -1,55 +1,283 @@ -import React from 'react'; +"use client"; -interface Product { - id: string; - name: string; - price: string; - imageSrc: string; - imageAlt?: string; - onFavorite?: () => void; - onProductClick?: () => void; - isFavorited?: boolean; -} +import { memo, useState, useCallback } from "react"; +import { useRouter } from "next/navigation"; +import { Plus, Minus } from "lucide-react"; +import CardStack from "@/components/cardStack/CardStack"; +import ProductImage from "@/components/shared/ProductImage"; +import QuantityButton from "@/components/shared/QuantityButton"; +import Button from "@/components/button/Button"; +import { useTheme } from "@/providers/themeProvider/ThemeProvider"; +import { useProducts } from "@/hooks/useProducts"; +import { getButtonProps } from "@/lib/buttonUtils"; +import { cls, shouldUseInvertedText } from "@/lib/utils"; +import type { Product } from "@/lib/api/product"; +import type { LucideIcon } from "lucide-react"; +import type { ButtonConfig, ButtonAnimationType, GridVariant, CardAnimationType, TitleSegment } from "@/components/cardStack/types"; +import type { CTAButtonVariant, ButtonPropsForVariant } from "@/components/button/types"; +import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; -interface ProductCardThreeProps { - products?: Product[]; - carouselMode?: 'auto' | 'buttons'; - gridVariant: 'uniform-all-items-equal' | 'bento-grid' | 'bento-grid-inverted' | 'two-columns-alternating-heights' | 'asymmetric-60-wide-40-narrow' | 'three-columns-all-equal-width' | 'four-items-2x2-equal-grid' | 'one-large-right-three-stacked-left' | 'items-top-row-full-width-bottom' | 'full-width-top-items-bottom-row' | 'one-large-left-three-stacked-right'; - animationType: 'none' | 'opacity' | 'slide-up' | 'scale-rotate' | 'blur-reveal'; - uniformGridCustomHeightClasses?: string; - title: string; - titleSegments?: Array<{ type: 'text'; content: string } | { type: 'image'; src: string; alt?: string }>; - description: string; - tag?: string; - tagIcon?: React.ComponentType; - tagAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal'; - buttons?: Array<{ text: string; onClick?: () => void; href?: string }>; - buttonAnimation?: 'none' | 'opacity' | 'slide-up' | 'blur-reveal'; - textboxLayout: 'default' | 'split' | 'split-actions' | 'split-description' | 'inline-image'; - useInvertedBackground: boolean; - ariaLabel?: string; - className?: string; - containerClassName?: string; - cardClassName?: string; - imageClassName?: string; - textBoxTitleClassName?: string; - textBoxTitleImageWrapperClassName?: string; - textBoxTitleImageClassName?: string; - textBoxDescriptionClassName?: string; - cardNameClassName?: string; - cardPriceClassName?: string; - gridClassName?: string; - carouselClassName?: string; - controlsClassName?: string; - textBoxClassName?: string; - textBoxTagClassName?: string; - textBoxButtonContainerClassName?: string; - textBoxButtonClassName?: string; - textBoxButtonTextClassName?: string; -} +type ProductCardThreeGridVariant = Exclude; -const ProductCardThree: React.FC = (props) => { - return
ProductCardThree Component
; +type ProductCard = Product & { + onQuantityChange?: (quantity: number) => void; + initialQuantity?: number; + priceButtonProps?: Partial>; }; +interface ProductCardThreeProps { + products?: ProductCard[]; + carouselMode?: "auto" | "buttons"; + gridVariant: ProductCardThreeGridVariant; + uniformGridCustomHeightClasses?: string; + animationType: CardAnimationType; + title: string; + titleSegments?: TitleSegment[]; + description: string; + tag?: string; + tagIcon?: LucideIcon; + tagAnimation?: ButtonAnimationType; + buttons?: ButtonConfig[]; + buttonAnimation?: ButtonAnimationType; + textboxLayout: TextboxLayout; + useInvertedBackground: InvertedBackground; + ariaLabel?: string; + className?: string; + containerClassName?: string; + cardClassName?: string; + imageClassName?: string; + textBoxTitleClassName?: string; + textBoxTitleImageWrapperClassName?: string; + textBoxTitleImageClassName?: string; + textBoxDescriptionClassName?: string; + cardNameClassName?: string; + quantityControlsClassName?: string; + gridClassName?: string; + carouselClassName?: string; + controlsClassName?: string; + textBoxClassName?: string; + textBoxTagClassName?: string; + textBoxButtonContainerClassName?: string; + textBoxButtonClassName?: string; + textBoxButtonTextClassName?: string; +} + + +interface ProductCardItemProps { + product: ProductCard; + shouldUseLightText: boolean; + isFromApi: boolean; + onBuyClick?: (productId: string, quantity: number) => void; + cardClassName?: string; + imageClassName?: string; + cardNameClassName?: string; + quantityControlsClassName?: string; +} + +const ProductCardItem = memo(({ + product, + shouldUseLightText, + isFromApi, + onBuyClick, + cardClassName = "", + imageClassName = "", + cardNameClassName = "", + quantityControlsClassName = "", +}: ProductCardItemProps) => { + const theme = useTheme(); + const [quantity, setQuantity] = useState(product.initialQuantity || 1); + + const handleIncrement = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + const newQuantity = quantity + 1; + setQuantity(newQuantity); + product.onQuantityChange?.(newQuantity); + }, [quantity, product]); + + const handleDecrement = useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + if (quantity > 1) { + const newQuantity = quantity - 1; + setQuantity(newQuantity); + product.onQuantityChange?.(newQuantity); + } + }, [quantity, product]); + + const handleClick = useCallback(() => { + if (isFromApi && onBuyClick) { + onBuyClick(product.id, quantity); + } else { + product.onProductClick?.(); + } + }, [isFromApi, onBuyClick, product, quantity]); + + return ( +
+ + +
+

+ {product.name} +

+ +
+
+ + + {quantity} + + +
+ +
+
+
+ ); +}); + +ProductCardItem.displayName = "ProductCardItem"; + +const ProductCardThree = ({ + products: productsProp, + carouselMode = "buttons", + gridVariant, + uniformGridCustomHeightClasses = "min-h-95 2xl:min-h-105", + animationType, + title, + titleSegments, + description, + tag, + tagIcon, + tagAnimation, + buttons, + buttonAnimation, + textboxLayout, + useInvertedBackground, + ariaLabel = "Product section", + className = "", + containerClassName = "", + cardClassName = "", + imageClassName = "", + textBoxTitleClassName = "", + textBoxTitleImageWrapperClassName = "", + textBoxTitleImageClassName = "", + textBoxDescriptionClassName = "", + cardNameClassName = "", + quantityControlsClassName = "", + gridClassName = "", + carouselClassName = "", + controlsClassName = "", + textBoxClassName = "", + textBoxTagClassName = "", + textBoxButtonContainerClassName = "", + textBoxButtonClassName = "", + textBoxButtonTextClassName = "", +}: ProductCardThreeProps) => { + const theme = useTheme(); + const router = useRouter(); + const { products: fetchedProducts, isLoading } = useProducts(); + const isFromApi = fetchedProducts.length > 0; + const products = (isFromApi ? fetchedProducts : productsProp) as ProductCard[]; + const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle); + + const handleProductClick = useCallback((product: ProductCard) => { + if (isFromApi) { + router.push(`/shop/${product.id}`); + } else { + product.onProductClick?.(); + } + }, [isFromApi, router]); + + if (isLoading && !productsProp) { + return ( +
+

Loading products...

+
+ ); + } + + if (!products || products.length === 0) { + return null; + } + + return ( + + {products?.map((product, index) => ( + handleProductClick(product) }} + shouldUseLightText={shouldUseLightText} + isFromApi={isFromApi} + cardClassName={cardClassName} + imageClassName={imageClassName} + cardNameClassName={cardNameClassName} + quantityControlsClassName={quantityControlsClassName} + /> + ))} + + ); +}; + +ProductCardThree.displayName = "ProductCardThree"; + export default ProductCardThree;