@cimplify/sdk 0.9.10 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/{ads-MkGm5l1T.d.mts → ads-BxbWrwqp.d.mts} +0 -8
  2. package/dist/{ads-MkGm5l1T.d.ts → ads-BxbWrwqp.d.ts} +0 -8
  3. package/dist/advanced.d.mts +2 -2
  4. package/dist/advanced.d.ts +2 -2
  5. package/dist/advanced.js +93 -80
  6. package/dist/advanced.mjs +93 -80
  7. package/dist/cli.js +184 -0
  8. package/dist/{client-BQ1gIg8t.d.mts → client-BSrq89H1.d.mts} +42 -374
  9. package/dist/{client-C3TQtGuy.d.ts → client-xBhdHLq4.d.ts} +42 -374
  10. package/dist/index.d.mts +6 -10
  11. package/dist/index.d.ts +6 -10
  12. package/dist/index.js +98 -126
  13. package/dist/index.mjs +98 -126
  14. package/dist/{payment-CTalZM5l.d.mts → payment-CrNyrc-D.d.mts} +145 -95
  15. package/dist/{payment-CTalZM5l.d.ts → payment-CrNyrc-D.d.ts} +145 -95
  16. package/dist/price-C9Z-hr49.d.mts +21 -0
  17. package/dist/price-RKKoTz-9.d.ts +21 -0
  18. package/dist/react.d.mts +1285 -35
  19. package/dist/react.d.ts +1285 -35
  20. package/dist/react.js +6596 -2598
  21. package/dist/react.mjs +6550 -2600
  22. package/dist/utils.d.mts +55 -2
  23. package/dist/utils.d.ts +55 -2
  24. package/dist/utils.js +23 -20
  25. package/dist/utils.mjs +23 -20
  26. package/package.json +13 -3
  27. package/registry/add-on-selector.json +15 -0
  28. package/registry/availability-badge.json +15 -0
  29. package/registry/booking-card.json +16 -0
  30. package/registry/booking-list.json +16 -0
  31. package/registry/booking-page.json +18 -0
  32. package/registry/bookings-page.json +17 -0
  33. package/registry/bundle-selector.json +15 -0
  34. package/registry/cart-page.json +17 -0
  35. package/registry/cart-summary.json +16 -0
  36. package/registry/catalogue-page.json +18 -0
  37. package/registry/category-filter.json +15 -0
  38. package/registry/category-grid.json +15 -0
  39. package/registry/checkout-page.json +15 -0
  40. package/registry/cn.json +13 -0
  41. package/registry/collection-page.json +16 -0
  42. package/registry/composite-selector.json +15 -0
  43. package/registry/date-slot-picker.json +16 -0
  44. package/registry/deal-banner.json +16 -0
  45. package/registry/deals-page.json +19 -0
  46. package/registry/discount-input.json +16 -0
  47. package/registry/index.json +411 -0
  48. package/registry/order-detail-page.json +16 -0
  49. package/registry/order-history-page.json +17 -0
  50. package/registry/order-history.json +16 -0
  51. package/registry/order-summary.json +16 -0
  52. package/registry/price.json +13 -0
  53. package/registry/product-card.json +17 -0
  54. package/registry/product-customizer.json +20 -0
  55. package/registry/product-grid.json +16 -0
  56. package/registry/product-image-gallery.json +13 -0
  57. package/registry/product-page.json +19 -0
  58. package/registry/product-sheet.json +18 -0
  59. package/registry/quantity-selector.json +13 -0
  60. package/registry/sale-badge.json +16 -0
  61. package/registry/search-input.json +15 -0
  62. package/registry/search-page.json +16 -0
  63. package/registry/service-card.json +16 -0
  64. package/registry/service-grid.json +16 -0
  65. package/registry/slot-picker.json +16 -0
  66. package/registry/staff-picker.json +15 -0
  67. package/registry/store-nav.json +15 -0
  68. package/registry/variant-selector.json +15 -0
  69. package/dist/index-B_25cFc1.d.ts +0 -320
  70. package/dist/index-Cd0shhZU.d.mts +0 -320
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "catalogue-page",
3
+ "title": "CataloguePage",
4
+ "description": "Browse all products with category filtering and search.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "product-grid",
8
+ "category-filter",
9
+ "search-input",
10
+ "cn"
11
+ ],
12
+ "files": [
13
+ {
14
+ "path": "catalogue-page.tsx",
15
+ "content": "\"use client\";\n\nimport React, { useState, useMemo, useCallback } from \"react\";\nimport type { Product, Category } from \"@cimplify/sdk\";\nimport { useProducts, useCategories } from \"@cimplify/sdk/react\";\nimport { ProductGrid } from \"./product-grid\";\nimport { CategoryFilter } from \"@cimplify/sdk/react\";\nimport { SearchInput } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CataloguePageClassNames {\n root?: string;\n header?: string;\n title?: string;\n searchContainer?: string;\n filterContainer?: string;\n gridContainer?: string;\n empty?: string;\n loading?: string;\n}\n\nexport interface CataloguePageProps {\n /** Page title. */\n title?: string;\n /** Pre-fetched products for SSR. When provided, skips client-side fetch. */\n products?: Product[];\n /** Pre-fetched categories for SSR. */\n categories?: Category[];\n /** Show category filter. Default: true. */\n showFilter?: boolean;\n /** Show search input. Default: true. */\n showSearch?: boolean;\n /** Called when a product is selected (e.g. navigate to product page). */\n onProductClick?: (product: Product) => void;\n /** Called when a category is selected. */\n onCategoryClick?: (category: Category) => void;\n /** Custom card renderer passed to ProductGrid. */\n renderCard?: (product: Product) => React.ReactNode;\n /** Custom image renderer passed to ProductGrid. */\n renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n /** Grid column config. */\n columns?: { sm?: number; md?: number; lg?: number; xl?: number };\n className?: string;\n classNames?: CataloguePageClassNames;\n}\n\n/**\n * CataloguePage — browse all products with category filtering and search.\n *\n * SSR-friendly: pass `products` and `categories` props for server rendering.\n * Omit them for fully client-side data fetching.\n */\nexport function CataloguePage({\n title = \"Products\",\n products: productsProp,\n categories: categoriesProp,\n showFilter = true,\n showSearch = true,\n onProductClick,\n onCategoryClick,\n renderCard,\n renderImage,\n columns,\n className,\n classNames,\n}: CataloguePageProps): React.ReactElement {\n const [selectedCategoryId, setSelectedCategoryId] = useState<string | null>(null);\n const [searchQuery, setSearchQuery] = useState(\"\");\n\n const { products: fetchedProducts, isLoading: productsLoading } = useProducts({\n enabled: productsProp === undefined,\n });\n const { categories: fetchedCategories } = useCategories({\n enabled: showFilter && categoriesProp === undefined,\n });\n\n const allProducts = productsProp ?? fetchedProducts;\n const categories = categoriesProp ?? fetchedCategories;\n\n const filteredProducts = useMemo(() => {\n let result = allProducts;\n if (selectedCategoryId) {\n result = result.filter((p) => p.category_id === selectedCategoryId);\n }\n if (searchQuery.trim().length >= 2) {\n const q = searchQuery.trim().toLowerCase();\n result = result.filter(\n (p) =>\n p.name.toLowerCase().includes(q) ||\n p.description?.toLowerCase().includes(q),\n );\n }\n return result;\n }, [allProducts, selectedCategoryId, searchQuery]);\n\n const handleCategorySelect = useCallback(\n (id: string | null) => {\n setSelectedCategoryId(id);\n if (id) {\n const cat = categories.find((c: Category) => c.id === id);\n if (cat) onCategoryClick?.(cat);\n }\n },\n [categories, onCategoryClick],\n );\n\n return (\n <div data-cimplify-catalogue-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-catalogue-header className={classNames?.header}>\n <h1 data-cimplify-catalogue-title className={classNames?.title}>\n {title}\n </h1>\n\n {showSearch && (\n <div data-cimplify-catalogue-search className={classNames?.searchContainer}>\n <SearchInput\n onResultClick={onProductClick}\n searchOptions={{ category: selectedCategoryId ?? undefined }}\n />\n </div>\n )}\n </div>\n\n {/* Category filter */}\n {showFilter && categories.length > 0 && (\n <div data-cimplify-catalogue-filter className={classNames?.filterContainer}>\n <CategoryFilter\n selectedId={selectedCategoryId}\n onSelect={handleCategorySelect}\n />\n </div>\n )}\n\n {/* Product grid */}\n <div data-cimplify-catalogue-grid className={classNames?.gridContainer}>\n {productsLoading && !productsProp ? (\n <div\n data-cimplify-catalogue-loading\n aria-busy=\"true\"\n className={classNames?.loading}\n />\n ) : (\n <ProductGrid\n products={filteredProducts}\n renderCard={renderCard}\n renderImage={renderImage}\n columns={columns}\n emptyMessage=\"No products found\"\n />\n )}\n </div>\n </div>\n );\n}\n"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "category-filter",
3
+ "title": "CategoryFilter",
4
+ "description": "Selectable category chips for filtering products.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "cn"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "category-filter.tsx",
12
+ "content": "\"use client\";\n\nimport React, { useCallback } from \"react\";\nimport type { Category } from \"@cimplify/sdk\";\nimport { useCategories } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CategoryFilterClassNames {\n root?: string;\n item?: string;\n allButton?: string;\n count?: string;\n}\n\nexport interface CategoryFilterProps {\n /** Currently selected category ID. Null means \"all\". */\n selectedId?: string | null;\n /** Called when a category is selected. Null means \"all\". */\n onSelect: (categoryId: string | null) => void;\n /** Label for the \"all\" option. Default: \"All\". */\n allLabel?: string;\n /** Show product counts per category. Default: true. */\n showCounts?: boolean;\n className?: string;\n classNames?: CategoryFilterClassNames;\n}\n\n/**\n * CategoryFilter — horizontal or vertical list of category chips.\n *\n * Fetches categories via `useCategories` and renders selectable buttons.\n * The parent controls selection state via `selectedId` + `onSelect`.\n */\nexport function CategoryFilter({\n selectedId = null,\n onSelect,\n allLabel = \"All\",\n showCounts = true,\n className,\n classNames,\n}: CategoryFilterProps): React.ReactElement {\n const { categories, isLoading } = useCategories();\n\n const handleSelect = useCallback(\n (id: string | null) => {\n onSelect(id);\n },\n [onSelect],\n );\n\n if (isLoading) {\n return (\n <div\n data-cimplify-category-filter\n aria-busy=\"true\"\n className={cn(className, classNames?.root)}\n />\n );\n }\n\n return (\n <div\n data-cimplify-category-filter\n className={cn(className, classNames?.root)}\n role=\"tablist\"\n aria-label=\"Filter by category\"\n >\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={selectedId === null}\n onClick={() => handleSelect(null)}\n data-cimplify-category-filter-item\n data-selected={selectedId === null || undefined}\n className={cn(classNames?.item, classNames?.allButton)}\n >\n {allLabel}\n </button>\n\n {categories.map((category: Category) => (\n <button\n key={category.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={selectedId === category.id}\n onClick={() => handleSelect(category.id)}\n data-cimplify-category-filter-item\n data-selected={selectedId === category.id || undefined}\n className={classNames?.item}\n >\n {category.name}\n {showCounts && category.product_count != null && (\n <span data-cimplify-category-count className={classNames?.count}>\n {category.product_count}\n </span>\n )}\n </button>\n ))}\n </div>\n );\n}\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "category-grid",
3
+ "title": "CategoryGrid",
4
+ "description": "Responsive grid of category cards.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "cn"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "category-grid.tsx",
12
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { Category } from \"@cimplify/sdk\";\nimport { useCategories } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CategoryGridClassNames {\n root?: string;\n item?: string;\n name?: string;\n description?: string;\n count?: string;\n empty?: string;\n}\n\nexport interface CategoryGridProps {\n /** Override categories (skips useCategories fetch). */\n categories?: Category[];\n /** Called when a category card is clicked. */\n onSelect?: (category: Category) => void;\n /** Custom card renderer. */\n renderCard?: (category: Category) => React.ReactNode;\n /** Responsive column counts. */\n columns?: { sm?: number; md?: number; lg?: number };\n /** Text shown when empty. */\n emptyMessage?: string;\n className?: string;\n classNames?: CategoryGridClassNames;\n}\n\n/**\n * CategoryGrid — responsive grid of category cards.\n *\n * Fetches categories via `useCategories` unless overridden.\n * Uses `React.useId()` for hydration-safe responsive CSS.\n */\nexport function CategoryGrid({\n categories: categoriesProp,\n onSelect,\n renderCard,\n columns,\n emptyMessage,\n className,\n classNames,\n}: CategoryGridProps): React.ReactElement {\n const { categories: fetched, isLoading } = useCategories({\n enabled: categoriesProp === undefined,\n });\n const categories = categoriesProp ?? fetched;\n\n const rawId = React.useId();\n const gridId = `cimplify-cat-grid-${rawId.replace(/:/g, \"\")}`;\n\n const sm = columns?.sm ?? 2;\n const md = columns?.md ?? 3;\n const lg = columns?.lg ?? 4;\n\n if (isLoading && categories.length === 0) {\n return (\n <div\n data-cimplify-category-grid\n aria-busy=\"true\"\n className={cn(className, classNames?.root)}\n />\n );\n }\n\n if (categories.length === 0) {\n return (\n <div\n data-cimplify-category-grid\n data-empty\n className={cn(className, classNames?.root, classNames?.empty)}\n >\n <p>{emptyMessage ?? \"No categories found\"}</p>\n </div>\n );\n }\n\n const css = [\n `#${gridId}{display:grid;grid-template-columns:repeat(${sm},1fr);gap:1rem}`,\n `@media(min-width:768px){#${gridId}{grid-template-columns:repeat(${md},1fr)}}`,\n `@media(min-width:1024px){#${gridId}{grid-template-columns:repeat(${lg},1fr)}}`,\n ].join(\"\");\n\n return (\n <>\n <style dangerouslySetInnerHTML={{ __html: css }} />\n <div\n id={gridId}\n data-cimplify-category-grid\n className={cn(className, classNames?.root)}\n >\n {categories.map((category: Category) => (\n <button\n key={category.id}\n type=\"button\"\n onClick={() => onSelect?.(category)}\n data-cimplify-category-card\n className={classNames?.item}\n >\n {renderCard ? (\n renderCard(category)\n ) : (\n <>\n <span data-cimplify-category-name className={classNames?.name}>\n {category.name}\n </span>\n {category.description && (\n <span data-cimplify-category-description className={classNames?.description}>\n {category.description}\n </span>\n )}\n {category.product_count != null && (\n <span data-cimplify-category-count className={classNames?.count}>\n {category.product_count} {category.product_count === 1 ? \"product\" : \"products\"}\n </span>\n )}\n </>\n )}\n </button>\n ))}\n </div>\n </>\n );\n}\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "checkout-page",
3
+ "title": "CheckoutPage",
4
+ "description": "Multi-step checkout with auth, address, and payment.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "cn"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "checkout-page.tsx",
12
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { ProcessCheckoutResult } from \"@cimplify/sdk\";\nimport { useCimplify } from \"@cimplify/sdk/react\";\nimport { CimplifyCheckout } from \"@cimplify/sdk/react\";\nimport type { CimplifyCheckoutProps } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CheckoutPageClassNames {\n root?: string;\n header?: string;\n title?: string;\n checkout?: string;\n}\n\nexport interface CheckoutPageProps {\n /** Page title. */\n title?: string;\n /** Called after successful checkout. */\n onComplete: (result: ProcessCheckoutResult) => void;\n /** Called on checkout failure. */\n onError?: (error: { code: string; message: string }) => void;\n /** Props forwarded to CimplifyCheckout. */\n checkoutProps?: Partial<\n Omit<CimplifyCheckoutProps, \"client\" | \"onComplete\" | \"onError\">\n >;\n className?: string;\n classNames?: CheckoutPageClassNames;\n}\n\n/**\n * CheckoutPage — thin page shell around CimplifyCheckout.\n *\n * Reads the CimplifyClient from CimplifyProvider context,\n * renders a page header, and delegates all checkout logic\n * to CimplifyCheckout.\n */\nexport function CheckoutPage({\n title = \"Checkout\",\n onComplete,\n onError,\n checkoutProps,\n className,\n classNames,\n}: CheckoutPageProps): React.ReactElement {\n const { client } = useCimplify();\n\n return (\n <div data-cimplify-checkout-page className={cn(className, classNames?.root)}>\n <div data-cimplify-checkout-header className={classNames?.header}>\n <h1 data-cimplify-checkout-title className={classNames?.title}>\n {title}\n </h1>\n </div>\n\n <CimplifyCheckout\n client={client}\n onComplete={onComplete}\n onError={onError}\n className={classNames?.checkout}\n {...checkoutProps}\n />\n </div>\n );\n}\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "cn",
3
+ "title": "cn",
4
+ "description": "Utility that merges Tailwind CSS classes with clsx + tailwind-merge.",
5
+ "type": "utility",
6
+ "registryDependencies": [],
7
+ "files": [
8
+ {
9
+ "path": "utils/cn.ts",
10
+ "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "collection-page",
3
+ "title": "CollectionPage",
4
+ "description": "Curated product collection with header and grid.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "product-grid",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "collection-page.tsx",
13
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, Collection } from \"@cimplify/sdk\";\nimport { useCollection } from \"@cimplify/sdk/react\";\nimport { ProductGrid } from \"./product-grid\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface CollectionPageClassNames {\n root?: string;\n header?: string;\n title?: string;\n description?: string;\n image?: string;\n grid?: string;\n loading?: string;\n}\n\nexport interface CollectionPageProps {\n /** Collection slug or ID. */\n collectionId: string;\n /** Pre-fetched collection for SSR. */\n collection?: Collection;\n /** Pre-fetched products for SSR. */\n products?: Product[];\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 /** Grid column config. */\n columns?: { sm?: number; md?: number; lg?: number; xl?: number };\n className?: string;\n classNames?: CollectionPageClassNames;\n}\n\n/**\n * CollectionPage — curated product collection with header and grid.\n *\n * SSR-friendly: pass `collection` and `products` props for server rendering.\n */\nexport function CollectionPage({\n collectionId,\n collection: collectionProp,\n products: productsProp,\n renderCard,\n renderImage,\n columns,\n className,\n classNames,\n}: CollectionPageProps): React.ReactElement {\n const {\n collection: fetchedCollection,\n products: fetchedProducts,\n isLoading,\n } = useCollection(collectionProp ? null : collectionId, {\n enabled: !collectionProp,\n });\n\n const collection = collectionProp ?? fetchedCollection;\n const products = productsProp ?? fetchedProducts;\n\n if (isLoading && !collection) {\n return (\n <div\n data-cimplify-collection-page\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n />\n );\n }\n\n if (!collection) {\n return (\n <div data-cimplify-collection-page className={cn(className, classNames?.root)}>\n <p>Collection not found.</p>\n </div>\n );\n }\n\n return (\n <div data-cimplify-collection-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-collection-header className={classNames?.header}>\n {collection.image_url && (\n <img\n src={collection.image_url}\n alt={collection.name}\n data-cimplify-collection-image\n className={classNames?.image}\n />\n )}\n <h1 data-cimplify-collection-title className={classNames?.title}>\n {collection.name}\n </h1>\n {collection.description && (\n <p data-cimplify-collection-description className={classNames?.description}>\n {collection.description}\n </p>\n )}\n </div>\n\n {/* Products */}\n <div data-cimplify-collection-grid className={classNames?.grid}>\n <ProductGrid\n products={products}\n renderCard={renderCard}\n renderImage={renderImage}\n columns={columns}\n />\n </div>\n </div>\n );\n}\n"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "composite-selector",
3
+ "title": "CompositeSelector",
4
+ "description": "Composite product builder with group constraints and live pricing.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price"
8
+ ],
9
+ "files": [
10
+ {
11
+ "path": "composite-selector.tsx",
12
+ "content": "\"use client\";\n\nimport React, { useState, useCallback, useMemo, useEffect } from \"react\";\nimport type {\n CompositeGroupView,\n CompositeComponentView,\n ComponentSelectionInput,\n CompositePriceResult,\n} from \"@cimplify/sdk\";\nimport { useCimplify } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\n\nexport interface CompositeSelectorProps {\n compositeId: string;\n groups: CompositeGroupView[];\n onSelectionsChange: (selections: ComponentSelectionInput[]) => void;\n onPriceChange?: (price: CompositePriceResult | null) => void;\n onReady?: (ready: boolean) => void;\n /** Skip the local composite price fetch when the quote system handles pricing */\n skipPriceFetch?: boolean;\n className?: string;\n}\n\nexport function CompositeSelector({\n compositeId,\n groups,\n onSelectionsChange,\n onPriceChange,\n onReady,\n skipPriceFetch,\n className,\n}: CompositeSelectorProps): React.ReactElement | null {\n const { client } = useCimplify();\n\n const [groupSelections, setGroupSelections] = useState<\n Record<string, Record<string, number>>\n >({});\n const [priceResult, setPriceResult] = useState<CompositePriceResult | null>(null);\n const [isPriceLoading, setIsPriceLoading] = useState(false);\n const [priceError, setPriceError] = useState(false);\n\n const selections = useMemo((): ComponentSelectionInput[] => {\n const result: ComponentSelectionInput[] = [];\n for (const groupSels of Object.values(groupSelections)) {\n for (const [componentId, qty] of Object.entries(groupSels)) {\n if (qty > 0) {\n result.push({ component_id: componentId, quantity: qty });\n }\n }\n }\n return result;\n }, [groupSelections]);\n\n useEffect(() => {\n onSelectionsChange(selections);\n }, [selections, onSelectionsChange]);\n\n useEffect(() => {\n onPriceChange?.(priceResult);\n }, [priceResult, onPriceChange]);\n\n const allGroupsSatisfied = useMemo(() => {\n for (const group of groups) {\n const groupSels = groupSelections[group.id] || {};\n const totalSelected = Object.values(groupSels).reduce((sum, q) => sum + q, 0);\n if (totalSelected < group.min_selections) return false;\n }\n return true;\n }, [groups, groupSelections]);\n\n useEffect(() => {\n onReady?.(allGroupsSatisfied);\n }, [allGroupsSatisfied, onReady]);\n\n useEffect(() => {\n if (skipPriceFetch || !allGroupsSatisfied || selections.length === 0) return;\n\n let cancelled = false;\n const timer = setTimeout(() => {\n void (async () => {\n setIsPriceLoading(true);\n setPriceError(false);\n try {\n const result = await client.catalogue.calculateCompositePrice(compositeId, selections);\n if (cancelled) return;\n if (result.ok) {\n setPriceResult(result.value);\n } else {\n setPriceError(true);\n }\n } catch {\n if (!cancelled) setPriceError(true);\n } finally {\n if (!cancelled) setIsPriceLoading(false);\n }\n })();\n }, 300);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n };\n }, [selections, allGroupsSatisfied, compositeId, client, skipPriceFetch]);\n\n const toggleComponent = useCallback(\n (group: CompositeGroupView, component: CompositeComponentView) => {\n setGroupSelections((prev) => {\n const groupSels = { ...(prev[group.id] || {}) };\n const currentQty = groupSels[component.id] || 0;\n\n if (currentQty > 0) {\n if (group.min_selections > 0) {\n const totalOthers = Object.entries(groupSels)\n .filter(([id]) => id !== component.id)\n .reduce((sum, [, q]) => sum + q, 0);\n if (totalOthers < group.min_selections) {\n return prev;\n }\n }\n delete groupSels[component.id];\n } else {\n const totalSelected = Object.values(groupSels).reduce((sum, q) => sum + q, 0);\n if (group.max_selections && totalSelected >= group.max_selections) {\n if (group.max_selections === 1) {\n return { ...prev, [group.id]: { [component.id]: 1 } };\n }\n return prev;\n }\n groupSels[component.id] = 1;\n }\n\n return { ...prev, [group.id]: groupSels };\n });\n },\n [],\n );\n\n const updateQuantity = useCallback(\n (group: CompositeGroupView, componentId: string, delta: number) => {\n setGroupSelections((prev) => {\n const groupSels = { ...(prev[group.id] || {}) };\n const current = groupSels[componentId] || 0;\n const next = Math.max(0, current + delta);\n\n if (next === current) return prev;\n\n if (group.max_quantity_per_component && next > group.max_quantity_per_component) {\n return prev;\n }\n\n const totalAfter = Object.entries(groupSels)\n .reduce((sum, [id, q]) => sum + (id === componentId ? next : q), 0);\n\n if (delta > 0 && group.max_selections && totalAfter > group.max_selections) {\n return prev;\n }\n\n if (delta < 0 && totalAfter < group.min_selections) {\n return prev;\n }\n\n if (next === 0) {\n delete groupSels[componentId];\n } else {\n groupSels[componentId] = next;\n }\n\n return { ...prev, [group.id]: groupSels };\n });\n },\n [],\n );\n\n if (groups.length === 0) {\n return null;\n }\n\n return (\n <div data-cimplify-composite-selector className={className}>\n {[...groups]\n .sort((a, b) => a.display_order - b.display_order)\n .map((group) => {\n const groupSels = groupSelections[group.id] || {};\n const totalSelected = Object.values(groupSels).reduce((sum, q) => sum + q, 0);\n const minMet = totalSelected >= group.min_selections;\n const isSingleSelect = group.max_selections === 1;\n\n return (\n <div key={group.id} data-cimplify-composite-group>\n <div data-cimplify-composite-group-header>\n <div>\n <span data-cimplify-composite-group-name>\n {group.name}\n {group.min_selections > 0 && (\n <span data-cimplify-composite-required> *</span>\n )}\n </span>\n {group.description && (\n <span data-cimplify-composite-group-description>{group.description}</span>\n )}\n <span data-cimplify-composite-group-constraint>\n {group.min_selections > 0 && group.max_selections\n ? `Choose ${group.min_selections}\\u2013${group.max_selections}`\n : group.min_selections > 0\n ? `Choose at least ${group.min_selections}`\n : group.max_selections\n ? `Choose up to ${group.max_selections}`\n : \"Choose as many as you like\"}\n </span>\n </div>\n {!minMet && <span data-cimplify-composite-validation>Required</span>}\n </div>\n\n <div\n data-cimplify-composite-components\n role={isSingleSelect ? \"radiogroup\" : \"group\"}\n aria-label={group.name}\n >\n {group.components\n .filter((c) => c.is_available && !c.is_archived)\n .sort((a, b) => a.display_order - b.display_order)\n .map((component) => {\n const qty = groupSels[component.id] || 0;\n const isSelected = qty > 0;\n const displayName = component.display_name || component.id;\n\n return (\n <button\n key={component.id}\n type=\"button\"\n role={isSingleSelect ? \"radio\" : \"checkbox\"}\n aria-checked={isSelected}\n onClick={() => toggleComponent(group, component)}\n data-cimplify-composite-component\n data-selected={isSelected || undefined}\n >\n <div data-cimplify-composite-component-info>\n <span data-cimplify-composite-component-name>\n {displayName}\n </span>\n {component.is_popular && (\n <span data-cimplify-composite-badge=\"popular\">Popular</span>\n )}\n {component.is_premium && (\n <span data-cimplify-composite-badge=\"premium\">Premium</span>\n )}\n {component.display_description && (\n <span data-cimplify-composite-component-description>\n {component.display_description}\n </span>\n )}\n {component.calories != null && (\n <span data-cimplify-composite-component-calories>\n {component.calories} cal\n </span>\n )}\n </div>\n\n {group.allow_quantity && isSelected && (\n <span\n data-cimplify-composite-qty\n onClick={(e: React.MouseEvent) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n onClick={() => updateQuantity(group, component.id, -1)}\n aria-label={`Decrease ${displayName} quantity`}\n >\n &#x2212;\n </button>\n <span>{qty}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(group, component.id, 1)}\n aria-label={`Increase ${displayName} quantity`}\n >\n +\n </button>\n </span>\n )}\n\n {component.price != null && component.price !== 0 && (\n <Price amount={component.price} prefix=\"+\" />\n )}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n\n {priceResult && (\n <div data-cimplify-composite-summary>\n {priceResult.base_price !== 0 && (\n <div data-cimplify-composite-summary-line>\n <span>Base</span>\n <Price amount={priceResult.base_price} />\n </div>\n )}\n {priceResult.components_total !== 0 && (\n <div data-cimplify-composite-summary-line>\n <span>Selections</span>\n <Price amount={priceResult.components_total} />\n </div>\n )}\n <div data-cimplify-composite-summary-total>\n <span>Total</span>\n <Price amount={priceResult.final_price} />\n </div>\n </div>\n )}\n\n {isPriceLoading && (\n <div data-cimplify-composite-calculating>Calculating price...</div>\n )}\n\n {priceError && !isPriceLoading && (\n <div data-cimplify-composite-price-error>Unable to calculate price</div>\n )}\n </div>\n );\n}\n"
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "date-slot-picker",
3
+ "title": "DateSlotPicker",
4
+ "description": "Horizontal date strip with slot picker for service scheduling.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "slot-picker",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "date-slot-picker.tsx",
13
+ "content": "\"use client\";\n\nimport React, { useState, useMemo, useCallback } from \"react\";\nimport type { AvailableSlot, DayAvailability } from \"@cimplify/sdk\";\nimport { useServiceAvailability } from \"@cimplify/sdk/react\";\nimport { SlotPicker } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface DateSlotPickerClassNames {\n root?: string;\n dateStrip?: string;\n dateButton?: string;\n nav?: string;\n navButton?: string;\n slots?: string;\n loading?: string;\n}\n\nexport interface DateSlotPickerProps {\n /** Service ID to fetch availability and slots for. */\n serviceId: string;\n /** Number of days to show in the date strip. Default: 7. */\n daysToShow?: number;\n /** Number of participants. */\n participantCount?: number;\n /** Currently selected slot. */\n selectedSlot?: AvailableSlot | null;\n /** Called when a slot is selected. */\n onSlotSelect?: (slot: AvailableSlot, date: string) => void;\n /** Pre-fetched availability data (skips fetch). */\n availability?: DayAvailability[];\n /** Show price on slots. Default: true. */\n showPrice?: boolean;\n className?: string;\n classNames?: DateSlotPickerClassNames;\n}\n\nfunction formatDate(dateStr: string): string {\n const date = new Date(dateStr + \"T00:00:00\");\n return date.toLocaleDateString(undefined, { weekday: \"short\", month: \"short\", day: \"numeric\" });\n}\n\nfunction toDateString(date: Date): string {\n return date.toISOString().split(\"T\")[0];\n}\n\nfunction addDays(date: Date, days: number): Date {\n const result = new Date(date);\n result.setDate(result.getDate() + days);\n return result;\n}\n\nexport function DateSlotPicker({\n serviceId,\n daysToShow = 7,\n participantCount,\n selectedSlot,\n onSlotSelect,\n availability: availabilityProp,\n showPrice = true,\n className,\n classNames,\n}: DateSlotPickerProps): React.ReactElement {\n const [offset, setOffset] = useState(0);\n const [selectedDate, setSelectedDate] = useState<string>(toDateString(new Date()));\n\n const dateRange = useMemo(() => {\n const today = new Date();\n const start = addDays(today, offset);\n const dates: string[] = [];\n for (let i = 0; i < daysToShow; i++) {\n dates.push(toDateString(addDays(start, i)));\n }\n return {\n dates,\n startDate: dates[0],\n endDate: dates[dates.length - 1],\n };\n }, [offset, daysToShow]);\n\n const { days: fetchedDays, isLoading: availabilityLoading } = useServiceAvailability(\n serviceId,\n dateRange.startDate,\n dateRange.endDate,\n {\n participantCount,\n enabled: availabilityProp === undefined,\n },\n );\n\n const days = availabilityProp ?? fetchedDays;\n\n const availabilityMap = useMemo(() => {\n const map = new Map<string, DayAvailability>();\n for (const day of days) {\n map.set(day.date, day);\n }\n return map;\n }, [days]);\n\n const handlePrev = useCallback(() => {\n setOffset((prev) => Math.max(0, prev - daysToShow));\n }, [daysToShow]);\n\n const handleNext = useCallback(() => {\n setOffset((prev) => prev + daysToShow);\n }, [daysToShow]);\n\n const handleDateSelect = useCallback((date: string) => {\n setSelectedDate(date);\n }, []);\n\n const handleSlotSelect = useCallback(\n (slot: AvailableSlot) => {\n onSlotSelect?.(slot, selectedDate);\n },\n [onSlotSelect, selectedDate],\n );\n\n return (\n <div data-cimplify-date-slot-picker className={cn(className, classNames?.root)}>\n <div data-cimplify-date-nav className={classNames?.nav}>\n <button\n type=\"button\"\n onClick={handlePrev}\n disabled={offset === 0}\n data-cimplify-date-nav-prev\n className={classNames?.navButton}\n >\n &larr;\n </button>\n <button\n type=\"button\"\n onClick={handleNext}\n data-cimplify-date-nav-next\n className={classNames?.navButton}\n >\n &rarr;\n </button>\n </div>\n\n <div data-cimplify-date-strip className={classNames?.dateStrip} role=\"tablist\">\n {dateRange.dates.map((date) => {\n const dayInfo = availabilityMap.get(date);\n const hasAvailability = dayInfo?.has_availability !== false;\n const isSelected = selectedDate === date;\n return (\n <button\n key={date}\n type=\"button\"\n role=\"tab\"\n aria-selected={isSelected}\n onClick={() => handleDateSelect(date)}\n data-cimplify-date-button\n data-selected={isSelected || undefined}\n data-available={hasAvailability || undefined}\n data-fully-booked={(!hasAvailability) || undefined}\n className={classNames?.dateButton}\n >\n {formatDate(date)}\n </button>\n );\n })}\n </div>\n\n {availabilityLoading && (\n <div\n data-cimplify-date-slot-loading\n aria-busy=\"true\"\n className={classNames?.loading}\n />\n )}\n\n <div data-cimplify-date-slots className={classNames?.slots}>\n <SlotPicker\n serviceId={serviceId}\n date={selectedDate}\n participantCount={participantCount}\n selectedSlot={selectedSlot}\n onSlotSelect={handleSlotSelect}\n showPrice={showPrice}\n />\n </div>\n </div>\n );\n}\n"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "deal-banner",
3
+ "title": "DealBanner",
4
+ "description": "Displays active deals and promotions.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "deal-banner.tsx",
13
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { Deal } from \"@cimplify/sdk\";\nimport { useDeals } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface DealBannerClassNames {\n root?: string;\n item?: string;\n description?: string;\n value?: string;\n badge?: string;\n empty?: string;\n}\n\nexport interface DealBannerProps {\n /** Override deals (skips useDeals fetch). For SSR, pass pre-fetched deals. */\n deals?: Deal[];\n /** Location ID for location-specific deals. */\n locationId?: string;\n /** Called when a deal is clicked. */\n onDealClick?: (deal: Deal) => void;\n /** Custom deal card renderer. */\n renderDeal?: (deal: Deal) => React.ReactNode;\n /** Maximum deals to show. Default: all. */\n limit?: number;\n className?: string;\n classNames?: DealBannerClassNames;\n}\n\nfunction formatBenefitLabel(deal: Deal): string {\n switch (deal.benefit_type) {\n case \"percentage\":\n return `${deal.value}% off`;\n case \"fixed\":\n return `Save`;\n case \"free_item\":\n return \"Free item\";\n case \"buy_x_get_y_free\":\n return `Buy ${deal.buy_quantity ?? \"\"} get ${deal.get_quantity ?? 1} free`;\n case \"points\":\n return `Earn ${deal.value} points`;\n default:\n return \"Special offer\";\n }\n}\n\n/**\n * DealBanner — displays active deals/promotions.\n *\n * Renders as a horizontal scrollable strip or grid of deal cards.\n */\nexport function DealBanner({\n deals: dealsProp,\n locationId,\n onDealClick,\n renderDeal,\n limit,\n className,\n classNames,\n}: DealBannerProps): React.ReactElement | null {\n const { deals: fetched, isLoading } = useDeals({\n locationId,\n enabled: dealsProp === undefined,\n });\n\n const allDeals = dealsProp ?? fetched;\n const deals = limit ? allDeals.slice(0, limit) : allDeals;\n\n if (isLoading && deals.length === 0) {\n return (\n <div\n data-cimplify-deal-banner\n aria-busy=\"true\"\n className={cn(className, classNames?.root)}\n />\n );\n }\n\n if (deals.length === 0) {\n return null;\n }\n\n return (\n <div data-cimplify-deal-banner className={cn(className, classNames?.root)}>\n {deals.map((deal) => (\n <button\n key={deal.id}\n type=\"button\"\n onClick={() => onDealClick?.(deal)}\n data-cimplify-deal-item\n data-benefit-type={deal.benefit_type}\n className={classNames?.item}\n >\n {renderDeal ? (\n renderDeal(deal)\n ) : (\n <>\n <span data-cimplify-deal-badge className={classNames?.badge}>\n {formatBenefitLabel(deal)}\n </span>\n <span data-cimplify-deal-description className={classNames?.description}>\n {deal.description}\n </span>\n {deal.benefit_type === \"fixed\" && (\n <span data-cimplify-deal-value className={classNames?.value}>\n <Price amount={deal.value} />\n </span>\n )}\n {deal.min_order_value && (\n <span data-cimplify-deal-minimum>\n Min. order <Price amount={deal.min_order_value} />\n </span>\n )}\n </>\n )}\n </button>\n ))}\n </div>\n );\n}\n"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "deals-page",
3
+ "title": "DealsPage",
4
+ "description": "Promotions landing page with deal banners and on-sale products.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "deal-banner",
8
+ "product-grid",
9
+ "sale-badge",
10
+ "product-card",
11
+ "cn"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "deals-page.tsx",
16
+ "content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, Deal, ProductDealInfo } from \"@cimplify/sdk\";\nimport { useDeals, useProductsOnSale } from \"@cimplify/sdk/react\";\nimport { DealBanner } from \"@cimplify/sdk/react\";\nimport { ProductGrid } from \"./product-grid\";\nimport { SaleBadge } from \"./sale-badge\";\nimport { ProductCard } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface DealsPageClassNames {\n root?: string;\n header?: string;\n title?: string;\n bannerSection?: string;\n productsSection?: string;\n productsTitle?: string;\n empty?: string;\n loading?: string;\n}\n\nexport interface DealsPageProps {\n /** Page title. */\n title?: string;\n /** Pre-fetched deals for SSR. */\n deals?: Deal[];\n /** Pre-fetched products on sale for SSR. */\n productsOnSale?: ProductDealInfo[];\n /** Pre-fetched product objects for rendering. */\n products?: Product[];\n /** Location ID for location-specific deals. */\n locationId?: string;\n /** Custom image renderer. */\n renderImage?: (props: { src: string; alt: string; className?: string }) => React.ReactNode;\n /** Grid column config. */\n columns?: { sm?: number; md?: number; lg?: number; xl?: number };\n className?: string;\n classNames?: DealsPageClassNames;\n}\n\n/**\n * DealsPage — promotions landing page with deal banners and on-sale products.\n *\n * SSR-friendly: pass `deals` and `productsOnSale` props for server rendering.\n */\nexport function DealsPage({\n title = \"Deals & Offers\",\n deals: dealsProp,\n productsOnSale: productsOnSaleProp,\n products: productsProp,\n locationId,\n renderImage,\n columns,\n className,\n classNames,\n}: DealsPageProps): React.ReactElement {\n const { deals: fetchedDeals, isLoading: dealsLoading } = useDeals({\n locationId,\n enabled: dealsProp === undefined,\n });\n const { products: fetchedOnSale, isLoading: productsLoading } = useProductsOnSale({\n enabled: productsOnSaleProp === undefined,\n });\n\n const deals = dealsProp ?? fetchedDeals;\n const onSale = productsOnSaleProp ?? fetchedOnSale;\n const isLoading = dealsLoading || productsLoading;\n\n const isEmpty = deals.length === 0 && onSale.length === 0;\n\n if (isLoading && isEmpty) {\n return (\n <div\n data-cimplify-deals-page\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n />\n );\n }\n\n return (\n <div data-cimplify-deals-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-deals-header className={classNames?.header}>\n <h1 data-cimplify-deals-title className={classNames?.title}>\n {title}\n </h1>\n </div>\n\n {/* Deal banners */}\n {deals.length > 0 && (\n <div data-cimplify-deals-banner-section className={classNames?.bannerSection}>\n <DealBanner deals={deals} />\n </div>\n )}\n\n {/* Products on sale */}\n {(onSale.length > 0 || (productsProp && productsProp.length > 0)) && (\n <div data-cimplify-deals-products className={classNames?.productsSection}>\n <h2 data-cimplify-deals-products-title className={classNames?.productsTitle}>\n On Sale Now\n </h2>\n {productsProp ? (\n <ProductGrid\n products={productsProp}\n renderImage={renderImage}\n columns={columns}\n />\n ) : (\n <div data-cimplify-deals-sale-list>\n {onSale.map((dealInfo: ProductDealInfo) => (\n <div key={`${dealInfo.product_id}-${dealInfo.deal_id}`} data-cimplify-deals-sale-item>\n <SaleBadge\n product={{ id: dealInfo.product_id, name: dealInfo.deal_name } as Product}\n dealInfo={dealInfo}\n showOriginalPrice\n />\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n\n {/* Empty state */}\n {isEmpty && (\n <div data-cimplify-deals-empty className={classNames?.empty}>\n <p>No deals available right now. Check back soon!</p>\n </div>\n )}\n </div>\n );\n}\n"
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "discount-input",
3
+ "title": "DiscountInput",
4
+ "description": "Discount code input with inline validation.",
5
+ "type": "component",
6
+ "registryDependencies": [
7
+ "price",
8
+ "cn"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "discount-input.tsx",
13
+ "content": "\"use client\";\n\nimport React, { useState, useCallback } from \"react\";\nimport type { DiscountValidation } from \"@cimplify/sdk\";\nimport { useValidateDiscount } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface DiscountInputClassNames {\n root?: string;\n input?: string;\n button?: string;\n result?: string;\n error?: string;\n success?: string;\n}\n\nexport interface DiscountInputProps {\n /** Current order subtotal for validation. */\n orderSubtotal: string;\n /** Location ID for location-specific discounts. */\n locationId?: string;\n /** Called when a valid discount is applied. */\n onApply?: (validation: DiscountValidation) => void;\n /** Called when discount is cleared. */\n onClear?: () => void;\n /** Placeholder text. */\n placeholder?: string;\n className?: string;\n classNames?: DiscountInputClassNames;\n}\n\n/**\n * DiscountInput — discount code input with inline validation.\n *\n * Wraps `useValidateDiscount` with a text input and apply button.\n * Shows validation result inline (success with amount, or error).\n */\nexport function DiscountInput({\n orderSubtotal,\n locationId,\n onApply,\n onClear,\n placeholder = \"Discount code\",\n className,\n classNames,\n}: DiscountInputProps): React.ReactElement {\n const [code, setCode] = useState(\"\");\n const [appliedValidation, setAppliedValidation] = useState<DiscountValidation | null>(null);\n const { validate, isValidating, error } = useValidateDiscount();\n\n const handleApply = useCallback(async () => {\n const trimmed = code.trim();\n if (!trimmed) return;\n\n const result = await validate(trimmed, orderSubtotal, locationId);\n if (result) {\n if (result.is_eligible) {\n setAppliedValidation(result);\n onApply?.(result);\n } else {\n setAppliedValidation(result);\n }\n }\n }, [code, validate, orderSubtotal, locationId, onApply]);\n\n const handleClear = useCallback(() => {\n setCode(\"\");\n setAppliedValidation(null);\n onClear?.();\n }, [onClear]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\") {\n void handleApply();\n }\n },\n [handleApply],\n );\n\n const isApplied = appliedValidation?.is_eligible === true;\n\n return (\n <div data-cimplify-discount className={cn(className, classNames?.root)}>\n <div data-cimplify-discount-form>\n <input\n type=\"text\"\n value={code}\n onChange={(e) => setCode(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={isApplied}\n data-cimplify-discount-input\n className={classNames?.input}\n aria-label=\"Discount code\"\n />\n {isApplied ? (\n <button\n type=\"button\"\n onClick={handleClear}\n data-cimplify-discount-clear\n className={classNames?.button}\n >\n Remove\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={handleApply}\n disabled={isValidating || code.trim().length === 0}\n data-cimplify-discount-apply\n className={classNames?.button}\n >\n {isValidating ? \"Checking...\" : \"Apply\"}\n </button>\n )}\n </div>\n\n {error && (\n <div data-cimplify-discount-error className={classNames?.error}>\n {error.message}\n </div>\n )}\n\n {appliedValidation && !appliedValidation.is_eligible && (\n <div data-cimplify-discount-error className={classNames?.error}>\n {appliedValidation.ineligibility_reason ?? \"This code is not valid.\"}\n </div>\n )}\n\n {isApplied && appliedValidation.discount_amount && (\n <div data-cimplify-discount-success className={classNames?.success}>\n <span>Discount applied</span>\n <Price amount={appliedValidation.discount_amount} prefix=\"-\" />\n </div>\n )}\n </div>\n );\n}\n"
14
+ }
15
+ ]
16
+ }