'use client'; import { useState, useEffect, useCallback } from 'react'; import { ChevronDown, Package } from 'lucide-react'; import { TemplateSearch } from './TemplateSearch'; import { CategoryFilter } from './CategoryFilter'; import { TemplateCard } from './TemplateCard'; import type { MarketplaceTemplate } from '@mcpengine/ai-pipeline/types'; type SortOption = 'popular' | 'newest' | 'most_forked'; interface MarketplacePageProps { initialData?: MarketplaceTemplate[]; initialMeta?: { page: number; limit: number; total: number; totalPages: number; categories: string[]; }; } export function MarketplacePage({ initialData, initialMeta }: MarketplacePageProps) { const [templates, setTemplates] = useState(initialData || []); const [search, setSearch] = useState(''); const [category, setCategory] = useState(''); const [sort, setSort] = useState('popular'); const [page, setPage] = useState(1); const [totalPages, setTotalPages] = useState(initialMeta?.totalPages || 1); const [total, setTotal] = useState(initialMeta?.total || 0); const [categories, setCategories] = useState(initialMeta?.categories || []); const [loading, setLoading] = useState(false); const [sortOpen, setSortOpen] = useState(false); const fetchTemplates = useCallback(async () => { setLoading(true); try { const params = new URLSearchParams(); if (search) params.set('q', search); if (category) params.set('category', category); params.set('page', String(page)); params.set('limit', '24'); // Sort is handled client-side via the API's default ordering // but we pass a hint for future server support params.set('sort', sort); const res = await fetch(`/api/marketplace?${params.toString()}`); if (!res.ok) throw new Error('Failed to fetch'); const json = await res.json(); setTemplates(json.data || []); setTotalPages(json.meta?.totalPages || 1); setTotal(json.meta?.total || 0); if (json.meta?.categories) { setCategories(json.meta.categories); } } catch (err) { console.error('[MarketplacePage] fetch error:', err); } finally { setLoading(false); } }, [search, category, sort, page]); useEffect(() => { fetchTemplates(); }, [fetchTemplates]); // Reset page on filter change useEffect(() => { setPage(1); }, [search, category, sort]); const sortLabel: Record = { popular: 'Popular', newest: 'Newest', most_forked: 'Most Forked', }; // Build category counts (we don't have per-category counts from API, show available categories) const categoryCounts = categories.reduce( (acc, cat) => ({ ...acc, [cat]: 0 }), {} as Record, ); return (
{/* Header */}

Marketplace

Browse {total > 0 ? total.toLocaleString() : ''} community templates

{/* Search bar */} {/* Category filter + sort */}
0 ? categoryCounts : undefined} />
{/* Sort dropdown */}
{sortOpen && ( <>
setSortOpen(false)} />
{(Object.keys(sortLabel) as SortOption[]).map((opt) => ( ))}
)}
{/* Template grid */} {loading ? (
{Array.from({ length: 8 }).map((_, i) => (
))}
) : templates.length === 0 ? ( /* Empty state */

No templates found

{search ? `No results for "${search}". Try a different search term or category.` : 'No templates available in this category yet.'}

) : (
{templates.map((t) => ( ))}
)} {/* Pagination */} {totalPages > 1 && !loading && (
{Array.from({ length: Math.min(totalPages, 7) }, (_, i) => { let pageNum: number; if (totalPages <= 7) { pageNum = i + 1; } else if (page <= 4) { pageNum = i + 1; } else if (page >= totalPages - 3) { pageNum = totalPages - 6 + i; } else { pageNum = page - 3 + i; } return ( ); })}
)}
); }