@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.
- package/dist/{ads-MkGm5l1T.d.mts → ads-BxbWrwqp.d.mts} +0 -8
- package/dist/{ads-MkGm5l1T.d.ts → ads-BxbWrwqp.d.ts} +0 -8
- package/dist/advanced.d.mts +2 -2
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.js +93 -80
- package/dist/advanced.mjs +93 -80
- package/dist/cli.js +184 -0
- package/dist/{client-BQ1gIg8t.d.mts → client-BSrq89H1.d.mts} +42 -374
- package/dist/{client-C3TQtGuy.d.ts → client-xBhdHLq4.d.ts} +42 -374
- package/dist/index.d.mts +6 -10
- package/dist/index.d.ts +6 -10
- package/dist/index.js +98 -126
- package/dist/index.mjs +98 -126
- package/dist/{payment-CTalZM5l.d.mts → payment-CrNyrc-D.d.mts} +145 -95
- package/dist/{payment-CTalZM5l.d.ts → payment-CrNyrc-D.d.ts} +145 -95
- package/dist/price-C9Z-hr49.d.mts +21 -0
- package/dist/price-RKKoTz-9.d.ts +21 -0
- package/dist/react.d.mts +1285 -35
- package/dist/react.d.ts +1285 -35
- package/dist/react.js +6596 -2598
- package/dist/react.mjs +6550 -2600
- package/dist/utils.d.mts +55 -2
- package/dist/utils.d.ts +55 -2
- package/dist/utils.js +23 -20
- package/dist/utils.mjs +23 -20
- package/package.json +13 -3
- package/registry/add-on-selector.json +15 -0
- package/registry/availability-badge.json +15 -0
- package/registry/booking-card.json +16 -0
- package/registry/booking-list.json +16 -0
- package/registry/booking-page.json +18 -0
- package/registry/bookings-page.json +17 -0
- package/registry/bundle-selector.json +15 -0
- package/registry/cart-page.json +17 -0
- package/registry/cart-summary.json +16 -0
- package/registry/catalogue-page.json +18 -0
- package/registry/category-filter.json +15 -0
- package/registry/category-grid.json +15 -0
- package/registry/checkout-page.json +15 -0
- package/registry/cn.json +13 -0
- package/registry/collection-page.json +16 -0
- package/registry/composite-selector.json +15 -0
- package/registry/date-slot-picker.json +16 -0
- package/registry/deal-banner.json +16 -0
- package/registry/deals-page.json +19 -0
- package/registry/discount-input.json +16 -0
- package/registry/index.json +411 -0
- package/registry/order-detail-page.json +16 -0
- package/registry/order-history-page.json +17 -0
- package/registry/order-history.json +16 -0
- package/registry/order-summary.json +16 -0
- package/registry/price.json +13 -0
- package/registry/product-card.json +17 -0
- package/registry/product-customizer.json +20 -0
- package/registry/product-grid.json +16 -0
- package/registry/product-image-gallery.json +13 -0
- package/registry/product-page.json +19 -0
- package/registry/product-sheet.json +18 -0
- package/registry/quantity-selector.json +13 -0
- package/registry/sale-badge.json +16 -0
- package/registry/search-input.json +15 -0
- package/registry/search-page.json +16 -0
- package/registry/service-card.json +16 -0
- package/registry/service-grid.json +16 -0
- package/registry/slot-picker.json +16 -0
- package/registry/staff-picker.json +15 -0
- package/registry/store-nav.json +15 -0
- package/registry/variant-selector.json +15 -0
- package/dist/index-B_25cFc1.d.ts +0 -320
- package/dist/index-Cd0shhZU.d.mts +0 -320
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-page",
|
|
3
|
+
"title": "ProductPage",
|
|
4
|
+
"description": "Full product detail page with badges and related products.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"product-sheet",
|
|
8
|
+
"availability-badge",
|
|
9
|
+
"sale-badge",
|
|
10
|
+
"product-grid",
|
|
11
|
+
"cn"
|
|
12
|
+
],
|
|
13
|
+
"files": [
|
|
14
|
+
{
|
|
15
|
+
"path": "product-page.tsx",
|
|
16
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport type { AddToCartOptions } from \"@cimplify/sdk/react\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { ProductSheet } from \"@cimplify/sdk/react\";\nimport { AvailabilityBadge } from \"./availability-badge\";\nimport { SaleBadge } from \"./sale-badge\";\nimport { ProductGrid } from \"./product-grid\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface ProductPageClassNames {\n root?: string;\n main?: string;\n badges?: string;\n relatedSection?: string;\n relatedTitle?: string;\n loading?: string;\n}\n\nexport interface ProductPageProps {\n /** Product slug or ID to display. */\n productId: string;\n /** Pre-fetched product for SSR. Skips client-side fetch when provided. */\n product?: ProductWithDetails;\n /** Pre-fetched related products for SSR. */\n relatedProducts?: Product[];\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 /** Show availability badge. Default: true. */\n showAvailability?: boolean;\n /** Show sale badge. Default: true. */\n showSaleBadge?: boolean;\n /** Show related products section. Default: true. */\n showRelated?: boolean;\n /** Related section title. */\n relatedTitle?: string;\n className?: string;\n classNames?: ProductPageClassNames;\n}\n\n/**\n * ProductPage — full product detail page with badges and related products.\n *\n * SSR-friendly: pass `product` prop for server rendering.\n * Composes ProductSheet, AvailabilityBadge, SaleBadge, and a related products grid.\n */\nexport function ProductPage({\n productId,\n product: productProp,\n relatedProducts,\n onAddToCart,\n renderImage,\n showAvailability = true,\n showSaleBadge = true,\n showRelated = true,\n relatedTitle = \"You might also like\",\n className,\n classNames,\n}: ProductPageProps): React.ReactElement {\n const { product: fetched, isLoading } = useProduct(productId, {\n enabled: !productProp,\n });\n const product = productProp ?? fetched;\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 data-cimplify-product-page-skeleton style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}>\n <div style={{ aspectRatio: \"4/3\", backgroundColor: \"rgba(0,0,0,0.06)\", borderRadius: \"0.5rem\" }} />\n <div style={{ height: \"2rem\", width: \"60%\", backgroundColor: \"rgba(0,0,0,0.06)\", borderRadius: \"0.25rem\" }} />\n <div style={{ height: \"1rem\", width: \"30%\", backgroundColor: \"rgba(0,0,0,0.06)\", borderRadius: \"0.25rem\" }} />\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>Product not found.</p>\n </div>\n );\n }\n\n return (\n <div data-cimplify-product-page className={cn(className, classNames?.root)}>\n {/* Badges */}\n {(showAvailability || showSaleBadge) && (\n <div data-cimplify-product-page-badges className={classNames?.badges}>\n {showAvailability && <AvailabilityBadge product={product} />}\n {showSaleBadge && <SaleBadge product={product} />}\n </div>\n )}\n\n {/* Main product content */}\n <div data-cimplify-product-page-main className={classNames?.main}>\n <ProductSheet\n product={product}\n onAddToCart={onAddToCart}\n renderImage={renderImage}\n />\n </div>\n\n {/* Related products */}\n {showRelated && relatedProducts && relatedProducts.length > 0 && (\n <div data-cimplify-product-page-related className={classNames?.relatedSection}>\n <h2 data-cimplify-product-page-related-title className={classNames?.relatedTitle}>\n {relatedTitle}\n </h2>\n <ProductGrid\n products={relatedProducts}\n renderImage={renderImage}\n />\n </div>\n )}\n </div>\n );\n}\n"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "product-sheet",
|
|
3
|
+
"title": "ProductSheet",
|
|
4
|
+
"description": "Full product detail view with gallery, header, and customizer.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"price",
|
|
8
|
+
"product-image-gallery",
|
|
9
|
+
"product-customizer",
|
|
10
|
+
"cn"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "product-sheet.tsx",
|
|
15
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, ProductWithDetails } from \"@cimplify/sdk\";\nimport type { AddToCartOptions } from \"@cimplify/sdk/react\";\nimport { useProduct } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { ProductImageGallery } from \"@cimplify/sdk/react\";\nimport { ProductCustomizer } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface ProductSheetClassNames {\n root?: string;\n image?: string;\n header?: string;\n name?: string;\n price?: string;\n description?: string;\n customizer?: string;\n loading?: string;\n}\n\nexport interface ProductSheetProps {\n /** A slim Product (triggers lazy-fetch) or a fully-loaded ProductWithDetails. */\n product: Product | ProductWithDetails;\n /** Called when the sheet should close. */\n onClose?: () => void;\n /** Override the default 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: {\n src: string;\n alt: string;\n className?: string;\n }) => React.ReactNode;\n className?: string;\n classNames?: ProductSheetClassNames;\n}\n\nfunction isProductWithDetails(\n product: Product | ProductWithDetails,\n): product is ProductWithDetails {\n return \"variants\" in product;\n}\n\n/**\n * ProductSheet — full product detail view composing gallery, header, and customizer.\n *\n * When given a slim `Product`, it lazy-fetches the full details via `useProduct`.\n * When given a `ProductWithDetails`, it skips the fetch entirely.\n */\nexport function ProductSheet({\n product,\n onClose,\n onAddToCart,\n renderImage,\n className,\n classNames,\n}: ProductSheetProps): React.ReactElement {\n const needsFetch = !isProductWithDetails(product);\n const { product: fetched, isLoading } = useProduct(\n product.slug ?? product.id,\n { enabled: needsFetch },\n );\n const fullProduct = needsFetch ? fetched : (product as ProductWithDetails);\n\n // Loading state\n if (isLoading && !fullProduct) {\n return (\n <div\n data-cimplify-product-sheet\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n >\n <div\n data-cimplify-product-sheet-skeleton\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"1rem\",\n }}\n >\n <div\n style={{\n aspectRatio: \"4/3\",\n backgroundColor: \"rgba(0,0,0,0.06)\",\n borderRadius: \"0.5rem\",\n }}\n />\n <div\n style={{\n height: \"1.5rem\",\n width: \"60%\",\n backgroundColor: \"rgba(0,0,0,0.06)\",\n borderRadius: \"0.25rem\",\n }}\n />\n <div\n style={{\n height: \"1rem\",\n width: \"30%\",\n backgroundColor: \"rgba(0,0,0,0.06)\",\n borderRadius: \"0.25rem\",\n }}\n />\n </div>\n </div>\n );\n }\n\n // Error state\n if (!fullProduct) {\n return (\n <div\n data-cimplify-product-sheet\n className={cn(className, classNames?.root)}\n >\n <p>Product not found.</p>\n </div>\n );\n }\n\n // Collect images\n const images: string[] = [];\n if (fullProduct.images && fullProduct.images.length > 0) {\n images.push(...fullProduct.images.filter(Boolean));\n } else if (fullProduct.image_url) {\n images.push(fullProduct.image_url);\n }\n\n const hasMultipleImages = images.length > 1;\n const singleImage = images[0];\n\n return (\n <div\n data-cimplify-product-sheet\n className={cn(className, classNames?.root)}\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"1rem\" }}\n >\n {/* Image area */}\n {hasMultipleImages ? (\n <ProductImageGallery\n images={images}\n productName={fullProduct.name}\n className={classNames?.image}\n />\n ) : singleImage ? (\n <div data-cimplify-product-sheet-image className={classNames?.image}>\n {renderImage ? (\n renderImage({ src: singleImage, alt: fullProduct.name })\n ) : (\n <img\n src={singleImage}\n alt={fullProduct.name}\n style={{ width: \"100%\", height: \"auto\", objectFit: \"cover\" }}\n />\n )}\n </div>\n ) : null}\n\n {/* Header */}\n <div data-cimplify-product-sheet-header className={classNames?.header}>\n <h2\n data-cimplify-product-sheet-name\n className={classNames?.name}\n style={{ margin: 0 }}\n >\n {fullProduct.name}\n </h2>\n <Price\n amount={fullProduct.default_price}\n className={classNames?.price}\n />\n </div>\n\n {/* Description */}\n {fullProduct.description && (\n <p\n data-cimplify-product-sheet-description\n className={classNames?.description}\n style={{ margin: 0 }}\n >\n {fullProduct.description}\n </p>\n )}\n\n {/* Customizer */}\n <ProductCustomizer\n product={fullProduct}\n onAddToCart={onAddToCart}\n className={classNames?.customizer}\n />\n </div>\n );\n}\n"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quantity-selector",
|
|
3
|
+
"title": "QuantitySelector",
|
|
4
|
+
"description": "Controlled increment/decrement quantity input.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [],
|
|
7
|
+
"files": [
|
|
8
|
+
{
|
|
9
|
+
"path": "quantity-selector.tsx",
|
|
10
|
+
"content": "\"use client\";\n\nimport React from \"react\";\n\nexport interface QuantitySelectorProps {\n value: number;\n onChange: (value: number) => void;\n min?: number;\n max?: number;\n className?: string;\n}\n\n/**\n * QuantitySelector — controlled increment/decrement input.\n *\n * Pure presentation. Pass `value` and `onChange` to drive it.\n */\nexport function QuantitySelector({\n value,\n onChange,\n min = 1,\n max,\n className,\n}: QuantitySelectorProps): React.ReactElement {\n return (\n <div data-cimplify-quantity className={className} style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.5rem\" }}>\n <button\n type=\"button\"\n onClick={() => onChange(Math.max(min, value - 1))}\n disabled={value <= min}\n aria-label=\"Decrease quantity\"\n data-cimplify-quantity-decrement\n >\n −\n </button>\n <span data-cimplify-quantity-value aria-live=\"polite\">{value}</span>\n <button\n type=\"button\"\n onClick={() => onChange(max != null ? Math.min(max, value + 1) : value + 1)}\n disabled={max != null && value >= max}\n aria-label=\"Increase quantity\"\n data-cimplify-quantity-increment\n >\n +\n </button>\n </div>\n );\n}\n"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sale-badge",
|
|
3
|
+
"title": "SaleBadge",
|
|
4
|
+
"description": "Sale/discount indicator with percentage and original price.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"price",
|
|
8
|
+
"cn"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "sale-badge.tsx",
|
|
13
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product, ProductDealInfo } from \"@cimplify/sdk\";\nimport type { ProductWithPrice } from \"@cimplify/sdk\";\nimport {\n isOnSale,\n getDiscountPercentage,\n getBasePrice,\n parsePrice,\n} from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface SaleBadgeClassNames {\n root?: string;\n percentage?: string;\n label?: string;\n originalPrice?: string;\n}\n\nexport interface SaleBadgeProps {\n /** Product, optionally enriched with price_info for sale detection. */\n product: Product & Partial<ProductWithPrice>;\n /** Deal info from useProductDeals / useProductsOnSale. */\n dealInfo?: ProductDealInfo;\n /** Override badge text entirely. */\n label?: string;\n /** Show the original (pre-discount) price with strikethrough styling. */\n showOriginalPrice?: boolean;\n /** Show the percentage off. Default: true. */\n showPercentage?: boolean;\n className?: string;\n classNames?: SaleBadgeClassNames;\n}\n\n/**\n * SaleBadge — shows a sale/discount indicator for a product.\n *\n * Returns `null` when there's no deal, no sale price difference, and no label override,\n * so it's safe to render unconditionally — it simply won't show for non-sale products.\n */\nexport function SaleBadge({\n product,\n dealInfo,\n label,\n showOriginalPrice = false,\n showPercentage = true,\n className,\n classNames,\n}: SaleBadgeProps): React.ReactElement | null {\n const onSale = isOnSale(product);\n const hasDeal = dealInfo !== undefined;\n\n if (!hasDeal && !onSale && !label) {\n return null;\n }\n\n // Percentage: prefer dealInfo when it's a percentage benefit, else compute from prices\n let percentage: number | null = null;\n if (hasDeal && dealInfo.benefit_type === \"percentage\") {\n percentage = parsePrice(dealInfo.value);\n } else if (onSale) {\n percentage = getDiscountPercentage(product);\n }\n\n // Badge text: explicit label > deal label > computed \"X% off\"\n const badgeText =\n label ??\n dealInfo?.label ??\n (percentage != null && percentage > 0 ? `${percentage}% off` : null);\n\n if (!badgeText) {\n return null;\n }\n\n return (\n <span\n data-cimplify-sale-badge\n className={cn(className, classNames?.root)}\n style={{ display: \"inline-flex\", alignItems: \"center\", gap: \"0.375rem\" }}\n >\n {showPercentage && percentage != null && percentage > 0 && (\n <span data-cimplify-sale-percentage className={classNames?.percentage}>\n -{percentage}%\n </span>\n )}\n <span data-cimplify-sale-label className={classNames?.label}>\n {badgeText}\n </span>\n {showOriginalPrice && onSale && (\n <span data-cimplify-sale-original-price className={classNames?.originalPrice}>\n <Price amount={getBasePrice(product)} />\n </span>\n )}\n </span>\n );\n}\n"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "search-input",
|
|
3
|
+
"title": "SearchInput",
|
|
4
|
+
"description": "Search bar with debounced results dropdown.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"cn"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "search-input.tsx",
|
|
12
|
+
"content": "\"use client\";\n\nimport React, { useCallback } from \"react\";\nimport { useSearch } from \"@cimplify/sdk/react\";\nimport type { UseSearchOptions } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface SearchInputClassNames {\n root?: string;\n input?: string;\n clearButton?: string;\n results?: string;\n resultItem?: string;\n empty?: string;\n loading?: string;\n}\n\nexport interface SearchInputProps {\n /** Placeholder text for the input. */\n placeholder?: string;\n /** Search options forwarded to useSearch. */\n searchOptions?: UseSearchOptions;\n /** Called when a product result is clicked. */\n onResultClick?: (product: import(\"../types/product\").Product) => void;\n /** Custom result item renderer. */\n renderResult?: (product: import(\"../types/product\").Product) => React.ReactNode;\n /** Show inline results dropdown. Default: true. */\n showResults?: boolean;\n className?: string;\n classNames?: SearchInputClassNames;\n}\n\n/**\n * SearchInput — search bar with debounced results dropdown.\n *\n * Wraps `useSearch` with a controlled input and optional inline results list.\n */\nexport function SearchInput({\n placeholder = \"Search products...\",\n searchOptions,\n onResultClick,\n renderResult,\n showResults = true,\n className,\n classNames,\n}: SearchInputProps): React.ReactElement {\n const { results, isLoading, query, setQuery, clear } = useSearch(searchOptions);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n setQuery(e.target.value);\n },\n [setQuery],\n );\n\n return (\n <div\n data-cimplify-search\n className={cn(className, classNames?.root)}\n style={{ position: \"relative\" }}\n >\n <input\n type=\"search\"\n value={query}\n onChange={handleChange}\n placeholder={placeholder}\n data-cimplify-search-input\n className={classNames?.input}\n aria-label=\"Search products\"\n />\n\n {query.length > 0 && (\n <button\n type=\"button\"\n onClick={clear}\n data-cimplify-search-clear\n className={classNames?.clearButton}\n aria-label=\"Clear search\"\n >\n ×\n </button>\n )}\n\n {showResults && query.length > 0 && (\n <div data-cimplify-search-results className={classNames?.results}>\n {isLoading && (\n <div data-cimplify-search-loading className={classNames?.loading} aria-busy=\"true\">\n Searching...\n </div>\n )}\n\n {!isLoading && results.length === 0 && query.length >= 2 && (\n <div data-cimplify-search-empty className={classNames?.empty}>\n No results found\n </div>\n )}\n\n {!isLoading &&\n results.map((product) => (\n <button\n key={product.id}\n type=\"button\"\n onClick={() => onResultClick?.(product)}\n data-cimplify-search-result\n className={classNames?.resultItem}\n >\n {renderResult ? (\n renderResult(product)\n ) : (\n <>\n <span data-cimplify-search-result-name>{product.name}</span>\n </>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "search-page",
|
|
3
|
+
"title": "SearchPage",
|
|
4
|
+
"description": "Dedicated search page with input and results grid.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"product-grid",
|
|
8
|
+
"cn"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "search-page.tsx",
|
|
13
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Product } from \"@cimplify/sdk\";\nimport { useSearch } from \"@cimplify/sdk/react\";\nimport { ProductGrid } from \"./product-grid\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface SearchPageClassNames {\n root?: string;\n header?: string;\n title?: string;\n inputContainer?: string;\n input?: string;\n clearButton?: string;\n resultCount?: string;\n grid?: string;\n empty?: string;\n loading?: string;\n}\n\nexport interface SearchPageProps {\n /** Page title. */\n title?: string;\n /** Placeholder text. */\n placeholder?: string;\n /** Called when a product is clicked in results. */\n onProductClick?: (product: Product) => void;\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?: SearchPageClassNames;\n}\n\n/**\n * SearchPage — dedicated search page with input and results grid.\n *\n * Uses `useSearch` for debounced full-text search.\n */\nexport function SearchPage({\n title = \"Search\",\n placeholder = \"What are you looking for?\",\n onProductClick,\n renderCard,\n renderImage,\n columns,\n className,\n classNames,\n}: SearchPageProps): React.ReactElement {\n const { results, isLoading, query, setQuery, clear } = useSearch();\n\n return (\n <div data-cimplify-search-page className={cn(className, classNames?.root)}>\n {/* Header */}\n <div data-cimplify-search-page-header className={classNames?.header}>\n <h1 data-cimplify-search-page-title className={classNames?.title}>\n {title}\n </h1>\n </div>\n\n {/* Search input */}\n <div data-cimplify-search-page-input className={classNames?.inputContainer}>\n <input\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={placeholder}\n data-cimplify-search-page-field\n className={classNames?.input}\n aria-label=\"Search products\"\n autoFocus\n />\n {query.length > 0 && (\n <button\n type=\"button\"\n onClick={clear}\n data-cimplify-search-page-clear\n className={classNames?.clearButton}\n aria-label=\"Clear search\"\n >\n ×\n </button>\n )}\n </div>\n\n {/* Result count */}\n {query.length >= 2 && !isLoading && (\n <div data-cimplify-search-page-count className={classNames?.resultCount}>\n {results.length} {results.length === 1 ? \"result\" : \"results\"} for “{query}”\n </div>\n )}\n\n {/* Results */}\n <div data-cimplify-search-page-grid className={classNames?.grid}>\n {isLoading ? (\n <div data-cimplify-search-page-loading aria-busy=\"true\" className={classNames?.loading}>\n Searching...\n </div>\n ) : query.length >= 2 && results.length === 0 ? (\n <div data-cimplify-search-page-empty className={classNames?.empty}>\n <p>No products found for “{query}”</p>\n </div>\n ) : (\n results.length > 0 && (\n <ProductGrid\n products={results}\n renderCard={renderCard}\n renderImage={renderImage}\n columns={columns}\n />\n )\n )}\n </div>\n </div>\n );\n}\n"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "service-card",
|
|
3
|
+
"title": "ServiceCard",
|
|
4
|
+
"description": "Service display card with image, duration, price, and book action.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"price",
|
|
8
|
+
"cn"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "service-card.tsx",
|
|
13
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Service } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface ServiceCardClassNames {\n root?: string;\n imageContainer?: string;\n image?: string;\n body?: string;\n name?: string;\n description?: string;\n meta?: string;\n duration?: string;\n price?: string;\n action?: string;\n}\n\nexport interface ServiceCardProps {\n /** The service to display. */\n service: Service;\n /** Called when the card or book button is clicked. */\n onBook?: (service: Service) => void;\n /** Link href for page mode. Renders as `<a>` instead of `<button>`. */\n href?: string;\n /** Label for the action button. Default: \"Book\". */\n actionLabel?: string;\n /** Custom image renderer (e.g. Next.js Image). */\n renderImage?: (props: {\n src: string;\n alt: string;\n className?: string;\n }) => React.ReactNode;\n /** Replace the entire default card body. */\n children?: React.ReactNode;\n /** Image aspect ratio. Default: \"4/3\". */\n aspectRatio?: \"square\" | \"4/3\" | \"16/10\" | \"3/4\";\n className?: string;\n classNames?: ServiceCardClassNames;\n}\n\nconst ASPECT_STYLES: Record<string, React.CSSProperties> = {\n square: { aspectRatio: \"1/1\" },\n \"4/3\": { aspectRatio: \"4/3\" },\n \"16/10\": { aspectRatio: \"16/10\" },\n \"3/4\": { aspectRatio: \"3/4\" },\n};\n\nexport function ServiceCard({\n service,\n onBook,\n href,\n actionLabel = \"Book\",\n renderImage,\n children,\n aspectRatio = \"4/3\",\n className,\n classNames,\n}: ServiceCardProps): React.ReactElement {\n const imageUrl = service.image_url;\n\n const cardBody = children ?? (\n <>\n {imageUrl && (\n <div\n data-cimplify-service-card-image-container\n className={classNames?.imageContainer}\n style={{\n overflow: \"hidden\",\n ...ASPECT_STYLES[aspectRatio],\n }}\n >\n {renderImage ? (\n renderImage({\n src: imageUrl,\n alt: service.name,\n className: classNames?.image,\n })\n ) : (\n <img\n src={imageUrl}\n alt={service.name}\n className={classNames?.image}\n style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\" }}\n data-cimplify-service-card-image\n />\n )}\n </div>\n )}\n\n <div data-cimplify-service-card-body className={classNames?.body}>\n <span data-cimplify-service-card-name className={classNames?.name}>\n {service.name}\n </span>\n {service.description && (\n <span\n data-cimplify-service-card-description\n className={classNames?.description}\n style={{\n display: \"-webkit-box\",\n WebkitLineClamp: 2,\n WebkitBoxOrient: \"vertical\",\n overflow: \"hidden\",\n }}\n >\n {service.description}\n </span>\n )}\n <div data-cimplify-service-card-meta className={classNames?.meta}>\n <span data-cimplify-service-card-duration className={classNames?.duration}>\n {service.duration_minutes} min\n </span>\n {service.price && (\n <span data-cimplify-service-card-price className={classNames?.price}>\n <Price amount={service.price} />\n </span>\n )}\n </div>\n </div>\n\n {!href && onBook && (\n <span data-cimplify-service-card-action className={classNames?.action}>\n {actionLabel}\n </span>\n )}\n </>\n );\n\n if (href) {\n return (\n <a\n href={href}\n data-cimplify-service-card\n data-available={service.is_available || undefined}\n className={cn(className, classNames?.root)}\n style={{ display: \"block\", textDecoration: \"none\", color: \"inherit\" }}\n >\n {cardBody}\n </a>\n );\n }\n\n return (\n <button\n type=\"button\"\n onClick={() => onBook?.(service)}\n disabled={!service.is_available}\n data-cimplify-service-card\n data-available={service.is_available || undefined}\n className={cn(className, classNames?.root)}\n style={{\n display: \"block\",\n width: \"100%\",\n textAlign: \"inherit\",\n background: \"none\",\n border: \"none\",\n padding: 0,\n cursor: service.is_available ? \"pointer\" : \"default\",\n font: \"inherit\",\n color: \"inherit\",\n }}\n >\n {cardBody}\n </button>\n );\n}\n"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "service-grid",
|
|
3
|
+
"title": "ServiceGrid",
|
|
4
|
+
"description": "Responsive grid of bookable services with self-fetching.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"service-card",
|
|
8
|
+
"cn"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "service-grid.tsx",
|
|
13
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Service } from \"@cimplify/sdk\";\nimport { useServices } from \"@cimplify/sdk/react\";\nimport { ServiceCard } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface ServiceGridClassNames {\n root?: string;\n item?: string;\n empty?: string;\n loading?: string;\n}\n\nexport interface ServiceGridProps {\n /** Pre-fetched services (skips fetch). */\n services?: Service[];\n /** Responsive column counts at each breakpoint. */\n columns?: { sm?: number; md?: number; lg?: number; xl?: number };\n /** Called when a service card is clicked. */\n onServiceSelect?: (service: Service) => void;\n /** Generate href for each service (renders cards as links). */\n getServiceHref?: (service: Service) => string;\n /** Custom card renderer per service. */\n renderCard?: (service: Service) => React.ReactNode;\n /** Custom image renderer passed to default ServiceCards. */\n renderImage?: (props: {\n src: string;\n alt: string;\n className?: string;\n }) => React.ReactNode;\n /** Only show available services. Default: true. */\n availableOnly?: boolean;\n /** Label for the book action on each card. */\n actionLabel?: string;\n /** Text shown when empty. */\n emptyMessage?: string;\n className?: string;\n classNames?: ServiceGridClassNames;\n}\n\nexport function ServiceGrid({\n services: servicesProp,\n columns,\n onServiceSelect,\n getServiceHref,\n renderCard,\n renderImage,\n availableOnly = true,\n actionLabel,\n emptyMessage = \"No services available\",\n className,\n classNames,\n}: ServiceGridProps): React.ReactElement {\n const rawId = React.useId();\n const gridId = `cimplify-service-grid-${rawId.replace(/:/g, \"\")}`;\n\n const { services: fetched, isLoading } = useServices({\n enabled: servicesProp === undefined,\n });\n\n const allServices = servicesProp ?? fetched;\n const services = availableOnly\n ? allServices.filter((s) => s.is_available)\n : allServices;\n\n const sm = columns?.sm ?? 1;\n const md = columns?.md ?? 2;\n const lg = columns?.lg ?? 3;\n const xl = columns?.xl ?? 4;\n\n if (isLoading && services.length === 0) {\n return (\n <div\n data-cimplify-service-grid\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n />\n );\n }\n\n if (services.length === 0) {\n return (\n <div\n data-cimplify-service-grid\n data-empty\n className={cn(className, classNames?.root, classNames?.empty)}\n >\n <p>{emptyMessage}</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 `@media(min-width:1280px){#${gridId}{grid-template-columns:repeat(${xl},1fr)}}`,\n ].join(\"\");\n\n return (\n <>\n <style dangerouslySetInnerHTML={{ __html: css }} />\n <div\n id={gridId}\n data-cimplify-service-grid\n className={cn(className, classNames?.root)}\n >\n {services.map((service) => (\n <div\n key={service.id}\n data-cimplify-service-grid-item\n className={classNames?.item}\n >\n {renderCard ? (\n renderCard(service)\n ) : (\n <ServiceCard\n service={service}\n onBook={onServiceSelect}\n href={getServiceHref?.(service)}\n actionLabel={actionLabel}\n renderImage={renderImage}\n />\n )}\n </div>\n ))}\n </div>\n </>\n );\n}\n"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "slot-picker",
|
|
3
|
+
"title": "SlotPicker",
|
|
4
|
+
"description": "Time slot grid for a single day with morning/afternoon/evening grouping.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"price",
|
|
8
|
+
"cn"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "slot-picker.tsx",
|
|
13
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { AvailableSlot } from \"@cimplify/sdk\";\nimport { useAvailableSlots } from \"@cimplify/sdk/react\";\nimport { Price } from \"@cimplify/sdk/react\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface SlotPickerClassNames {\n root?: string;\n group?: string;\n groupLabel?: string;\n slot?: string;\n slotTime?: string;\n slotPrice?: string;\n loading?: string;\n empty?: string;\n}\n\nexport interface SlotPickerProps {\n /** Pre-fetched slots (skips fetch). */\n slots?: AvailableSlot[];\n /** Service ID — used to fetch slots when `slots` prop is not provided. */\n serviceId?: string;\n /** Date string (YYYY-MM-DD) — used to fetch slots when `slots` prop is not provided. */\n date?: string;\n /** Number of participants for capacity-based availability. */\n participantCount?: number;\n /** Currently selected slot. */\n selectedSlot?: AvailableSlot | null;\n /** Called when a slot is selected. */\n onSlotSelect?: (slot: AvailableSlot) => void;\n /** Whether to group slots by time of day. Default: true. */\n groupByTimeOfDay?: boolean;\n /** Show price on each slot. Default: true. */\n showPrice?: boolean;\n /** Text shown when no slots available. */\n emptyMessage?: string;\n className?: string;\n classNames?: SlotPickerClassNames;\n}\n\ninterface SlotGroup {\n label: string;\n slots: AvailableSlot[];\n}\n\nfunction getTimeOfDay(timeStr: string): \"morning\" | \"afternoon\" | \"evening\" {\n const hour = parseInt(timeStr.split(\"T\").pop()?.split(\":\")[0] ?? timeStr.split(\":\")[0], 10);\n if (hour < 12) return \"morning\";\n if (hour < 17) return \"afternoon\";\n return \"evening\";\n}\n\nconst TIME_OF_DAY_LABELS: Record<string, string> = {\n morning: \"Morning\",\n afternoon: \"Afternoon\",\n evening: \"Evening\",\n};\n\nfunction groupSlots(slots: AvailableSlot[]): SlotGroup[] {\n const groups: Record<string, AvailableSlot[]> = {};\n for (const slot of slots) {\n const tod = getTimeOfDay(slot.start_time);\n if (!groups[tod]) groups[tod] = [];\n groups[tod].push(slot);\n }\n return ([\"morning\", \"afternoon\", \"evening\"] as const)\n .filter((tod) => groups[tod]?.length)\n .map((tod) => ({ label: TIME_OF_DAY_LABELS[tod], slots: groups[tod] }));\n}\n\nfunction formatTime(timeStr: string): string {\n try {\n const date = new Date(timeStr);\n if (!isNaN(date.getTime())) {\n return date.toLocaleTimeString(undefined, { hour: \"numeric\", minute: \"2-digit\" });\n }\n } catch {\n // noop\n }\n\n const parts = timeStr.split(\":\");\n if (parts.length >= 2) {\n const hour = parseInt(parts[0], 10);\n const minute = parts[1];\n const ampm = hour >= 12 ? \"PM\" : \"AM\";\n const displayHour = hour % 12 || 12;\n return `${displayHour}:${minute} ${ampm}`;\n }\n return timeStr;\n}\n\nexport function SlotPicker({\n slots: slotsProp,\n serviceId,\n date,\n participantCount,\n selectedSlot,\n onSlotSelect,\n groupByTimeOfDay = true,\n showPrice = true,\n emptyMessage = \"No available slots\",\n className,\n classNames,\n}: SlotPickerProps): React.ReactElement {\n const { slots: fetched, isLoading } = useAvailableSlots(\n serviceId ?? null,\n date ?? null,\n {\n participantCount,\n enabled: slotsProp === undefined && !!serviceId && !!date,\n },\n );\n\n const slots = slotsProp ?? fetched;\n\n if (isLoading && slots.length === 0) {\n return (\n <div\n data-cimplify-slot-picker\n aria-busy=\"true\"\n className={cn(className, classNames?.root, classNames?.loading)}\n />\n );\n }\n\n if (slots.length === 0) {\n return (\n <div\n data-cimplify-slot-picker\n data-empty\n className={cn(className, classNames?.root, classNames?.empty)}\n >\n <p>{emptyMessage}</p>\n </div>\n );\n }\n\n const groups = groupByTimeOfDay ? groupSlots(slots) : [{ label: \"\", slots }];\n\n return (\n <div data-cimplify-slot-picker className={cn(className, classNames?.root)}>\n {groups.map((group) => (\n <div key={group.label || \"all\"} data-cimplify-slot-group className={classNames?.group}>\n {group.label && (\n <div data-cimplify-slot-group-label className={classNames?.groupLabel}>\n {group.label}\n </div>\n )}\n {group.slots.map((slot) => {\n const isSelected =\n selectedSlot?.start_time === slot.start_time &&\n selectedSlot?.end_time === slot.end_time;\n return (\n <button\n key={`${slot.start_time}-${slot.end_time}`}\n type=\"button\"\n disabled={!slot.is_available}\n onClick={() => slot.is_available && onSlotSelect?.(slot)}\n data-cimplify-slot\n data-selected={isSelected || undefined}\n data-unavailable={!slot.is_available || undefined}\n className={classNames?.slot}\n >\n <span data-cimplify-slot-time className={classNames?.slotTime}>\n {formatTime(slot.start_time)}\n </span>\n {showPrice && slot.price && (\n <span data-cimplify-slot-price className={classNames?.slotPrice}>\n <Price amount={slot.price} />\n </span>\n )}\n </button>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "staff-picker",
|
|
3
|
+
"title": "StaffPicker",
|
|
4
|
+
"description": "Staff member selection list with avatar and bio.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"cn"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "staff-picker.tsx",
|
|
12
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport type { Staff } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface StaffPickerClassNames {\n root?: string;\n option?: string;\n avatar?: string;\n name?: string;\n bio?: string;\n}\n\nexport interface StaffPickerProps {\n /** List of available staff members. */\n staff: Staff[];\n /** Currently selected staff ID, or null for \"Any available\". */\n selectedStaffId?: string | null;\n /** Called when a staff member is selected. Passes null for \"Any available\". */\n onStaffSelect?: (staffId: string | null) => void;\n /** Show \"Any available\" option. Default: true. */\n showAnyOption?: boolean;\n /** Label for the \"Any available\" option. */\n anyLabel?: string;\n className?: string;\n classNames?: StaffPickerClassNames;\n}\n\nexport function StaffPicker({\n staff,\n selectedStaffId,\n onStaffSelect,\n showAnyOption = true,\n anyLabel = \"Any available\",\n className,\n classNames,\n}: StaffPickerProps): React.ReactElement {\n return (\n <div data-cimplify-staff-picker className={cn(className, classNames?.root)}>\n {showAnyOption && (\n <button\n type=\"button\"\n onClick={() => onStaffSelect?.(null)}\n data-cimplify-staff-option\n data-selected={selectedStaffId === null || undefined}\n data-any\n className={classNames?.option}\n >\n <span data-cimplify-staff-name className={classNames?.name}>\n {anyLabel}\n </span>\n </button>\n )}\n {staff.map((member) => (\n <button\n key={member.id}\n type=\"button\"\n onClick={() => onStaffSelect?.(member.id)}\n data-cimplify-staff-option\n data-selected={selectedStaffId === member.id || undefined}\n className={classNames?.option}\n >\n {member.avatar_url && (\n <img\n src={member.avatar_url}\n alt={member.name}\n data-cimplify-staff-avatar\n className={classNames?.avatar}\n />\n )}\n <span data-cimplify-staff-name className={classNames?.name}>\n {member.name}\n </span>\n {member.bio && (\n <span data-cimplify-staff-bio className={classNames?.bio}>\n {member.bio}\n </span>\n )}\n </button>\n ))}\n </div>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "store-nav",
|
|
3
|
+
"title": "StoreNav",
|
|
4
|
+
"description": "Top navigation bar with brand, categories, cart badge, and search.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"cn"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "store-nav.tsx",
|
|
12
|
+
"content": "\"use client\";\n\nimport React from \"react\";\nimport { useCart, useCategories } from \"@cimplify/sdk/react\";\nimport type { Category } from \"@cimplify/sdk\";\nimport { cn } from \"@cimplify/sdk/react\";\n\nexport interface StoreNavClassNames {\n root?: string;\n brand?: string;\n categories?: string;\n categoryLink?: string;\n actions?: string;\n cartButton?: string;\n cartCount?: string;\n searchButton?: string;\n}\n\nexport interface StoreNavProps {\n /** Store/brand name. */\n storeName?: string;\n /** Custom brand element (logo, etc.). Overrides storeName. */\n renderBrand?: () => React.ReactNode;\n /** Override categories (skips fetch). */\n categories?: Category[];\n /** Called when a category link is clicked. */\n onCategoryClick?: (category: Category) => void;\n /** Called when the cart button is clicked. */\n onCartClick?: () => void;\n /** Called when the search button is clicked. */\n onSearchClick?: () => void;\n /** Hide category navigation. */\n hideCategories?: boolean;\n /** Hide the cart button. */\n hideCart?: boolean;\n /** Hide the search button. */\n hideSearch?: boolean;\n className?: string;\n classNames?: StoreNavClassNames;\n}\n\n/**\n * StoreNav — top navigation bar with brand, category links, cart badge, and search.\n *\n * Fetches categories via `useCategories` and cart count via `useCart`.\n * Renders as a semantic `<nav>` element.\n */\nexport function StoreNav({\n storeName,\n renderBrand,\n categories: categoriesProp,\n onCategoryClick,\n onCartClick,\n onSearchClick,\n hideCategories = false,\n hideCart = false,\n hideSearch = false,\n className,\n classNames,\n}: StoreNavProps): React.ReactElement {\n const { categories: fetched } = useCategories({\n enabled: !hideCategories && categoriesProp === undefined,\n });\n const { itemCount } = useCart();\n\n const categories = categoriesProp ?? fetched;\n\n return (\n <nav data-cimplify-store-nav className={cn(className, classNames?.root)}>\n {/* Brand */}\n <div data-cimplify-store-nav-brand className={classNames?.brand}>\n {renderBrand ? renderBrand() : storeName && <span>{storeName}</span>}\n </div>\n\n {/* Category links */}\n {!hideCategories && categories.length > 0 && (\n <div data-cimplify-store-nav-categories className={classNames?.categories}>\n {categories.map((category: Category) => (\n <button\n key={category.id}\n type=\"button\"\n onClick={() => onCategoryClick?.(category)}\n data-cimplify-store-nav-category\n className={classNames?.categoryLink}\n >\n {category.name}\n </button>\n ))}\n </div>\n )}\n\n {/* Actions */}\n <div data-cimplify-store-nav-actions className={classNames?.actions}>\n {!hideSearch && (\n <button\n type=\"button\"\n onClick={onSearchClick}\n data-cimplify-store-nav-search\n className={classNames?.searchButton}\n aria-label=\"Search\"\n >\n Search\n </button>\n )}\n\n {!hideCart && (\n <button\n type=\"button\"\n onClick={onCartClick}\n data-cimplify-store-nav-cart\n className={classNames?.cartButton}\n aria-label={`Cart (${itemCount} items)`}\n >\n Cart\n {itemCount > 0 && (\n <span data-cimplify-store-nav-cart-count className={classNames?.cartCount}>\n {itemCount}\n </span>\n )}\n </button>\n )}\n </div>\n </nav>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "variant-selector",
|
|
3
|
+
"title": "VariantSelector",
|
|
4
|
+
"description": "Select product variants via axis chips or direct list.",
|
|
5
|
+
"type": "component",
|
|
6
|
+
"registryDependencies": [
|
|
7
|
+
"price"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "variant-selector.tsx",
|
|
12
|
+
"content": "\"use client\";\n\nimport React, { useState, useEffect, useRef } from \"react\";\nimport type { ProductVariant, VariantAxisWithValues } from \"@cimplify/sdk\";\nimport type { Money } from \"@cimplify/sdk\";\nimport { parsePrice } from \"@cimplify/sdk\";\nimport { getVariantDisplayName } from \"@cimplify/sdk\";\nimport { Price } from \"@cimplify/sdk/react\";\n\nexport interface VariantSelectorProps {\n variants: ProductVariant[];\n variantAxes?: VariantAxisWithValues[];\n basePrice?: Money;\n selectedVariantId?: string;\n onVariantChange: (variantId: string | undefined, variant: ProductVariant | undefined) => void;\n productName?: string;\n className?: string;\n}\n\n/**\n * VariantSelector — select product variants via axis chips or direct list.\n *\n * Axis mode: one row of chips per axis (e.g. Size, Temperature).\n * Direct mode: a vertical list showing variant name + full effective price.\n */\nexport function VariantSelector({\n variants,\n variantAxes,\n basePrice,\n selectedVariantId,\n onVariantChange,\n productName,\n className,\n}: VariantSelectorProps): React.ReactElement | null {\n const [axisSelections, setAxisSelections] = useState<Record<string, string>>({});\n const initialized = useRef(false);\n\n useEffect(() => {\n initialized.current = false;\n }, [variants]);\n\n useEffect(() => {\n if (initialized.current) return;\n if (!variants || variants.length === 0) return;\n\n const defaultVariant = variants.find((v) => v.is_default) || variants[0];\n if (!defaultVariant) return;\n\n initialized.current = true;\n onVariantChange(defaultVariant.id, defaultVariant);\n\n if (defaultVariant.display_attributes) {\n const initial: Record<string, string> = {};\n for (const attr of defaultVariant.display_attributes) {\n initial[attr.axis_id] = attr.value_id;\n }\n setAxisSelections(initial);\n }\n }, [variants, onVariantChange]);\n\n useEffect(() => {\n if (!initialized.current) return;\n if (!variantAxes || variantAxes.length === 0) return;\n\n const match = variants.find((v) => {\n if (!v.display_attributes) return false;\n return v.display_attributes.every(\n (attr) => axisSelections[attr.axis_id] === attr.value_id,\n );\n });\n\n if (match && match.id !== selectedVariantId) {\n onVariantChange(match.id, match);\n }\n }, [axisSelections, variants, variantAxes, selectedVariantId, onVariantChange]);\n\n if (!variants || variants.length <= 1) {\n return null;\n }\n\n const basePriceNum = basePrice != null ? parsePrice(basePrice) : 0;\n\n // Axis-based selection\n if (variantAxes && variantAxes.length > 0) {\n return (\n <div data-cimplify-variant-selector className={className}>\n {variantAxes.map((axis) => (\n <div key={axis.id} data-cimplify-variant-axis>\n <label data-cimplify-variant-axis-label>{axis.name}</label>\n <div data-cimplify-variant-axis-options>\n {axis.values.map((value) => {\n const isSelected = axisSelections[axis.id] === value.id;\n return (\n <button\n key={value.id}\n type=\"button\"\n aria-selected={isSelected}\n onClick={() => {\n setAxisSelections((prev) => ({\n ...prev,\n [axis.id]: value.id,\n }));\n }}\n data-cimplify-variant-option\n data-selected={isSelected || undefined}\n >\n {value.name}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n );\n }\n\n // Direct variant list\n return (\n <div data-cimplify-variant-selector className={className}>\n <label data-cimplify-variant-list-label>Options</label>\n <div data-cimplify-variant-list>\n {variants.map((variant) => {\n const isSelected = selectedVariantId === variant.id;\n const adjustment = parsePrice(variant.price_adjustment);\n const effectivePrice = basePriceNum + adjustment;\n\n return (\n <button\n key={variant.id}\n type=\"button\"\n aria-selected={isSelected}\n onClick={() => onVariantChange(variant.id, variant)}\n data-cimplify-variant-option\n data-selected={isSelected || undefined}\n >\n <span data-cimplify-variant-name>{getVariantDisplayName(variant, productName)}</span>\n <span data-cimplify-variant-pricing>\n {adjustment !== 0 && (\n <span data-cimplify-variant-adjustment>\n {adjustment > 0 ? \"+\" : \"\"}\n <Price amount={variant.price_adjustment} />\n </span>\n )}\n <Price amount={effectivePrice} />\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
package/dist/index-B_25cFc1.d.ts
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { av as ChosenPrice, C as CurrencyCode, as as TaxPathComponent, at as PricePathTaxInfo, M as Money, bd as PaymentErrorDetails, bb as PaymentResponse, bc as PaymentStatusResponse } from './payment-CTalZM5l.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Price Types
|
|
5
|
-
*
|
|
6
|
-
* Types for price parsing, formatting, and display utilities.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Individual tax component (e.g., VAT, NHIL, GETFund)
|
|
11
|
-
* @deprecated Use `TaxPathComponent` from `types/cart` instead.
|
|
12
|
-
*/
|
|
13
|
-
type TaxComponent = TaxPathComponent;
|
|
14
|
-
/**
|
|
15
|
-
* Complete tax information from a pricing response
|
|
16
|
-
* @deprecated Use `PricePathTaxInfo` from `types/cart` instead.
|
|
17
|
-
*/
|
|
18
|
-
type TaxInfo = PricePathTaxInfo;
|
|
19
|
-
/**
|
|
20
|
-
* Price information in snake_case format (as returned from backend)
|
|
21
|
-
* Used by components that work with raw API responses
|
|
22
|
-
* @deprecated Use `ChosenPrice` from `types/cart` instead.
|
|
23
|
-
*/
|
|
24
|
-
type PriceInfo = ChosenPrice;
|
|
25
|
-
/**
|
|
26
|
-
* Minimal product shape for price utilities.
|
|
27
|
-
* Uses quote-aware `price_info` and plain numeric fallback fields.
|
|
28
|
-
*/
|
|
29
|
-
interface ProductWithPrice {
|
|
30
|
-
/** Pre-parsed price info from backend */
|
|
31
|
-
price_info?: ChosenPrice;
|
|
32
|
-
/** Final computed price in plain field form (if provided by API) */
|
|
33
|
-
final_price?: number | string | null;
|
|
34
|
-
/** Base/original price in plain field form */
|
|
35
|
-
base_price?: number | string | null;
|
|
36
|
-
/** Default/indicative price in plain field form */
|
|
37
|
-
default_price?: number | string | null;
|
|
38
|
-
/** Currency in plain field form */
|
|
39
|
-
currency?: CurrencyCode | null;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Options for price formatting functions
|
|
43
|
-
*/
|
|
44
|
-
interface FormatPriceOptions {
|
|
45
|
-
/** Currency code (default: "GHS") */
|
|
46
|
-
currency?: CurrencyCode;
|
|
47
|
-
/** Locale for Intl.NumberFormat (default: "en-US") */
|
|
48
|
-
locale?: string;
|
|
49
|
-
/** Minimum fraction digits (default: 2) */
|
|
50
|
-
minimumFractionDigits?: number;
|
|
51
|
-
/** Maximum fraction digits (default: 2) */
|
|
52
|
-
maximumFractionDigits?: number;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Options for compact price formatting
|
|
56
|
-
*/
|
|
57
|
-
interface FormatCompactOptions {
|
|
58
|
-
/** Currency code (default: "GHS") */
|
|
59
|
-
currency?: CurrencyCode;
|
|
60
|
-
/** Number of decimal places for compact notation (default: 1) */
|
|
61
|
-
decimals?: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Price Utilities
|
|
66
|
-
*
|
|
67
|
-
* Comprehensive utilities for parsing, formatting, and displaying prices.
|
|
68
|
-
* Handles quote-aware pricing fields, currency formatting, and product price helpers.
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* ```typescript
|
|
72
|
-
* import {
|
|
73
|
-
* formatPrice,
|
|
74
|
-
* formatPriceCompact,
|
|
75
|
-
* isOnSale,
|
|
76
|
-
* getDiscountPercentage
|
|
77
|
-
* } from '@cimplify/sdk';
|
|
78
|
-
*
|
|
79
|
-
* // Format prices
|
|
80
|
-
* formatPrice(29.99, 'USD'); // "$29.99"
|
|
81
|
-
* formatPrice(29.99, 'GHS'); // "GH₵29.99"
|
|
82
|
-
* formatPriceCompact(1500000, 'USD'); // "$1.5M"
|
|
83
|
-
*
|
|
84
|
-
* // Check for discounts
|
|
85
|
-
* if (isOnSale(product)) {
|
|
86
|
-
* console.log(`${getDiscountPercentage(product)}% off!`);
|
|
87
|
-
* }
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Currency code to symbol mapping
|
|
93
|
-
* Includes major world currencies and African currencies
|
|
94
|
-
*/
|
|
95
|
-
declare const CURRENCY_SYMBOLS: Record<string, string>;
|
|
96
|
-
/**
|
|
97
|
-
* Get currency symbol for a currency code
|
|
98
|
-
* @param currencyCode - ISO 4217 currency code (e.g., "USD", "GHS")
|
|
99
|
-
* @returns Currency symbol or the code itself if not found
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* getCurrencySymbol('USD') // "$"
|
|
103
|
-
* getCurrencySymbol('GHS') // "GH₵"
|
|
104
|
-
* getCurrencySymbol('XYZ') // "XYZ"
|
|
105
|
-
*/
|
|
106
|
-
declare function getCurrencySymbol(currencyCode: CurrencyCode): string;
|
|
107
|
-
/**
|
|
108
|
-
* Format a number compactly with K/M/B suffixes
|
|
109
|
-
* @param value - Number to format
|
|
110
|
-
* @param decimals - Decimal places (default: 1)
|
|
111
|
-
* @returns Compact string representation
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* formatNumberCompact(1234) // "1.2K"
|
|
115
|
-
* formatNumberCompact(1500000) // "1.5M"
|
|
116
|
-
* formatNumberCompact(2500000000) // "2.5B"
|
|
117
|
-
*/
|
|
118
|
-
declare function formatNumberCompact(value: number, decimals?: number): string;
|
|
119
|
-
/**
|
|
120
|
-
* Format a price with locale-aware currency formatting
|
|
121
|
-
* Uses Intl.NumberFormat for proper localization
|
|
122
|
-
*
|
|
123
|
-
* @param amount - Price amount (number or string)
|
|
124
|
-
* @param currency - ISO 4217 currency code (default: "GHS")
|
|
125
|
-
* @param locale - BCP 47 locale string (default: "en-US")
|
|
126
|
-
* @returns Formatted price string
|
|
127
|
-
*
|
|
128
|
-
* @example
|
|
129
|
-
* formatPrice(29.99, 'USD') // "$29.99"
|
|
130
|
-
* formatPrice(29.99, 'GHS') // "GH₵29.99"
|
|
131
|
-
* formatPrice('29.99', 'EUR') // "€29.99"
|
|
132
|
-
* formatPrice(1234.56, 'USD', 'de-DE') // "1.234,56 $"
|
|
133
|
-
*/
|
|
134
|
-
declare function formatPrice(amount: number | Money, currency?: CurrencyCode, locale?: string): string;
|
|
135
|
-
/**
|
|
136
|
-
* Format a price with +/- sign for adjustments
|
|
137
|
-
* Useful for showing price changes, modifiers, or discounts
|
|
138
|
-
*
|
|
139
|
-
* @param amount - Adjustment amount (positive or negative)
|
|
140
|
-
* @param currency - ISO 4217 currency code (default: "GHS")
|
|
141
|
-
* @param locale - BCP 47 locale string (default: "en-US")
|
|
142
|
-
* @returns Formatted adjustment string with sign
|
|
143
|
-
*
|
|
144
|
-
* @example
|
|
145
|
-
* formatPriceAdjustment(5.00, 'USD') // "+$5.00"
|
|
146
|
-
* formatPriceAdjustment(-3.50, 'GHS') // "-GH₵3.50"
|
|
147
|
-
* formatPriceAdjustment(0, 'EUR') // "€0.00"
|
|
148
|
-
*/
|
|
149
|
-
declare function formatPriceAdjustment(amount: number, currency?: CurrencyCode, locale?: string): string;
|
|
150
|
-
/**
|
|
151
|
-
* Format a price compactly for large numbers
|
|
152
|
-
* Uses K/M/B suffixes for thousands, millions, billions
|
|
153
|
-
*
|
|
154
|
-
* @param amount - Price amount (number or string)
|
|
155
|
-
* @param currency - ISO 4217 currency code (default: "GHS")
|
|
156
|
-
* @param decimals - Decimal places for compact notation (default: 1)
|
|
157
|
-
* @returns Compact formatted price
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* formatPriceCompact(999, 'USD') // "$999.00"
|
|
161
|
-
* formatPriceCompact(1500, 'GHS') // "GH₵1.5K"
|
|
162
|
-
* formatPriceCompact(2500000, 'USD') // "$2.5M"
|
|
163
|
-
* formatPriceCompact(1200000000, 'EUR') // "€1.2B"
|
|
164
|
-
*/
|
|
165
|
-
declare function formatPriceCompact(amount: number | Money, currency?: CurrencyCode, decimals?: number): string;
|
|
166
|
-
/**
|
|
167
|
-
* Simple currency symbol + amount format
|
|
168
|
-
* Lighter alternative to formatPrice without Intl
|
|
169
|
-
*
|
|
170
|
-
* @param amount - Price amount (number or string)
|
|
171
|
-
* @param currency - ISO 4217 currency code (default: "GHS")
|
|
172
|
-
* @returns Simple formatted price
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* formatMoney(29.99, 'USD') // "$29.99"
|
|
176
|
-
* formatMoney('15.00', 'GHS') // "GH₵15.00"
|
|
177
|
-
*/
|
|
178
|
-
declare function formatMoney(amount: Money | number, currency?: CurrencyCode): string;
|
|
179
|
-
/**
|
|
180
|
-
* Parse a price string or number to a numeric value
|
|
181
|
-
* Handles various input formats gracefully
|
|
182
|
-
*
|
|
183
|
-
* @param value - Value to parse (string, number, or undefined)
|
|
184
|
-
* @returns Parsed numeric value, or 0 if invalid
|
|
185
|
-
*
|
|
186
|
-
* @example
|
|
187
|
-
* parsePrice('29.99') // 29.99
|
|
188
|
-
* parsePrice(29.99) // 29.99
|
|
189
|
-
* parsePrice('$29.99') // 29.99 (strips non-numeric prefix)
|
|
190
|
-
* parsePrice(undefined) // 0
|
|
191
|
-
* parsePrice('invalid') // 0
|
|
192
|
-
*/
|
|
193
|
-
declare function parsePrice(value: Money | string | number | undefined | null): number;
|
|
194
|
-
/** Check whether tax info exists on the selected price payload. */
|
|
195
|
-
declare function hasTaxInfo(priceInfo: {
|
|
196
|
-
tax_info?: PricePathTaxInfo;
|
|
197
|
-
}): boolean;
|
|
198
|
-
/** Get tax amount from a selected price payload, defaulting to 0 when tax info is absent. */
|
|
199
|
-
declare function getTaxAmount(priceInfo: {
|
|
200
|
-
tax_info?: PricePathTaxInfo;
|
|
201
|
-
}): number;
|
|
202
|
-
/** Check whether the selected price is tax-inclusive. */
|
|
203
|
-
declare function isTaxInclusive(priceInfo: {
|
|
204
|
-
tax_info?: PricePathTaxInfo;
|
|
205
|
-
}): boolean;
|
|
206
|
-
/**
|
|
207
|
-
* Format a final price with tax annotation.
|
|
208
|
-
* Examples: "$10.50 (incl. tax)" or "$10.50 + $1.05 tax"
|
|
209
|
-
*/
|
|
210
|
-
declare function formatPriceWithTax(priceInfo: {
|
|
211
|
-
final_price: Money;
|
|
212
|
-
tax_info?: PricePathTaxInfo;
|
|
213
|
-
}, currency?: CurrencyCode): string;
|
|
214
|
-
/**
|
|
215
|
-
* Get the display price from a product.
|
|
216
|
-
* Prefers quote-aware price_info, then plain price fields.
|
|
217
|
-
*
|
|
218
|
-
* @param product - Product with price data
|
|
219
|
-
* @returns The final price to display
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* const price = getDisplayPrice(product);
|
|
223
|
-
* console.log(formatPrice(price, 'GHS')); // "GH₵29.99"
|
|
224
|
-
*/
|
|
225
|
-
declare function getDisplayPrice(product: ProductWithPrice): number;
|
|
226
|
-
/**
|
|
227
|
-
* Get the base price from a product (before markup/discount)
|
|
228
|
-
*
|
|
229
|
-
* @param product - Product with price data
|
|
230
|
-
* @returns The base price before adjustments
|
|
231
|
-
*/
|
|
232
|
-
declare function getBasePrice(product: ProductWithPrice): number;
|
|
233
|
-
/**
|
|
234
|
-
* Check if a product is on sale (discounted)
|
|
235
|
-
*
|
|
236
|
-
* @param product - Product with price data
|
|
237
|
-
* @returns True if the final price is less than the base price
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* if (isOnSale(product)) {
|
|
241
|
-
* return <Badge>Sale!</Badge>;
|
|
242
|
-
* }
|
|
243
|
-
*/
|
|
244
|
-
declare function isOnSale(product: ProductWithPrice): boolean;
|
|
245
|
-
/**
|
|
246
|
-
* Get the discount percentage for a product on sale
|
|
247
|
-
*
|
|
248
|
-
* @param product - Product with price data
|
|
249
|
-
* @returns Discount percentage (0-100), or 0 if not on sale
|
|
250
|
-
*
|
|
251
|
-
* @example
|
|
252
|
-
* const discount = getDiscountPercentage(product);
|
|
253
|
-
* if (discount > 0) {
|
|
254
|
-
* return <Badge>{discount}% OFF</Badge>;
|
|
255
|
-
* }
|
|
256
|
-
*/
|
|
257
|
-
declare function getDiscountPercentage(product: ProductWithPrice): number;
|
|
258
|
-
/**
|
|
259
|
-
* Get the markup percentage for a product
|
|
260
|
-
*
|
|
261
|
-
* @param product - Product with price data
|
|
262
|
-
* @returns Markup percentage, or 0 if no markup
|
|
263
|
-
*/
|
|
264
|
-
declare function getMarkupPercentage(product: ProductWithPrice): number;
|
|
265
|
-
/**
|
|
266
|
-
* Get the currency for a product
|
|
267
|
-
*
|
|
268
|
-
* @param product - Product with price data
|
|
269
|
-
* @returns Currency code (default: "GHS")
|
|
270
|
-
*/
|
|
271
|
-
declare function getProductCurrency(product: ProductWithPrice): CurrencyCode;
|
|
272
|
-
/**
|
|
273
|
-
* Format a product's display price
|
|
274
|
-
* Convenience function combining getDisplayPrice and formatPrice
|
|
275
|
-
*
|
|
276
|
-
* @param product - Product with price data
|
|
277
|
-
* @param locale - BCP 47 locale string (default: "en-US")
|
|
278
|
-
* @returns Formatted price string
|
|
279
|
-
*
|
|
280
|
-
* @example
|
|
281
|
-
* <span>{formatProductPrice(product)}</span> // "GH₵29.99"
|
|
282
|
-
*/
|
|
283
|
-
declare function formatProductPrice(product: ProductWithPrice, locale?: string): string;
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Categorize payment errors into user-friendly messages
|
|
287
|
-
*/
|
|
288
|
-
declare function categorizePaymentError(error: Error, errorCode?: string): PaymentErrorDetails;
|
|
289
|
-
/**
|
|
290
|
-
* Normalize payment response from different formats into a standard PaymentResponse
|
|
291
|
-
*/
|
|
292
|
-
declare function normalizePaymentResponse(response: unknown): PaymentResponse;
|
|
293
|
-
declare function isPaymentStatusSuccess(status: string | undefined): boolean;
|
|
294
|
-
declare function isPaymentStatusFailure(status: string | undefined): boolean;
|
|
295
|
-
declare function isPaymentStatusRequiresAction(status: string | undefined): boolean;
|
|
296
|
-
/**
|
|
297
|
-
* Normalize payment status response into a standard format
|
|
298
|
-
*/
|
|
299
|
-
declare function normalizeStatusResponse(response: unknown): PaymentStatusResponse;
|
|
300
|
-
/** Mobile money provider display names */
|
|
301
|
-
declare const MOBILE_MONEY_PROVIDERS: {
|
|
302
|
-
readonly mtn: {
|
|
303
|
-
readonly name: "MTN Mobile Money";
|
|
304
|
-
readonly prefix: readonly ["024", "054", "055", "059"];
|
|
305
|
-
};
|
|
306
|
-
readonly vodafone: {
|
|
307
|
-
readonly name: "Vodafone Cash";
|
|
308
|
-
readonly prefix: readonly ["020", "050"];
|
|
309
|
-
};
|
|
310
|
-
readonly airtel: {
|
|
311
|
-
readonly name: "AirtelTigo Money";
|
|
312
|
-
readonly prefix: readonly ["027", "057", "026", "056"];
|
|
313
|
-
};
|
|
314
|
-
};
|
|
315
|
-
/**
|
|
316
|
-
* Detect mobile money provider from phone number
|
|
317
|
-
*/
|
|
318
|
-
declare function detectMobileMoneyProvider(phoneNumber: string): "mtn" | "vodafone" | "airtel" | null;
|
|
319
|
-
|
|
320
|
-
export { type ProductWithPrice as A, type FormatCompactOptions as B, CURRENCY_SYMBOLS as C, type FormatPriceOptions as F, MOBILE_MONEY_PROVIDERS as M, type PriceInfo as P, type TaxComponent as T, formatPriceAdjustment as a, formatPriceCompact as b, formatMoney as c, formatNumberCompact as d, formatProductPrice as e, formatPrice as f, getTaxAmount as g, hasTaxInfo as h, isTaxInclusive as i, formatPriceWithTax as j, getCurrencySymbol as k, getDisplayPrice as l, getBasePrice as m, isOnSale as n, getDiscountPercentage as o, parsePrice as p, getMarkupPercentage as q, getProductCurrency as r, categorizePaymentError as s, normalizePaymentResponse as t, normalizeStatusResponse as u, isPaymentStatusFailure as v, isPaymentStatusRequiresAction as w, isPaymentStatusSuccess as x, detectMobileMoneyProvider as y, type TaxInfo as z };
|