'use client'; import React, { useEffect, useMemo, useState } from 'react'; import { useParams } from 'next/navigation'; import ProductCard from './product-card'; import ProductFilters, { type ProductFilterFacet } from './product-filters'; import ProductToolbar from './product-toolbar'; import { Button } from '@/components/ui/button'; import { Loader } from '@/components/ui/loader'; import { getCollectionProductsPage, type CollectionSortKey, } from '@/hooks/use-shopify-collections'; import type { Product } from '@/hooks/use-shopify-products'; const PAGE_SIZE = 24; const GRID_CLASSES = 'grid grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 gap-x-8 gap-y-12'; interface SortOption { label: string; sortKey: CollectionSortKey; reverse: boolean; } const SORT_OPTIONS: SortOption[] = [ { label: 'Featured', sortKey: 'COLLECTION_DEFAULT', reverse: false }, { label: 'Best Selling', sortKey: 'BEST_SELLING', reverse: false }, { label: 'Price: Low to High', sortKey: 'PRICE', reverse: false }, { label: 'Price: High to Low', sortKey: 'PRICE', reverse: true }, { label: 'Newest', sortKey: 'CREATED', reverse: true }, ]; interface CollectionDetailProps { /** * Pins the block to one collection. Left empty, it reads the `[handle]` * segment instead, which is what the `/collections/[handle]` template does. */ handle?: string; /** Overrides the collection's own title. Empty falls back to Shopify's. */ title?: string; } const CollectionDetail: React.FC = ({ handle: handleProp, title: titleProp, }) => { const params = useParams(); const handle = handleProp || (params?.handle as string); const [products, setProducts] = useState([]); const [filters, setFilters] = useState([]); const [title, setTitle] = useState(null); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [error, setError] = useState(null); const [cursor, setCursor] = useState(null); const [hasNextPage, setHasNextPage] = useState(false); const [sortIndex, setSortIndex] = useState(0); const [filtersOpen, setFiltersOpen] = useState(false); const [activeFilters, setActiveFilters] = useState([]); const sort = SORT_OPTIONS[sortIndex]; const activeKey = useMemo(() => activeFilters.join('|'), [activeFilters]); // Fall back to the handle until the collection's real title arrives. const formattedTitle = handle ? handle.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()) : 'Collection'; useEffect(() => { // Reached when the editor was opened on the literal `[handle]` pattern // rather than a real collection URL. Drop the skeleton and say so rather // than spinning forever. if (!handle || handle === '[handle]') { setLoading(false); setError( 'Open a collection page and add /editor to preview it, or pick a collection above.' ); return; } let cancelled = false; const run = async () => { try { setLoading(true); setError(null); const page = await getCollectionProductsPage(handle, { first: PAGE_SIZE, sortKey: sort.sortKey, reverse: sort.reverse, filterInputs: activeFilters, }); if (cancelled) return; if (!page.collection) { setError('Collection not found'); return; } setTitle(page.collection.title); setProducts(page.products); setCursor(page.endCursor); setHasNextPage(page.hasNextPage); // Keep the facet list stable while a selection is active, so options // don't disappear out from under the panel. if (activeFilters.length === 0) setFilters(page.filters); } catch (err) { if (cancelled) return; console.error('Error fetching collection products:', err); setError(err instanceof Error ? err.message : 'Failed to load collection'); } finally { if (!cancelled) setLoading(false); } }; run(); return () => { cancelled = true; }; }, [handle, sort.sortKey, sort.reverse, activeKey]); const handleLoadMore = async () => { if (loadingMore || !hasNextPage) return; try { setLoadingMore(true); const page = await getCollectionProductsPage(handle, { first: PAGE_SIZE, after: cursor, sortKey: sort.sortKey, reverse: sort.reverse, filterInputs: activeFilters, }); setProducts((prev) => { const seen = new Set(prev.map((p) => p.id)); return [...prev, ...page.products.filter((p) => !seen.has(p.id))]; }); setCursor(page.endCursor); setHasNextPage(page.hasNextPage); } catch (err) { console.error('Failed to load more products:', err); } finally { setLoadingMore(false); } }; return (

{titleProp || title || formattedTitle}

0 ? products.length : null} onOpenFilters={() => setFiltersOpen(true)} sortOptions={SORT_OPTIONS} sortIndex={sortIndex} onSortChange={setSortIndex} activeFilterCount={activeFilters.length} />
{loading ? (
{Array.from({ length: 10 }).map((_, index) => (
))}
) : error ? (

{error}

) : products.length === 0 ? (

{activeFilters.length > 0 ? 'No products matched your filters.' : "This collection doesn't have any products yet."}

) : ( <>
{products.map((product) => ( ))}
{hasNextPage && (
)} )}
); }; export default CollectionDetail;