'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 && (
)}
);
}