{
  "name": "catalogue-page",
  "title": "CataloguePage",
  "description": "Browse all products with category filtering and search.",
  "type": "component",
  "registryDependencies": [
    "product-grid",
    "category-filter",
    "search-input",
    "cn"
  ],
  "files": [
    {
      "path": "catalogue-page.tsx",
      "content": "\"use client\";\n\nimport React, { useState, useCallback, useMemo } from \"react\";\nimport type { Product, Category, PropertyFacet } from \"@cimplify/sdk\";\nimport { useProducts, useCategories, type UseProductsOptions } from \"@cimplify/sdk/react\";\nimport { prefetchProducts } from \"./hooks/use-products\";\nimport { speculate } from \"./utils/prefetch-policy\";\nimport { useOptionalCimplifyClient } from \"@cimplify/sdk/react\";\nimport type { CatalogueLayoutProps } from \"./catalogue-layouts/shared\";\nimport { DefaultCatalogueLayout } from \"./catalogue-layouts/default-catalogue-layout\";\nimport { CompactCatalogueLayout } from \"./catalogue-layouts/compact-catalogue-layout\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport type { CatalogueLayoutProps };\n\nexport enum CatalogueTemplate {\n  Default = \"default\",\n  Compact = \"compact\",\n}\n\nexport interface CataloguePageClassNames {\n  root?: string;\n}\n\nexport interface CataloguePageProps {\n  /** Explicit template. */\n  template?: CatalogueTemplate;\n  /** Layout map for overrides. */\n  layouts?: Partial<Record<CatalogueTemplate | string, React.ComponentType<CatalogueLayoutProps>>>;\n  /** Page title. */\n  title?: string;\n  /** Pre-fetched products for SSR. */\n  products?: Product[];\n  /** Pre-fetched categories for SSR. */\n  categories?: Category[];\n  /** Pre-fetched property facets for sidebar. */\n  facets?: PropertyFacet[];\n  /** Available tags for filtering. */\n  availableTags?: string[];\n  /** Products per page. */\n  pageSize?: number;\n  /** Default sort. */\n  defaultSort?: { by: string; order: string };\n  /** Custom card renderer. */\n  renderCard?: (product: Product) => React.ReactNode;\n  /** Custom image renderer. */\n  renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n  /** Custom link renderer. */\n  renderLink?: (props: { href: string; className?: string; children: React.ReactNode }) => React.ReactElement;\n  /** Quick-add handler. */\n  onQuickAdd?: (product: Product) => void;\n  /** Product click handler. */\n  onProductClick?: (product: Product) => void;\n  className?: string;\n  classNames?: CataloguePageClassNames;\n}\n\nconst BUILT_IN_LAYOUTS: Record<string, React.ComponentType<CatalogueLayoutProps>> = {\n  [CatalogueTemplate.Default]: DefaultCatalogueLayout,\n  [CatalogueTemplate.Compact]: CompactCatalogueLayout,\n};\n\nfunction useDebounce(value: string, delay: number): string {\n  const [debounced, setDebounced] = React.useState(value);\n  React.useEffect(() => {\n    const timer = setTimeout(() => setDebounced(value), delay);\n    return () => clearTimeout(timer);\n  }, [value, delay]);\n  return debounced;\n}\n\nexport function CataloguePage({\n  template,\n  layouts,\n  title,\n  products: productsProp,\n  categories: categoriesProp,\n  facets,\n  availableTags,\n  pageSize,\n  defaultSort,\n  renderCard,\n  renderImage,\n  renderLink,\n  onQuickAdd,\n  onProductClick,\n  className,\n  classNames,\n}: CataloguePageProps): React.ReactElement {\n  const [selectedCategory, setSelectedCategory] = useState<string | null>(null);\n  const [searchQuery, setSearchQuery] = useState(\"\");\n  const [sortBy, setSortBy] = useState(defaultSort?.by ?? \"created_at\");\n  const [sortOrder, setSortOrder] = useState(defaultSort?.order ?? \"desc\");\n  const [inStockOnly, setInStockOnly] = useState(false);\n  const [selectedTags, setSelectedTags] = useState<string[]>([]);\n  const [minPrice, setMinPrice] = useState(\"\");\n  const [maxPrice, setMaxPrice] = useState(\"\");\n  const [page, setPage] = useState(1);\n\n  const debouncedSearch = useDebounce(searchQuery, 300);\n\n  const filterOptions = useMemo<UseProductsOptions>(\n    () => ({\n      category: selectedCategory ?? undefined,\n      search: debouncedSearch.length >= 2 ? debouncedSearch : undefined,\n      sort_by: sortBy as \"name\" | \"price\" | \"created_at\" | \"updated_at\",\n      sort_order: sortOrder as \"asc\" | \"desc\",\n      in_stock: inStockOnly || undefined,\n      tags: selectedTags.length > 0 ? selectedTags : undefined,\n      min_price: minPrice ? parseFloat(minPrice) : undefined,\n      max_price: maxPrice ? parseFloat(maxPrice) : undefined,\n      limit: pageSize,\n    }),\n    [selectedCategory, debouncedSearch, sortBy, sortOrder, inStockOnly, selectedTags, minPrice, maxPrice, pageSize],\n  );\n\n  const { products: fetchedProducts, isLoading, pagination } = useProducts({\n    ...filterOptions,\n    enabled: productsProp === undefined,\n    page,\n  });\n\n  const { categories: fetchedCategories } = useCategories({\n    enabled: categoriesProp === undefined,\n  });\n\n  const products = productsProp ?? fetchedProducts;\n  const categories = categoriesProp ?? fetchedCategories;\n  const totalPages = pagination?.total_pages ?? 1;\n\n  const context = useOptionalCimplifyClient();\n  if (\n    context &&\n    context.prefetch.mode !== \"off\" &&\n    productsProp === undefined &&\n    !isLoading &&\n    page < totalPages\n  ) {\n    speculate(() => prefetchProducts(context.client, { ...filterOptions, page: page + 1 }));\n  }\n\n  const handleCategoryChange = useCallback((id: string | null) => {\n    setSelectedCategory(id);\n    setPage(1);\n  }, []);\n\n  const handleSortChange = useCallback((by: string, order: string) => {\n    setSortBy(by);\n    setSortOrder(order);\n    setPage(1);\n  }, []);\n\n  const handleTagToggle = useCallback((tag: string) => {\n    setSelectedTags((prev) =>\n      prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag],\n    );\n    setPage(1);\n  }, []);\n\n  const handlePriceRangeChange = useCallback((min: string, max: string) => {\n    setMinPrice(min);\n    setMaxPrice(max);\n    setPage(1);\n  }, []);\n\n  const key = template ?? CatalogueTemplate.Default;\n  const Layout = layouts?.[key] ?? BUILT_IN_LAYOUTS[key] ?? DefaultCatalogueLayout;\n\n  return (\n    <div data-cimplify-catalogue-page className={cn(className, classNames?.root)}>\n      <Layout\n        products={products}\n        categories={categories}\n        facets={facets}\n        isLoading={isLoading}\n        selectedCategory={selectedCategory}\n        onCategoryChange={handleCategoryChange}\n        searchQuery={searchQuery}\n        onSearchChange={setSearchQuery}\n        sortBy={sortBy}\n        sortOrder={sortOrder}\n        onSortChange={handleSortChange}\n        inStockOnly={inStockOnly}\n        onInStockChange={setInStockOnly}\n        selectedTags={selectedTags}\n        availableTags={availableTags}\n        onTagToggle={handleTagToggle}\n        minPrice={minPrice}\n        maxPrice={maxPrice}\n        onPriceRangeChange={handlePriceRangeChange}\n        page={page}\n        totalPages={totalPages}\n        onPageChange={setPage}\n        renderCard={renderCard}\n        renderImage={renderImage}\n        renderLink={renderLink}\n        onQuickAdd={onQuickAdd}\n        onProductClick={onProductClick}\n        title={title}\n      />\n    </div>\n  );\n}\n"
    }
  ]
}
