{
  "name": "product-page",
  "title": "ProductPage",
  "description": "Smart product page resolver with per-slug and per-type template routing.",
  "type": "component",
  "registryDependencies": [
    "default-product-layout",
    "food-product-layout",
    "wholesale-product-layout",
    "service-product-layout",
    "digital-product-layout",
    "cn"
  ],
  "files": [
    {
      "path": "product-page.tsx",
      "content": "\"use client\";\n\nimport React, { useEffect } from \"react\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport { PRODUCT_TYPE, RENDER_HINT } from \"@cimplify/sdk\";\nimport { trackProductView } from \"./hooks/use-product\";\nimport { useOptionalCimplifyClient } from \"@cimplify/sdk/react\";\nimport type { ProductLayoutProps } from \"@cimplify/sdk/react\";\nimport { DefaultProductLayout } from \"@cimplify/sdk/react\";\nimport { FoodProductLayout } from \"@cimplify/sdk/react\";\nimport { WholesaleProductLayout } from \"@cimplify/sdk/react\";\nimport { ServiceProductLayout } from \"@cimplify/sdk/react\";\nimport { DigitalProductLayout } from \"@cimplify/sdk/react\";\nimport { BundleProductLayout } from \"./layouts/bundle-product-layout\";\nimport { CompositeProductLayout } from \"./layouts/composite-product-layout\";\nimport type { AddToCartOptions } from \"@cimplify/sdk/react\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport type { ProductLayoutProps };\n\nexport enum ProductTemplate {\n  Default = \"default\",\n  Food = \"food\",\n  Wholesale = \"wholesale\",\n  Service = \"service\",\n  Digital = \"digital\",\n  Bundle = \"bundle\",\n  Composite = \"composite\",\n  Physical = \"physical\",\n}\n\nexport interface ProductPageClassNames {\n  root?: string;\n  loading?: string;\n}\n\nexport interface ProductPageProps {\n  /** Product slug or ID — used for client-side fetch when `product` is not provided. */\n  productId?: string;\n  /** Pre-fetched product for SSR. Skips client-side fetch when provided. */\n  product?: ProductWithDetails;\n  /** Per-slug page map. Highest priority — maps a product slug to a custom component. */\n  pages?: Record<string, React.ComponentType<ProductLayoutProps>>;\n  /** Per-type template map. Overrides built-in layouts for specific template keys. */\n  templates?: Partial<Record<ProductTemplate | string, React.ComponentType<ProductLayoutProps>>>;\n  /** Override add-to-cart behavior. */\n  onAddToCart?: (\n    product: ProductWithDetails,\n    quantity: number,\n    options: AddToCartOptions,\n  ) => void | Promise<void>;\n  /** Custom image renderer (e.g. Next.js Image). */\n  renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n  /** Breadcrumb renderer. Receives the product for context. */\n  renderBreadcrumb?: (product: ProductWithDetails) => React.ReactNode;\n  /** Pre-fetched related products. */\n  relatedProducts?: Product[];\n  /** Show related products section. Default: true. */\n  showRelated?: boolean;\n  className?: string;\n  classNames?: ProductPageClassNames;\n}\n\nconst BUILT_IN_LAYOUTS: Record<string, React.ComponentType<ProductLayoutProps>> = {\n  [ProductTemplate.Food]: FoodProductLayout,\n  [ProductTemplate.Wholesale]: WholesaleProductLayout,\n  [ProductTemplate.Service]: ServiceProductLayout,\n  [ProductTemplate.Digital]: DigitalProductLayout,\n  [ProductTemplate.Bundle]: BundleProductLayout,\n  [ProductTemplate.Composite]: CompositeProductLayout,\n  [ProductTemplate.Default]: DefaultProductLayout,\n};\n\nfunction resolveTemplateKey(product: ProductWithDetails): ProductTemplate | string {\n  const metaTemplate = product.metadata?.page_template;\n  if (typeof metaTemplate === \"string\" && metaTemplate.trim()) {\n    return metaTemplate.trim();\n  }\n\n  if (product.type === PRODUCT_TYPE.Bundle) {\n    return ProductTemplate.Bundle;\n  }\n\n  if (product.type === PRODUCT_TYPE.Composite) {\n    return ProductTemplate.Composite;\n  }\n\n  if (product.quantity_pricing && product.quantity_pricing.length > 1) {\n    return ProductTemplate.Wholesale;\n  }\n\n  if (product.type === PRODUCT_TYPE.Service) {\n    return ProductTemplate.Service;\n  }\n\n  if (product.type === PRODUCT_TYPE.Digital) {\n    return ProductTemplate.Digital;\n  }\n\n  if (product.render_hint === RENDER_HINT.Food) {\n    return ProductTemplate.Food;\n  }\n\n  if (product.render_hint === RENDER_HINT.Physical) {\n    return ProductTemplate.Physical;\n  }\n\n  return ProductTemplate.Default;\n}\n\nfunction resolveLayout(\n  product: ProductWithDetails,\n  pages?: Record<string, React.ComponentType<ProductLayoutProps>>,\n  templates?: Partial<Record<string, React.ComponentType<ProductLayoutProps>>>,\n): React.ComponentType<ProductLayoutProps> {\n  // 1. Per-slug page (AI-generated, highest priority)\n  if (pages?.[product.slug]) {\n    return pages[product.slug];\n  }\n\n  const key = resolveTemplateKey(product);\n\n  // 2. Consumer-provided template override\n  if (templates?.[key]) {\n    return templates[key];\n  }\n\n  // 3. Built-in layout\n  if (BUILT_IN_LAYOUTS[key]) {\n    return BUILT_IN_LAYOUTS[key];\n  }\n\n  // 4. Fallback\n  return DefaultProductLayout;\n}\n\nexport function ProductPage({\n  productId,\n  product: productProp,\n  pages,\n  templates,\n  onAddToCart,\n  renderImage,\n  renderBreadcrumb,\n  relatedProducts,\n  showRelated = true,\n  className,\n  classNames,\n}: ProductPageProps): React.ReactElement {\n  const resolvedId = productId || productProp?.slug || productProp?.id || \"\";\n  const { product: fetched, isLoading } = useProduct(resolvedId, {\n    enabled: !productProp && resolvedId.length > 0,\n  });\n  const product = productProp ?? fetched;\n\n  const context = useOptionalCimplifyClient();\n  useEffect(() => {\n    if (product && context) {\n      trackProductView(context.client, product);\n    }\n  }, [context, product]);\n\n  if (isLoading && !product) {\n    return (\n      <div\n        data-cimplify-product-page\n        aria-busy=\"true\"\n        className={cn(className, classNames?.root, classNames?.loading)}\n      >\n        <div className=\"grid lg:grid-cols-2 gap-8 lg:gap-12 animate-pulse\">\n          <div className=\"aspect-square bg-muted\" />\n          <div className=\"space-y-4 py-4\">\n            <div className=\"h-8 w-3/5 bg-muted\" />\n            <div className=\"h-6 w-2/5 bg-muted\" />\n            <div className=\"h-4 w-4/5 bg-muted\" />\n            <div className=\"h-4 w-3/5 bg-muted\" />\n            <div className=\"h-14 w-full bg-muted mt-8\" />\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (!product) {\n    return (\n      <div data-cimplify-product-page className={cn(className, classNames?.root)}>\n        <p className=\"text-muted-foreground\">Product not found.</p>\n      </div>\n    );\n  }\n\n  const Layout = resolveLayout(product, pages, templates);\n\n  return (\n    <div data-cimplify-product-page className={cn(className, classNames?.root)}>\n      <Layout\n        product={product}\n        onAddToCart={onAddToCart}\n        renderImage={renderImage}\n        renderBreadcrumb={renderBreadcrumb}\n        relatedProducts={relatedProducts}\n        showRelated={showRelated}\n      />\n    </div>\n  );\n}\n"
    }
  ]
}
