'use client'; import React, { useState, useEffect, useRef } from 'react'; import Link from 'next/link'; import { ArrowRight, Layers, ChevronLeft, ChevronRight } from 'lucide-react'; import { Swiper, SwiperSlide } from 'swiper/react'; import { Navigation, A11y } from 'swiper/modules'; import type { Swiper as SwiperType } from 'swiper'; import { catalogService, CategoryResponse } from '../services/api/catalogService'; import { getImageUrl } from '../lib/utils'; import { getSectionTitle } from '../lib/homepageDefaults'; import { Skeleton } from '../components/ui/Skeleton'; import 'swiper/css'; import 'swiper/css/navigation'; interface CategoriesSectionProps { config?: any; initialCategories?: CategoryResponse[]; } /** Category tile image with official URL → product thumb → icon fallback */ function CategoryThumb({ name, primarySrc, fallbackSrc, }: { name: string; primarySrc: string; fallbackSrc?: string; }) { const [src, setSrc] = useState(primarySrc || fallbackSrc || ''); const [stage, setStage] = useState<'primary' | 'fallback' | 'empty'>(() => primarySrc ? 'primary' : fallbackSrc ? 'fallback' : 'empty' ); useEffect(() => { if (primarySrc) { setSrc(primarySrc); setStage('primary'); } else if (fallbackSrc) { setSrc(fallbackSrc); setStage('fallback'); } else { setSrc(''); setStage('empty'); } }, [primarySrc, fallbackSrc]); const handleError = () => { if (stage === 'primary' && fallbackSrc) { setSrc(fallbackSrc); setStage('fallback'); return; } setSrc(''); setStage('empty'); }; if (!src || stage === 'empty') { return ; } return ( {name} ); } export const CategoriesSection: React.FC = ({ config, initialCategories, }) => { const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); const [canSlide, setCanSlide] = useState(false); const [productThumbs, setProductThumbs] = useState>( {} ); const swiperRef = useRef(null); useEffect(() => { const sortCats = (cats: any[]) => [...cats].sort((a, b) => { const orderA = parseInt(a.sort_order || '0', 10); const orderB = parseInt(b.sort_order || '0', 10); return orderA - orderB; }); if (initialCategories && initialCategories.length > 0) { setCategories(sortCats(initialCategories)); setLoading(false); return; } setLoading(true); catalogService .getCategories() .then((res) => { setLoading(false); const rawCats = config?.metadata_json?.categories || config?.metadata?.categories; if (res && res.length > 0) { setCategories(sortCats(res)); } else if (rawCats && rawCats.length > 0) { setCategories(sortCats(rawCats)); } else { setCategories([]); } }) .catch((err) => { console.error('Failed to load categories:', err); setLoading(false); const rawCats = config?.metadata_json?.categories || config?.metadata?.categories; if (rawCats) { setCategories(sortCats(rawCats)); } else { setError(true); } }); }, [config, initialCategories]); // Build category_id → first product thumbnail map (used when category image is missing/broken) useEffect(() => { if (categories.length === 0) return; let cancelled = false; catalogService .getProductsPaginated({ limit: 48 }) .then((res) => { if (cancelled) return; const map: Record = {}; for (const p of res.products || []) { const cid = p.category_id; const thumb = getImageUrl(p.thumbnail_url); if (cid && thumb && !map[cid]) { map[cid] = thumb; } } setProductThumbs(map); }) .catch(() => {}); return () => { cancelled = true; }; }, [categories]); const title = getSectionTitle(config, 'featured_categories'); const syncOverflow = (swiper: SwiperType) => { if (!swiper) return; setCanSlide(!swiper.isLocked && categories.length > 0); }; useEffect(() => { const swiper = swiperRef.current; if (!swiper) return; setTimeout(() => { if ( swiper.params && swiper.params.navigation && typeof swiper.params.navigation !== 'boolean' ) { swiper.params.navigation.prevEl = '.cat-swiper-prev'; swiper.params.navigation.nextEl = '.cat-swiper-next'; } if (swiper.navigation) { try { swiper.navigation.destroy(); swiper.navigation.init(); swiper.navigation.update(); } catch (e) {} } syncOverflow(swiper); }, 0); }, [categories.length]); return (

{title}

View All
{loading ? (
{[1, 2, 3, 4, 5, 6].map((i) => (
))}
) : error || categories.length === 0 ? (

No Categories Found

Categories added in the catalog will appear here.

) : (
{ swiperRef.current = swiper; setTimeout(() => { if ( swiper && swiper.params && swiper.params.navigation && typeof swiper.params.navigation !== 'boolean' ) { swiper.params.navigation.prevEl = '.cat-swiper-prev'; swiper.params.navigation.nextEl = '.cat-swiper-next'; } if (swiper?.navigation) { try { swiper.navigation.destroy(); swiper.navigation.init(); swiper.navigation.update(); } catch (e) {} } syncOverflow(swiper); }, 0); }} onResize={syncOverflow} onBreakpoint={syncOverflow} onLock={() => setCanSlide(false)} onUnlock={() => setCanSlide(true)} navigation={{ prevEl: '.cat-swiper-prev', nextEl: '.cat-swiper-next', }} watchOverflow spaceBetween={18} slidesPerView={2} breakpoints={{ 480: { slidesPerView: 3, spaceBetween: 18 }, 768: { slidesPerView: 4, spaceBetween: 20 }, 1024: { slidesPerView: 5, spaceBetween: 22 }, 1280: { slidesPerView: 6, spaceBetween: 24 }, }} > {categories.map((cat) => { const primarySrc = getImageUrl( cat.image_url || cat.image || cat.icon_url ); const fallbackSrc = productThumbs[cat.category_id] || productThumbs[cat.id] || ''; const count = cat.product_count !== undefined && cat.product_count !== null ? Number(cat.product_count) : null; return (

{cat.name}

{count !== null ? `${count} Products` : 'Shop now'}
); })}
)}
); };