-
-
- {product.name}
-
-
- {product.variant}
-
+
+
+
{title}
+
{description}
+
+
+ {products.map((product) => (
+
+ {product.imageSrc && (
+

+ )}
+
{product.name}
+
${product.price}
+ {product.description && (
+
{product.description}
+ )}
-
- {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 (
-
- );
- }
-
- 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;
+}
diff --git a/src/components/sections/product/ProductCardOne.tsx b/src/components/sections/product/ProductCardOne.tsx
index 15537bc..3743b04 100644
--- a/src/components/sections/product/ProductCardOne.tsx
+++ b/src/components/sections/product/ProductCardOne.tsx
@@ -1,226 +1,35 @@
-"use client";
-
-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;
+import React from "react";
+import { Product } from "@/lib/api/product";
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;
}
-interface ProductCardItemProps {
- product: ProductCard;
- shouldUseLightText: boolean;
- cardClassName?: string;
- imageClassName?: string;
- cardNameClassName?: string;
- cardPriceClassName?: string;
+export default function ProductCardOne({
+ products = [],
+ title = "Products", description = "Our premium product collection"}: ProductCardOneProps) {
+ return (
+
+
+
{title}
+
{description}
+
+
+ {products.map((product) => (
+
+ {product.imageSrc && (
+

+ )}
+
{product.name}
+
${product.price}
+ {product.description && (
+
{product.description}
+ )}
+
+ ))}
+
+
+ );
}
-
-const ProductCardItem = memo(({
- product,
- shouldUseLightText,
- cardClassName = "",
- imageClassName = "",
- cardNameClassName = "",
- cardPriceClassName = "",
-}: ProductCardItemProps) => {
- return (
-
-
-
-
-
-
- {product.name}
-
-
- {product.price}
-
-
-
-
-
-
- );
-});
-
-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 (
-
- );
- }
-
- if (!products || products.length === 0) {
- return null;
- }
-
- return (
-
- {products?.map((product, index) => (
- handleProductClick(product) }}
- shouldUseLightText={shouldUseLightText}
- cardClassName={cardClassName}
- imageClassName={imageClassName}
- cardNameClassName={cardNameClassName}
- cardPriceClassName={cardPriceClassName}
- />
- ))}
-
- );
-};
-
-ProductCardOne.displayName = "ProductCardOne";
-
-export default ProductCardOne;
diff --git a/src/components/sections/product/ProductCardThree.tsx b/src/components/sections/product/ProductCardThree.tsx
index f53d136..b88bdb2 100644
--- a/src/components/sections/product/ProductCardThree.tsx
+++ b/src/components/sections/product/ProductCardThree.tsx
@@ -1,283 +1,35 @@
-"use client";
-
-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>;
-};
+import React from "react";
+import { Product } from "@/lib/api/product";
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?: Product[];
+ title?: string;
+ description?: string;
}
-
-interface ProductCardItemProps {
- product: ProductCard;
- shouldUseLightText: boolean;
- isFromApi: boolean;
- onBuyClick?: (productId: string, quantity: number) => void;
- cardClassName?: string;
- imageClassName?: string;
- cardNameClassName?: string;
- quantityControlsClassName?: string;
+export default function ProductCardThree({
+ products = [],
+ title = "Products", description = "Our premium product collection"}: ProductCardThreeProps) {
+ return (
+
+
+
{title}
+
{description}
+
+
+ {products.map((product) => (
+
+ {product.imageSrc && (
+

+ )}
+
{product.name}
+
${product.price}
+ {product.description && (
+
{product.description}
+ )}
+
+ ))}
+
+
+ );
}
-
-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 (
-
- );
- }
-
- 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;
diff --git a/src/components/sections/product/ProductCardTwo.tsx b/src/components/sections/product/ProductCardTwo.tsx
index fe4a562..4ceadb4 100644
--- a/src/components/sections/product/ProductCardTwo.tsx
+++ b/src/components/sections/product/ProductCardTwo.tsx
@@ -1,267 +1,35 @@
-"use client";
-
-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;
-};
+import React from "react";
+import { Product } from "@/lib/api/product";
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?: Product[];
+ title?: string;
+ description?: string;
}
-interface ProductCardItemProps {
- product: ProductCard;
- shouldUseLightText: boolean;
- cardClassName?: string;
- imageClassName?: string;
- cardBrandClassName?: string;
- cardNameClassName?: string;
- cardPriceClassName?: string;
- cardRatingClassName?: string;
- actionButtonClassName?: string;
+export default function ProductCardTwo({
+ products = [],
+ title = "Products", description = "Our premium product collection"}: ProductCardTwoProps) {
+ return (
+
+
+
{title}
+
{description}
+
+
+ {products.map((product) => (
+
+ {product.imageSrc && (
+

+ )}
+
{product.name}
+
${product.price}
+ {product.description && (
+
{product.description}
+ )}
+
+ ))}
+
+
+ );
}
-
-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}
-
-
-
- );
-});
-
-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 (
-
- );
- }
-
- 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}
- />
- ))}
-
- );
-};
-
-ProductCardTwo.displayName = "ProductCardTwo";
-
-export default ProductCardTwo;
diff --git a/src/hooks/useProduct.ts b/src/hooks/useProduct.ts
index 3407f3a..023b615 100644
--- a/src/hooks/useProduct.ts
+++ b/src/hooks/useProduct.ts
@@ -1,45 +1,28 @@
-"use client";
-
-import { useEffect, useState } from "react";
+import { useState, useEffect } 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);
+export const useProduct = (productId: string) => {
+ const [product, setProduct] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
- useEffect(() => {
- let isMounted = true;
+ useEffect(() => {
+ const fetch = async () => {
+ try {
+ setLoading(true);
+ const data = await fetchProduct(productId);
+ setProduct(data);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Unknown error");
+ } finally {
+ setLoading(false);
+ }
+ };
- async function loadProduct() {
- if (!productId) {
- setIsLoading(false);
- return;
- }
+ if (productId) {
+ fetch();
+ }
+ }, [productId]);
- 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 };
-}
+ return { product, loading, error };
+};
diff --git a/src/hooks/useProducts.ts b/src/hooks/useProducts.ts
index 53609fa..3b2b6d4 100644
--- a/src/hooks/useProducts.ts
+++ b/src/hooks/useProducts.ts
@@ -1,39 +1,26 @@
-"use client";
-
-import { useEffect, useState } from "react";
+import { useState, useEffect } from "react";
import { Product, fetchProducts } from "@/lib/api/product";
-export function useProducts() {
- const [products, setProducts] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
- const [error, setError] = useState(null);
+export const useProducts = () => {
+ const [products, setProducts] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
- useEffect(() => {
- let isMounted = true;
+ useEffect(() => {
+ const fetch = async () => {
+ try {
+ setLoading(true);
+ const data = await fetchProducts();
+ setProducts(data);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Unknown error");
+ } finally {
+ setLoading(false);
+ }
+ };
- async function loadProducts() {
- try {
- const data = await fetchProducts();
- if (isMounted) {
- setProducts(data);
- }
- } catch (err) {
- if (isMounted) {
- setError(err instanceof Error ? err : new Error("Failed to fetch products"));
- }
- } finally {
- if (isMounted) {
- setIsLoading(false);
- }
- }
- }
+ fetch();
+ }, []);
- loadProducts();
-
- return () => {
- isMounted = false;
- };
- }, []);
-
- return { products, isLoading, error };
-}
+ return { products, loading, error };
+};
diff --git a/src/lib/api/product.ts b/src/lib/api/product.ts
index 3e93c15..5941d50 100644
--- a/src/lib/api/product.ts
+++ b/src/lib/api/product.ts
@@ -1,6 +1,14 @@
-export const fetchProducts = async () => {
+export interface Product {
+ id: string;
+ name: string;
+ price: number;
+ description?: string;
+ imageSrc?: string;
+ category?: string;
+}
+
+export const fetchProducts = async (): Promise => {
try {
- // Fetch products from API
const response = await fetch("/api/products");
const data = await response.json();
return data;
@@ -10,9 +18,8 @@ export const fetchProducts = async () => {
}
};
-export const fetchProductDetail = async (id: string) => {
+export const fetchProduct = async (id: string): Promise => {
try {
- // Fetch single product from API
const response = await fetch(`/api/products/${id}`);
const data = await response.json();
return data;
@@ -21,3 +28,7 @@ export const fetchProductDetail = async (id: string) => {
return null;
}
};
+
+export const fetchProductDetail = async (id: string): Promise => {
+ return fetchProduct(id);
+};