'use client'; import { useState, useEffect, useMemo, useRef } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { catalogService, ProductResponse, CategoryResponse, BrandResponse, } from '@/services/api/catalogService'; import { formatCurrency, getCatalogPriceInfo, getImageUrl } from '@/lib/utils'; import { applyCatalogFilters, CATALOG_API_LIMIT, CATALOG_MAX_API_PAGES, CATALOG_PAGE_SIZE, CATALOG_PRICE_RANGES, getDisplayStarCount, productMatchesRating, type CatalogHighlight, } from '@/lib/catalogFilters'; import { LayoutGrid, List, Minus, Plus, Heart, Eye, GitCompare, ShoppingCart, Star, ChevronDown, } from 'lucide-react'; import { useCartStore } from '@/store/cartStore'; import { AnnouncementBar } from '@/layout/AnnouncementBar'; import { Header } from '@/layout/Header'; import { Navbar } from '@/layout/Navbar'; import { Footer } from '@/layout/Footer'; import { StickyHeaderSpacer } from '@/components/StickyHeaderSpacer'; import { FloatingButtons } from '@/components/FloatingButtons'; import { ProductCardSkeleton } from '@/components/ui/Skeleton'; import { mapProductCardToProductResponse } from '@/utils/productMapper'; import { addToCompareAndNavigate } from '@/lib/compare'; import BlurHashImage from '@/components/ui/BlurHashImage'; interface StandardCatalogProps { initialCategory?: CategoryResponse | null; categoryParam?: string | null; brandParam?: string | null; searchQuery?: string | null; } const HIGHLIGHTS: CatalogHighlight[] = [ 'All Products', 'Best Seller', 'New Arrivals', 'Sale', 'Hot Items', ]; type CollapseKey = 'categories' | 'brands' | 'highlight' | 'price' | 'rating'; function mapApiProduct(p: any): ProductResponse { return mapProductCardToProductResponse(p); } function CatalogProductCard({ product, viewMode, }: { product: ProductResponse; viewMode: 'grid' | 'list'; }) { const addToCart = useCartStore((s) => s.addToCart); const toggleWishlist = useCartStore((s) => s.toggleWishlist); const setQuickViewProduct = useCartStore((s) => s.setQuickViewProduct); const router = useRouter(); const wishlist = useCartStore((s) => s.wishlist) || []; const compareList = useCartStore((s) => s.compareList) || []; const variant = product.variants?.[0]; const { price, oldPrice, discount: mappedDiscount } = getCatalogPriceInfo(product); const compare = oldPrice || 0; const discount = mappedDiscount || (compare > price ? Math.round(((compare - price) / compare) * 100) : 0); const image = getImageUrl(product.images[0]?.image_url); const rating = getDisplayStarCount(product); const isWishlisted = wishlist.some((i) => i.product_id === product.product_id); const isCompared = compareList.some((i) => i.product_id === product.product_id); const hasOptions = (product.variants?.length || 0) > 1; const ctaLabel = hasOptions ? 'Select Options' : 'Add To Cart'; const handleCart = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!variant) return; if (hasOptions) { window.location.href = `/products/${product.slug}`; return; } void addToCart(product, variant, 1); }; if (viewMode === 'list') { return (
{discount > 0 && ( -{discount}% )} {image ? ( ) : (
)}
{product.name}
{Array.from({ length: 5 }).map((_, i) => ( ))}
{formatCurrency(price)} {compare > price && ( {formatCurrency(compare)} )}
); } return (
{discount > 0 && ( -{discount}% )}
{image ? ( ) : (
)}
{product.name}
{Array.from({ length: 5 }).map((_, i) => ( ))}
{formatCurrency(price)} {compare > price && ( {formatCurrency(compare)} )}
); } function FilterCard({ title, open, onToggle, children, }: { title: string; open: boolean; onToggle: () => void; children: React.ReactNode; }) { return (
{open &&
{children}
}
); } export function StandardCatalog({ initialCategory, categoryParam, brandParam, searchQuery, }: StandardCatalogProps) { const [productPool, setProductPool] = useState([]); const [categories, setCategories] = useState([]); const [brands, setBrands] = useState([]); const [selectedCategory, setSelectedCategory] = useState('all'); const [selectedBrand, setSelectedBrand] = useState('all'); const [highlight, setHighlight] = useState('All Products'); const [priceRangeId, setPriceRangeId] = useState('all'); const [selectedRating, setSelectedRating] = useState(null); const [sortBy, setSortBy] = useState('default'); const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [page, setPage] = useState(1); const [isLoading, setIsLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const fetchIdRef = useRef(0); const [collapsed, setCollapsed] = useState>({ categories: false, brands: false, highlight: false, price: false, rating: false, }); const [allProductsTotal, setAllProductsTotal] = useState(0); const [categoryCounts, setCategoryCounts] = useState>({}); useEffect(() => { catalogService.getProductsPaginated({ limit: 1 }).then((res) => { if (res && typeof res.total === 'number') { setAllProductsTotal(res.total); } }).catch(() => {}); }, []); const toggleCollapse = (key: CollapseKey) => setCollapsed((prev) => ({ ...prev, [key]: !prev[key] })); useEffect(() => { catalogService.getCategories().then((catData) => { setCategories(catData); const counts: Record = {}; for (const c of catData) { if (c.product_count != null) { counts[c.category_id] = c.product_count; counts[c.slug] = c.product_count; } } setCategoryCounts(counts); if (categoryParam) { const found = catData.find( (c) => c.slug.toLowerCase() === categoryParam.toLowerCase() || c.category_id === categoryParam ); setSelectedCategory(found?.category_id || categoryParam); } else if (initialCategory) { setSelectedCategory(initialCategory.category_id); } else { setSelectedCategory('all'); } }); }, [categoryParam, initialCategory]); useEffect(() => { const activeCatId = selectedCategory === 'all' ? undefined : selectedCategory; catalogService.getBrands(activeCatId).then((brandData) => { setBrands(brandData); if (brandParam) { const found = brandData.find( (b) => b.slug.toLowerCase() === brandParam.toLowerCase() || b.brand_id === brandParam ); setSelectedBrand(found?.brand_id || brandParam); } else { setSelectedBrand('all'); } }); }, [selectedCategory, brandParam]); useEffect(() => { setPage(1); }, [ selectedCategory, selectedBrand, searchQuery, highlight, priceRangeId, selectedRating, sortBy, ]); useEffect(() => { const fetchId = ++fetchIdRef.current; setIsLoading(true); setProductPool([]); const loadAll = async () => { const paramsBase: Record = { limit: CATALOG_API_LIMIT, }; if (selectedCategory !== 'all') paramsBase.category = selectedCategory; if (selectedBrand !== 'all') paramsBase.brand = selectedBrand; if (searchQuery) paramsBase.search = searchQuery; const accumulated: ProductResponse[] = []; const seen = new Set(); let apiPage = 1; let total = Number.POSITIVE_INFINITY; while (apiPage <= CATALOG_MAX_API_PAGES && accumulated.length < total) { try { const res = await catalogService.getProductsPaginated({ ...paramsBase, page: apiPage, }); if (fetchId !== fetchIdRef.current) return; total = typeof res.total === 'number' ? res.total : accumulated.length; const mapped = (res.products || []).map(mapApiProduct); if (mapped.length === 0) break; let added = 0; for (const product of mapped) { if (seen.has(product.product_id)) continue; seen.add(product.product_id); accumulated.push(product); added += 1; } if (added > 0) { setProductPool([...accumulated]); if (apiPage === 1) setIsLoading(false); } if (accumulated.length >= total || mapped.length < CATALOG_API_LIMIT) break; apiPage += 1; } catch { break; } } if (fetchId !== fetchIdRef.current) return; setProductPool([...accumulated]); setIsLoading(false); }; void loadAll(); }, [selectedCategory, selectedBrand, searchQuery]); const filteredProducts = useMemo(() => { return applyCatalogFilters(productPool, { priceRangeId, selectedRating, highlight, sortBy, }); }, [productPool, priceRangeId, selectedRating, highlight, sortBy]); const ratingFilterPool = useMemo( () => applyCatalogFilters(productPool, { priceRangeId, selectedRating: null, highlight, sortBy: 'default', }), [productPool, priceRangeId, highlight] ); const displayed = useMemo( () => filteredProducts.slice(0, page * CATALOG_PAGE_SIZE), [filteredProducts, page] ); const totalProducts = filteredProducts.length; const showingFrom = displayed.length === 0 ? 0 : 1; const showingTo = displayed.length; const hasMore = displayed.length < totalProducts; const resetFilters = () => { setSelectedCategory('all'); setSelectedBrand('all'); setHighlight('All Products'); setPriceRangeId('all'); setSelectedRating(null); setSortBy('default'); setPage(1); }; const handleLoadMore = () => { setLoadingMore(true); setPage((p) => p + 1); // next paint after slice grows requestAnimationFrame(() => setLoadingMore(false)); }; return (

Showing{' '} {showingFrom}–{showingTo} {' '} of {totalProducts} results

{isLoading ? (
{Array.from({ length: 8 }).map((_, i) => (
))}
) : displayed.length === 0 ? (

No products found matching your filters.

) : viewMode === 'grid' ? (
{displayed.map((p) => ( ))}
) : (
{displayed.map((p) => ( ))}
)} {hasMore && !isLoading && (
)}
); }