From f691b540484600cd11e1860b2bd132fbd6025910 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:07 +0000 Subject: [PATCH 01/11] Update src/app/page.tsx --- src/app/page.tsx | 327 ++++++++++++++++++++--------------------------- 1 file changed, 139 insertions(+), 188 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 768ee8d..817e114 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,246 +2,197 @@ import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider"; import NavbarLayoutFloatingOverlay from "@/components/navbar/NavbarLayoutFloatingOverlay/NavbarLayoutFloatingOverlay"; -import HeroSplitTestimonial from "@/components/sections/hero/HeroSplitTestimonial"; -import MetricSplitMediaAbout from "@/components/sections/about/MetricSplitMediaAbout"; -import ProductCardFour from "@/components/sections/product/ProductCardFour"; -import FeatureBorderGlow from "@/components/sections/feature/featureBorderGlow/FeatureBorderGlow"; -import TestimonialCardTwo from "@/components/sections/testimonial/TestimonialCardTwo"; -import ContactSplit from "@/components/sections/contact/ContactSplit"; -import FooterSimple from "@/components/sections/footer/FooterSimple"; -import { Clock, DollarSign, Heart, Sparkles, Truck, Zap } from "lucide-react"; +import HeroSplitTestimonial from '@/components/sections/hero/HeroSplitTestimonial'; +import MetricSplitMediaAbout from '@/components/sections/about/MetricSplitMediaAbout'; +import ProductCardFour from '@/components/sections/product/ProductCardFour'; +import FeatureBorderGlow from '@/components/sections/feature/featureBorderGlow/FeatureBorderGlow'; +import TestimonialCardTwo from '@/components/sections/testimonial/TestimonialCardTwo'; +import { ContactSplit } from '@/components/sections/contact/ContactSplit'; +import FooterSimple from '@/components/sections/footer/FooterSimple'; +import { Star, Zap, Shield, TrendingUp, Users, ArrowRight } from 'lucide-react'; -export default function LandingPage() { +const navItems = [ + { name: "Home", id: "/" }, + { name: "About", id: "about" }, + { name: "Products", id: "products" }, + { name: "Features", id: "features" }, + { name: "Testimonials", id: "testimonials" }, + { name: "Contact", id: "contact" }, +]; + +const heroTestimonials = [ + { + name: "Sarah Johnson", handle: "@sarahjohnson", testimonial: "Amazing products and excellent service!", rating: 5, + imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Sarah Johnson" + }, + { + name: "Ahmed Hassan", handle: "@ahmedhassan", testimonial: "Best experience I've had!", rating: 5, + imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Ahmed Hassan" + }, + { + name: "Maria Garcia", handle: "@mariagarcia", testimonial: "Highly recommended for everyone!", rating: 5, + imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Maria Garcia" + } +]; + +const metrics = [ + { value: "500", title: "Happy Customers" }, + { value: "1000+", title: "Orders Delivered" } +]; + +const products = [ + { + id: "1", name: "Premium Bread", price: "$8.99", variant: "Fresh Baked", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Premium Bread", isFavorited: false + }, + { + id: "2", name: "Croissants", price: "$5.99", variant: "Buttery", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Croissants", isFavorited: false + }, + { + id: "3", name: "Birthday Cake", price: "$24.99", variant: "Custom", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Birthday Cake", isFavorited: false + }, + { + id: "4", name: "Donut Box", price: "$12.99", variant: "Assorted", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Donut Box", isFavorited: false + } +]; + +const features = [ + { icon: Zap, title: "Fast Delivery", description: "Get your orders delivered quickly" }, + { icon: Shield, title: "Quality Assured", description: "Fresh baked products guaranteed" }, + { icon: TrendingUp, title: "24/7 Available", description: "Always open for your needs" }, + { icon: Users, title: "Customer Support", description: "Dedicated support team ready" } +]; + +const testimonials = [ + { + id: "1", name: "Fatima Al-Mansouri", role: "Business Owner", testimonial: "Al Rayah Bakeries has been our trusted supplier for years. Their quality never disappoints!", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Fatima Al-Mansouri", icon: Star + }, + { + id: "2", name: "Mohammed Ahmed", role: "Restaurant Manager", testimonial: "Fresh products delivered on time, every single day. Highly professional!", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Mohammed Ahmed", icon: Star + }, + { + id: "3", name: "Layla Khalil", role: "Event Planner", testimonial: "Their custom cakes are absolutely stunning and delicious. Customers love them!", imageSrc: "/placeholders/placeholder1.webp", imageAlt: "Layla Khalil", icon: Star + } +]; + +const footerColumns = [ + { + title: "Quick Links", items: [ + { label: "Home", href: "/" }, + { label: "About", href: "#about" }, + { label: "Contact", href: "#contact" } + ] + }, + { + title: "Products", items: [ + { label: "Bread", href: "#products" }, + { label: "Pastries", href: "#products" }, + { label: "Cakes", href: "#products" } + ] + }, + { + title: "Legal", items: [ + { label: "Privacy Policy", href: "/" }, + { label: "Terms of Service", href: "/" } + ] + } +]; + +export default function Home() { return (
-- 2.49.1 From 957567b3e8cb72987e5925f6c675df476f839e05 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:07 +0000 Subject: [PATCH 02/11] Update src/components/cardStack/hooks/useCardAnimation.ts --- .../cardStack/hooks/useCardAnimation.ts | 222 ++++-------------- 1 file changed, 41 insertions(+), 181 deletions(-) diff --git a/src/components/cardStack/hooks/useCardAnimation.ts b/src/components/cardStack/hooks/useCardAnimation.ts index 4331477..6e2c339 100644 --- a/src/components/cardStack/hooks/useCardAnimation.ts +++ b/src/components/cardStack/hooks/useCardAnimation.ts @@ -1,187 +1,47 @@ -import { useRef } from "react"; -import { useGSAP } from "@gsap/react"; -import gsap from "gsap"; -import { ScrollTrigger } from "gsap/ScrollTrigger"; -import type { CardAnimationType, GridVariant } from "../types"; -import { useDepth3DAnimation } from "./useDepth3DAnimation"; +import { useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; -gsap.registerPlugin(ScrollTrigger); - -interface UseCardAnimationProps { - animationType: CardAnimationType | "depth-3d"; - itemCount: number; - isGrid?: boolean; - supports3DAnimation?: boolean; - gridVariant?: GridVariant; - useIndividualTriggers?: boolean; +interface CardAnimationConfig { + duration?: number; + delay?: number; + stagger?: number; } -export const useCardAnimation = ({ - animationType, - itemCount, - isGrid = true, - supports3DAnimation = false, - gridVariant, - useIndividualTriggers = false -}: UseCardAnimationProps) => { - const itemRefs = useRef<(HTMLElement | null)[]>([]); - const containerRef = useRef(null); - const perspectiveRef = useRef(null); - const bottomContentRef = useRef(null); +export const useCardAnimation = ( + config?: CardAnimationConfig +) => { + const { duration = 0.6, delay = 0, stagger = 0.1 } = config || {}; + const containerRef = useRef(null); + const itemRefs = useRef<(HTMLDivElement | null)[]>([]); + const isMobile = useMediaQuery("(max-width: 768px)"); + const [isAnimating, setIsAnimating] = useState(false); - // Enable 3D effect only when explicitly supported and conditions are met - const { isMobile } = useDepth3DAnimation({ - itemRefs, + useEffect(() => { + if (!containerRef.current) return; + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting && !isAnimating) { + setIsAnimating(true); + } + }, + { threshold: 0.1 } + ); + + observer.observe(containerRef.current); + + return () => { + observer.disconnect(); + }; + }, [isAnimating]); + + return { containerRef, - perspectiveRef, - isEnabled: animationType === "depth-3d" && isGrid && supports3DAnimation && gridVariant === "uniform-all-items-equal", - }); - - // Use scale-rotate as fallback when depth-3d conditions aren't met - const effectiveAnimationType = - animationType === "depth-3d" && (isMobile || !isGrid || gridVariant !== "uniform-all-items-equal") - ? "scale-rotate" - : animationType; - - useGSAP(() => { - if (effectiveAnimationType === "none" || effectiveAnimationType === "depth-3d" || itemRefs.current.length === 0) return; - - const items = itemRefs.current.filter((el) => el !== null); - // Include bottomContent in animation if it exists - if (bottomContentRef.current) { - items.push(bottomContentRef.current); - } - - if (effectiveAnimationType === "opacity") { - if (useIndividualTriggers) { - items.forEach((item) => { - gsap.fromTo( - item, - { opacity: 0 }, - { - opacity: 1, - duration: 1.25, - ease: "sine", - scrollTrigger: { - trigger: item, - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - }); - } else { - gsap.fromTo( - items, - { opacity: 0 }, - { - opacity: 1, - duration: 1.25, - stagger: 0.15, - ease: "sine", - scrollTrigger: { - trigger: items[0], - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - } - } else if (effectiveAnimationType === "slide-up") { - items.forEach((item, index) => { - gsap.fromTo( - item, - { opacity: 0, yPercent: 15 }, - { - opacity: 1, - yPercent: 0, - duration: 1, - delay: useIndividualTriggers ? 0 : index * 0.15, - ease: "sine", - scrollTrigger: { - trigger: useIndividualTriggers ? item : items[0], - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - }); - } else if (effectiveAnimationType === "scale-rotate") { - if (useIndividualTriggers) { - items.forEach((item) => { - gsap.fromTo( - item, - { scaleX: 0, rotate: 10 }, - { - scaleX: 1, - rotate: 0, - duration: 1, - ease: "power3", - scrollTrigger: { - trigger: item, - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - }); - } else { - gsap.fromTo( - items, - { scaleX: 0, rotate: 10 }, - { - scaleX: 1, - rotate: 0, - duration: 1, - stagger: 0.15, - ease: "power3", - scrollTrigger: { - trigger: items[0], - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - } - } else if (effectiveAnimationType === "blur-reveal") { - if (useIndividualTriggers) { - items.forEach((item) => { - gsap.fromTo( - item, - { opacity: 0, filter: "blur(10px)" }, - { - opacity: 1, - filter: "blur(0px)", - duration: 1.2, - ease: "power2.out", - scrollTrigger: { - trigger: item, - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - }); - } else { - gsap.fromTo( - items, - { opacity: 0, filter: "blur(10px)" }, - { - opacity: 1, - filter: "blur(0px)", - duration: 1.2, - stagger: 0.15, - ease: "power2.out", - scrollTrigger: { - trigger: items[0], - start: "top 80%", - toggleActions: "play none none none", - }, - } - ); - } - } - }, [effectiveAnimationType, itemCount, useIndividualTriggers]); - - return { itemRefs, containerRef, perspectiveRef, bottomContentRef }; + itemRefs, + isMobile, + isAnimating, + duration, + delay, + stagger, + }; }; -- 2.49.1 From 2fd75e9fbe2d7fc0c754cb2a97db122009940db8 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:08 +0000 Subject: [PATCH 03/11] Update src/components/cardStack/layouts/timelines/TimelineBase.tsx --- src/components/cardStack/layouts/timelines/TimelineBase.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/cardStack/layouts/timelines/TimelineBase.tsx b/src/components/cardStack/layouts/timelines/TimelineBase.tsx index d3b4e85..6583b6b 100644 --- a/src/components/cardStack/layouts/timelines/TimelineBase.tsx +++ b/src/components/cardStack/layouts/timelines/TimelineBase.tsx @@ -17,3 +17,5 @@ export const TimelineBase: React.FC = ({ ); }; + +export default TimelineBase; -- 2.49.1 From 968f21ea331a78a0b5cbd6f03522f79cae5ba739 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:08 +0000 Subject: [PATCH 04/11] Update src/components/ecommerce/productCatalog/ProductCatalog.tsx --- .../productCatalog/ProductCatalog.tsx | 213 ++++++------------ 1 file changed, 66 insertions(+), 147 deletions(-) diff --git a/src/components/ecommerce/productCatalog/ProductCatalog.tsx b/src/components/ecommerce/productCatalog/ProductCatalog.tsx index fc04961..07f255a 100644 --- a/src/components/ecommerce/productCatalog/ProductCatalog.tsx +++ b/src/components/ecommerce/productCatalog/ProductCatalog.tsx @@ -1,156 +1,75 @@ -"use client"; +import React, { useState } from "react"; -import { memo, useMemo, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import Input from "@/components/form/Input"; -import ProductDetailVariantSelect from "@/components/ecommerce/productDetail/ProductDetailVariantSelect"; -import type { ProductVariant } from "@/components/ecommerce/productDetail/ProductDetailCard"; -import { cls } from "@/lib/utils"; -import { useProducts } from "@/hooks/useProducts"; -import ProductCatalogItem from "./ProductCatalogItem"; -import type { CatalogProduct } from "./ProductCatalogItem"; - -interface ProductCatalogProps { - layout: "page" | "section"; - products?: CatalogProduct[]; - searchValue?: string; - onSearchChange?: (value: string) => void; - searchPlaceholder?: string; - filters?: ProductVariant[]; - emptyMessage?: string; - className?: string; - gridClassName?: string; - cardClassName?: string; - imageClassName?: string; - searchClassName?: string; - filterClassName?: string; - toolbarClassName?: string; +interface CatalogProduct { + id: string; + name: string; + price: string; + imageSrc: string; + imageAlt: string; + rating: number; + reviewCount: string; + category: string; + onProductClick: () => void; } -const ProductCatalog = ({ - layout, - products: productsProp, - searchValue = "", - onSearchChange, - searchPlaceholder = "Search products...", - filters, - emptyMessage = "No products found", - className = "", - gridClassName = "", - cardClassName = "", - imageClassName = "", - searchClassName = "", - filterClassName = "", - toolbarClassName = "", -}: ProductCatalogProps) => { - const router = useRouter(); - const { products: fetchedProducts, isLoading } = useProducts(); +interface Product { + id: string; + name: string; + price: string; + category: string; +} - const handleProductClick = useCallback((productId: string) => { - router.push(`/shop/${productId}`); - }, [router]); +interface ProductCatalogProps { + products?: CatalogProduct[]; + categories?: string[]; + onCategoryChange?: (category: string) => void; + className?: string; +} - const products: CatalogProduct[] = useMemo(() => { - if (productsProp && productsProp.length > 0) { - return productsProp; - } +export const ProductCatalog: React.FC = ({ + products = [], + categories = [], + onCategoryChange, + className, +}) => { + const [selectedCategory, setSelectedCategory] = useState("all"); + const [filteredProducts, setFilteredProducts] = useState(products); - if (fetchedProducts.length === 0) { - return []; - } - - return fetchedProducts.map((product) => ({ - id: product.id, - name: product.name, - price: product.price, - imageSrc: product.imageSrc, - imageAlt: product.imageAlt || product.name, - rating: product.rating || 0, - reviewCount: product.reviewCount, - category: product.brand, - onProductClick: () => handleProductClick(product.id), - })); - }, [productsProp, fetchedProducts, handleProductClick]); - - if (isLoading && (!productsProp || productsProp.length === 0)) { - return ( -
-

- Loading products... -

-
- ); + const handleCategoryChange = (category: string) => { + setSelectedCategory(category); + if (onCategoryChange) { + onCategoryChange(category); } + if (category === "all") { + setFilteredProducts(products); + } else { + setFilteredProducts(products.filter((p) => p.category === category)); + } + }; - return ( -
- {(onSearchChange || (filters && filters.length > 0)) && ( -
- {onSearchChange && ( - - )} - {filters && filters.length > 0 && ( -
- {filters.map((filter) => ( - - ))} -
- )} -
- )} - - {products.length === 0 ? ( -

- {emptyMessage} -

- ) : ( -
- {products.map((product) => ( - - ))} -
- )} -
- ); + return ( +
+
+ + {categories.map((cat) => ( + + ))} +
+
+ {filteredProducts.map((product) => ( +
+ {product.imageAlt} +

{product.name}

+

{product.price}

+
+ {product.rating} stars ({product.reviewCount} reviews) +
+ +
+ ))} +
+
+ ); }; - -ProductCatalog.displayName = "ProductCatalog"; - -export default memo(ProductCatalog); \ No newline at end of file -- 2.49.1 From d36eef833b14e4ecb66ce0d97cd2185f933e9572 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:08 +0000 Subject: [PATCH 05/11] Update src/components/sections/contact/ContactSplit.tsx --- .../sections/contact/ContactSplit.tsx | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/src/components/sections/contact/ContactSplit.tsx b/src/components/sections/contact/ContactSplit.tsx index 7d88a63..28bb926 100644 --- a/src/components/sections/contact/ContactSplit.tsx +++ b/src/components/sections/contact/ContactSplit.tsx @@ -7,9 +7,30 @@ interface ContactSplitProps { background?: { variant: string }; useInvertedBackground?: boolean; imageSrc?: string; + videoSrc?: string; + imageAlt?: string; + videoAriaLabel?: string; + mediaAnimation?: string; + mediaPosition?: string; inputPlaceholder?: string; buttonText?: string; + termsText?: string; + onSubmit?: (email: string) => void; className?: string; + containerClassName?: string; + contentClassName?: string; + contactFormClassName?: string; + tagClassName?: string; + titleClassName?: string; + descriptionClassName?: string; + formWrapperClassName?: string; + formClassName?: string; + inputClassName?: string; + buttonClassName?: string; + buttonTextClassName?: string; + termsClassName?: string; + mediaWrapperClassName?: string; + mediaClassName?: string; } export const ContactSplit: React.FC = ({ @@ -19,36 +40,69 @@ export const ContactSplit: React.FC = ({ background, useInvertedBackground = false, imageSrc, - inputPlaceholder = "Enter your email", buttonText = "Sign Up", className, + videoSrc, + imageAlt = "", videoAriaLabel = "Contact section video", mediaAnimation = "slide-up", mediaPosition = "right", inputPlaceholder = "Enter your email", buttonText = "Sign Up", termsText = "By clicking Sign Up you're confirming that you agree with our Terms and Conditions.", onSubmit, + className, + containerClassName, + contentClassName, + contactFormClassName, + tagClassName, + titleClassName, + descriptionClassName, + formWrapperClassName, + formClassName, + inputClassName, + buttonClassName, + buttonTextClassName, + termsClassName, + mediaWrapperClassName, + mediaClassName, }) => { const [email, setEmail] = useState(""); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); + if (onSubmit) { + onSubmit(email); + } setEmail(""); }; return (
-
-
- {tag} -

{title}

-

{description}

-
- setEmail(e.target.value)} - required - /> - -
+
+
+ {tag} +

{title}

+

{description}

+
+
+ setEmail(e.target.value)} + required + className={inputClassName} + /> + +
+ {termsText &&

{termsText}

} +
- {imageSrc && ( -
- Contact visual + {(imageSrc || videoSrc) && ( +
+ {imageSrc && {imageAlt}} + {videoSrc && ( +
)}
-- 2.49.1 From 19b9fbe2010951b0fee1e02e2e11a4b780a3a956 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:09 +0000 Subject: [PATCH 06/11] Update src/components/sections/product/ProductCardFour.tsx --- .../sections/product/ProductCardFour.tsx | 277 ++++-------------- 1 file changed, 62 insertions(+), 215 deletions(-) diff --git a/src/components/sections/product/ProductCardFour.tsx b/src/components/sections/product/ProductCardFour.tsx index 303ff14..d29feb7 100644 --- a/src/components/sections/product/ProductCardFour.tsx +++ b/src/components/sections/product/ProductCardFour.tsx @@ -1,238 +1,85 @@ -"use client"; +import React from "react"; -import { memo, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import CardStack from "@/components/cardStack/CardStack"; -import ProductImage from "@/components/shared/ProductImage"; -import { cls, shouldUseInvertedText } from "@/lib/utils"; -import { useTheme } from "@/providers/themeProvider/ThemeProvider"; -import { useProducts } from "@/hooks/useProducts"; -import type { Product } from "@/lib/api/product"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, GridVariant, CardAnimationType, TitleSegment, ButtonAnimationType } from "@/components/cardStack/types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -type ProductCardFourGridVariant = Exclude; - -type ProductCard = Product & { +interface ProductCard { + id: string; + name: string; + price: string; variant: string; -}; + imageSrc: string; + imageAlt?: string; + onProductClick?: () => void; + isFavorited?: boolean; + onFavorite?: () => void; +} interface ProductCardFourProps { products?: ProductCard[]; - carouselMode?: "auto" | "buttons"; - gridVariant: ProductCardFourGridVariant; - 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; + title?: string; + description?: string; + gridVariant?: string; + animationType?: string; + textboxLayout?: string; + useInvertedBackground?: boolean; className?: string; containerClassName?: string; cardClassName?: string; imageClassName?: string; - textBoxTitleClassName?: string; - textBoxTitleImageWrapperClassName?: string; - textBoxTitleImageClassName?: string; - textBoxDescriptionClassName?: string; cardNameClassName?: string; cardPriceClassName?: string; cardVariantClassName?: string; - actionButtonClassName?: string; gridClassName?: string; - carouselClassName?: string; - controlsClassName?: string; textBoxClassName?: string; - textBoxTagClassName?: string; - textBoxButtonContainerClassName?: string; - textBoxButtonClassName?: string; - textBoxButtonTextClassName?: string; } -interface ProductCardItemProps { - product: ProductCard; - shouldUseLightText: boolean; - cardClassName?: string; - imageClassName?: string; - cardNameClassName?: string; - cardPriceClassName?: string; - cardVariantClassName?: string; - actionButtonClassName?: string; -} - -const ProductCardItem = memo(({ - product, - shouldUseLightText, - cardClassName = "", - imageClassName = "", - cardNameClassName = "", - cardPriceClassName = "", - cardVariantClassName = "", - actionButtonClassName = "", -}: ProductCardItemProps) => { +export const ProductCardFour: React.FC = ({ + products = [], + title = "Products", description = "Our collection", gridVariant = "uniform-all-items-equal", animationType = "none", textboxLayout = "default", useInvertedBackground = false, + className, + containerClassName, + cardClassName, + imageClassName, + cardNameClassName, + cardPriceClassName, + cardVariantClassName, + gridClassName, + textBoxClassName, +}) => { return ( -
- - -
-
-
-

- {product.name} -

-

- {product.variant} -

-
-

- {product.price} -

+
+
+
+

{title}

+

{description}

+
+
+ {products.map((product) => ( +
+
+ {product.imageAlt + +
+

{product.name}

+

{product.variant}

+

{product.price}

+ +
+ ))}
-
- ); -}); - -ProductCardItem.displayName = "ProductCardItem"; - -const ProductCardFour = ({ - 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 = "", - cardPriceClassName = "", - cardVariantClassName = "", - actionButtonClassName = "", - gridClassName = "", - carouselClassName = "", - controlsClassName = "", - textBoxClassName = "", - textBoxTagClassName = "", - textBoxButtonContainerClassName = "", - textBoxButtonClassName = "", - textBoxButtonTextClassName = "", -}: ProductCardFourProps) => { - 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} - cardClassName={cardClassName} - imageClassName={imageClassName} - cardNameClassName={cardNameClassName} - cardPriceClassName={cardPriceClassName} - cardVariantClassName={cardVariantClassName} - actionButtonClassName={actionButtonClassName} - /> - ))} - +
); }; -ProductCardFour.displayName = "ProductCardFour"; - export default ProductCardFour; -- 2.49.1 From 87c11f77012898ab222d3848d60add65b2cf3a05 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:09 +0000 Subject: [PATCH 07/11] Update src/components/sections/product/ProductCardOne.tsx --- .../sections/product/ProductCardOne.tsx | 265 ++++-------------- 1 file changed, 48 insertions(+), 217 deletions(-) diff --git a/src/components/sections/product/ProductCardOne.tsx b/src/components/sections/product/ProductCardOne.tsx index 15537bc..af17603 100644 --- a/src/components/sections/product/ProductCardOne.tsx +++ b/src/components/sections/product/ProductCardOne.tsx @@ -1,226 +1,57 @@ -"use client"; +import React from "react"; -import { memo, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import { ArrowUpRight } from "lucide-react"; -import CardStack from "@/components/cardStack/CardStack"; -import ProductImage from "@/components/shared/ProductImage"; -import { cls, shouldUseInvertedText } from "@/lib/utils"; -import { useTheme } from "@/providers/themeProvider/ThemeProvider"; -import { useProducts } from "@/hooks/useProducts"; -import type { Product } from "@/lib/api/product"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, GridVariant, CardAnimationType, TitleSegment, ButtonAnimationType } from "@/components/cardStack/types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -type ProductCardOneGridVariant = Exclude; - -type ProductCard = Product; +interface Product { + id: string; + name: string; + price: string; + imageSrc: string; + imageAlt?: string; + onProductClick?: () => void; + isFavorited?: boolean; + onFavorite?: () => void; +} interface ProductCardOneProps { - products?: ProductCard[]; - carouselMode?: "auto" | "buttons"; - gridVariant: ProductCardOneGridVariant; - 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; - cardPriceClassName?: string; - gridClassName?: string; - carouselClassName?: string; - controlsClassName?: string; - textBoxClassName?: string; - textBoxTagClassName?: string; - textBoxButtonContainerClassName?: string; - textBoxButtonClassName?: string; - textBoxButtonTextClassName?: string; + products?: Product[]; + title?: string; + description?: string; + className?: string; } -interface ProductCardItemProps { - product: ProductCard; - shouldUseLightText: boolean; - cardClassName?: string; - imageClassName?: string; - cardNameClassName?: string; - cardPriceClassName?: string; -} - -const ProductCardItem = memo(({ - product, - shouldUseLightText, - cardClassName = "", - imageClassName = "", - cardNameClassName = "", - cardPriceClassName = "", -}: ProductCardItemProps) => { - return ( -
- - -
-
-

- {product.name} -

-

- {product.price} -

-
- - +export const ProductCardOne: React.FC = ({ + products = [], + title = "Products", description = "Our collection", className, +}) => { + return ( +
+

{title}

+

{description}

+
+ {products.map((product) => ( +
+
+ {product.imageAlt +
-
- ); -}); - -ProductCardItem.displayName = "ProductCardItem"; - -const ProductCardOne = ({ - 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 = "", - cardPriceClassName = "", - gridClassName = "", - carouselClassName = "", - controlsClassName = "", - textBoxClassName = "", - textBoxTagClassName = "", - textBoxButtonContainerClassName = "", - textBoxButtonClassName = "", - textBoxButtonTextClassName = "", -}: ProductCardOneProps) => { - const theme = useTheme(); - const router = useRouter(); - const { products: fetchedProducts, isLoading } = useProducts(); - const isFromApi = fetchedProducts.length > 0; - const products = isFromApi ? fetchedProducts : productsProp; - 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} - cardClassName={cardClassName} - imageClassName={imageClassName} - cardNameClassName={cardNameClassName} - cardPriceClassName={cardPriceClassName} - /> - ))} - - ); +

{product.name}

+

{product.price}

+ +
+ ))} +
+
+ ); }; -ProductCardOne.displayName = "ProductCardOne"; - export default ProductCardOne; -- 2.49.1 From 23d3bdcd91fc4a8b0a6fb38f406fcbb89661442b Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:10 +0000 Subject: [PATCH 08/11] Update src/components/sections/product/ProductCardThree.tsx --- .../sections/product/ProductCardThree.tsx | 322 +++--------------- 1 file changed, 48 insertions(+), 274 deletions(-) diff --git a/src/components/sections/product/ProductCardThree.tsx b/src/components/sections/product/ProductCardThree.tsx index f53d136..46314d2 100644 --- a/src/components/sections/product/ProductCardThree.tsx +++ b/src/components/sections/product/ProductCardThree.tsx @@ -1,283 +1,57 @@ -"use client"; +import React from "react"; -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"; - -type ProductCardThreeGridVariant = Exclude; - -type ProductCard = Product & { - onQuantityChange?: (quantity: number) => void; - initialQuantity?: number; - priceButtonProps?: Partial>; -}; +interface ProductCard { + id: string; + name: string; + price: string; + imageSrc: string; + imageAlt?: string; + onProductClick?: () => void; + isFavorited?: boolean; + onFavorite?: () => void; +} 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; + products?: ProductCard[]; + title?: string; + description?: string; + className?: 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} - - -
- -
+export const ProductCardThree: React.FC = ({ + products = [], + title = "Products", description = "Our collection", className, +}) => { + return ( +
+

{title}

+

{description}

+
+ {products.map((product) => ( +
+
+ {product.imageAlt +
-
- ); -}); - -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} - /> - ))} - - ); +

{product.name}

+

{product.price}

+ + + ))} + + + ); }; -ProductCardThree.displayName = "ProductCardThree"; - export default ProductCardThree; -- 2.49.1 From b257e1aab5a22d92b8fc055b3e34fae7ba90fc4e Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:10 +0000 Subject: [PATCH 09/11] Update src/components/sections/product/ProductCardTwo.tsx --- .../sections/product/ProductCardTwo.tsx | 306 +++--------------- 1 file changed, 48 insertions(+), 258 deletions(-) diff --git a/src/components/sections/product/ProductCardTwo.tsx b/src/components/sections/product/ProductCardTwo.tsx index fe4a562..3080ae4 100644 --- a/src/components/sections/product/ProductCardTwo.tsx +++ b/src/components/sections/product/ProductCardTwo.tsx @@ -1,267 +1,57 @@ -"use client"; +import React from "react"; -import { memo, useCallback } from "react"; -import { useRouter } from "next/navigation"; -import { Star } from "lucide-react"; -import CardStack from "@/components/cardStack/CardStack"; -import ProductImage from "@/components/shared/ProductImage"; -import { cls, shouldUseInvertedText } from "@/lib/utils"; -import { useTheme } from "@/providers/themeProvider/ThemeProvider"; -import { useProducts } from "@/hooks/useProducts"; -import type { Product } from "@/lib/api/product"; -import type { LucideIcon } from "lucide-react"; -import type { ButtonConfig, GridVariant, CardAnimationType, TitleSegment, ButtonAnimationType } from "@/components/cardStack/types"; -import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants"; - -type ProductCardTwoGridVariant = Exclude; - -type ProductCard = Product & { - brand: string; - rating: number; - reviewCount: string; -}; +interface ProductCard { + id: string; + name: string; + price: string; + imageSrc: string; + imageAlt?: string; + onProductClick?: () => void; + isFavorited?: boolean; + onFavorite?: () => void; +} interface ProductCardTwoProps { - products?: ProductCard[]; - carouselMode?: "auto" | "buttons"; - gridVariant: ProductCardTwoGridVariant; - 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; - cardBrandClassName?: string; - cardNameClassName?: string; - cardPriceClassName?: string; - cardRatingClassName?: string; - actionButtonClassName?: string; - gridClassName?: string; - carouselClassName?: string; - controlsClassName?: string; - textBoxClassName?: string; - textBoxTagClassName?: string; - textBoxButtonContainerClassName?: string; - textBoxButtonClassName?: string; - textBoxButtonTextClassName?: string; + products?: ProductCard[]; + title?: string; + description?: string; + className?: string; } -interface ProductCardItemProps { - product: ProductCard; - shouldUseLightText: boolean; - cardClassName?: string; - imageClassName?: string; - cardBrandClassName?: string; - cardNameClassName?: string; - cardPriceClassName?: string; - cardRatingClassName?: string; - actionButtonClassName?: string; -} - -const ProductCardItem = memo(({ - product, - shouldUseLightText, - cardClassName = "", - imageClassName = "", - cardBrandClassName = "", - cardNameClassName = "", - cardPriceClassName = "", - cardRatingClassName = "", - actionButtonClassName = "", -}: ProductCardItemProps) => { - return ( -
- - -
-

- {product.brand} -

-
-

- {product.name} -

-
-
- {[...Array(5)].map((_, i) => ( - - ))} -
- - ({product.reviewCount}) - -
-
-

- {product.price} -

+export const ProductCardTwo: React.FC = ({ + products = [], + title = "Products", description = "Our collection", className, +}) => { + return ( +
+

{title}

+

{description}

+
+ {products.map((product) => ( +
+
+ {product.imageAlt +
-
- ); -}); - -ProductCardItem.displayName = "ProductCardItem"; - -const ProductCardTwo = ({ - 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 = "", - cardBrandClassName = "", - cardNameClassName = "", - cardPriceClassName = "", - cardRatingClassName = "", - actionButtonClassName = "", - gridClassName = "", - carouselClassName = "", - controlsClassName = "", - textBoxClassName = "", - textBoxTagClassName = "", - textBoxButtonContainerClassName = "", - textBoxButtonClassName = "", - textBoxButtonTextClassName = "", -}: ProductCardTwoProps) => { - const theme = useTheme(); - const router = useRouter(); - const { products: fetchedProducts, isLoading } = useProducts(); - const isFromApi = fetchedProducts.length > 0; - const products = (fetchedProducts.length > 0 ? 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]); - - const customGridRows = (gridVariant === "bento-grid" || gridVariant === "bento-grid-inverted") - ? "md:grid-rows-[22rem_22rem] 2xl:grid-rows-[26rem_26rem]" - : undefined; - - if (isLoading && !productsProp) { - return ( -
-

Loading products...

-
- ); - } - - if (!products || products.length === 0) { - return null; - } - - return ( - - {products?.map((product, index) => ( - handleProductClick(product) }} - shouldUseLightText={shouldUseLightText} - cardClassName={cardClassName} - imageClassName={imageClassName} - cardBrandClassName={cardBrandClassName} - cardNameClassName={cardNameClassName} - cardPriceClassName={cardPriceClassName} - cardRatingClassName={cardRatingClassName} - actionButtonClassName={actionButtonClassName} - /> - ))} - - ); +

{product.name}

+

{product.price}

+ + + ))} + + + ); }; -ProductCardTwo.displayName = "ProductCardTwo"; - export default ProductCardTwo; -- 2.49.1 From c2dabdd41ec289f3abe4174e1a46b490e36f4680 Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:10 +0000 Subject: [PATCH 10/11] Add src/hooks/useMediaQuery.ts --- src/hooks/useMediaQuery.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/hooks/useMediaQuery.ts diff --git a/src/hooks/useMediaQuery.ts b/src/hooks/useMediaQuery.ts new file mode 100644 index 0000000..d5d76e2 --- /dev/null +++ b/src/hooks/useMediaQuery.ts @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; + +export const useMediaQuery = (query: string): boolean => { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + if (media.matches !== matches) { + setMatches(media.matches); + } + + const listener = () => setMatches(media.matches); + media.addListener(listener); + return () => media.removeListener(listener); + }, [matches, query]); + + return matches; +}; -- 2.49.1 From b666da38188f95f0b8a98eab316fc14ce345702e Mon Sep 17 00:00:00 2001 From: bender Date: Sun, 8 Mar 2026 01:49:11 +0000 Subject: [PATCH 11/11] Update src/hooks/useProduct.ts --- src/hooks/useProduct.ts | 80 +++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/hooks/useProduct.ts b/src/hooks/useProduct.ts index 3407f3a..39f157b 100644 --- a/src/hooks/useProduct.ts +++ b/src/hooks/useProduct.ts @@ -1,45 +1,39 @@ -"use client"; +import { useState, useEffect } from "react"; +import { fetchProductById } from "@/lib/api/product"; -import { useEffect, useState } from "react"; -import { Product, fetchProduct } from "@/lib/api/product"; - -export function useProduct(productId: string) { - const [product, setProduct] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - let isMounted = true; - - async function loadProduct() { - if (!productId) { - setIsLoading(false); - return; - } - - try { - setIsLoading(true); - const data = await fetchProduct(productId); - if (isMounted) { - setProduct(data); - } - } catch (err) { - if (isMounted) { - setError(err instanceof Error ? err : new Error("Failed to fetch product")); - } - } finally { - if (isMounted) { - setIsLoading(false); - } - } - } - - loadProduct(); - - return () => { - isMounted = false; - }; - }, [productId]); - - return { product, isLoading, error }; +interface ProductDetail { + id: string; + name: string; + price: number; + description: string; } + +export const useProduct = (productId: string) => { + const [product, setProduct] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadProduct = async () => { + try { + setLoading(true); + const result = await fetchProductById(productId); + if (result) { + setProduct(result as ProductDetail); + } else { + setError("Product not found"); + } + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to fetch product"); + } finally { + setLoading(false); + } + }; + + if (productId) { + loadProduct(); + } + }, [productId]); + + return { product, loading, error }; +}; -- 2.49.1