@develemit/storefront 0.7.0 → 0.9.0

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["_Fragment","_Fragment","_Fragment","formatPrice","VAR_NAMES","themeToCssVars","vars","key","value","theme","defaults","priority","slug","StorefrontContext","createContext","StorefrontProvider","overrides","hrefs","useStorefront","useContext","PriceTag","classes","sold","className","priceCents","currency","locale","defaultAvailabilityLabels","AvailabilityBadge","text","labels","variant","label","availability","quantity","ProductCard","product","image","renderLink","Fragment","renderImage","ProductGrid","products","emptyState","index","priorityCount","CategoryFilter","ordered","sortCategories","categories","controlled","onSelect","item","active","selected","allLabel","c","formatLabel","ProductGallery","setActive","useState","images","event","i","ShopView","slots","activeCategory","formatCategoryLabel","sortForDisplay","nextHref","loadMoreLabel","ProductDetailView","FeaturedProducts","heading","emptyCart","addLine","first","state","line","existing","l","cartReducer","action","subtotalCents","sum","itemCount","localStorageCartStorage","raw","parsed","result","cartLineSchema","CartContext","defaultStorage","CartProvider","dispatch","useReducer","hydrated","setHydrated","isOpen","setOpen","stateRef","useRef","useEffect","cancelled","storage","loaded","add","useCallback","useMemo","useCart","ctx","defaultDiscountLabels","discountReasonMessage","reason","defaultCheckoutLabels","assign","url","useCheckout","cart","busy","setBusy","message","setMessage","startCheckout","lines","options","onDiscountInvalid","remove","onRedirect","defaultAddToCartLabels","AddToCartButton","inCart","unavailable","atCap","disabled","handleClick","openOnAdd","defaultCartLineItemLabels","CartLineItem","editable","setQuantity","parseDurationMs","match","amount","useDrawerPresence","durationMs","setDurationMs","wasOpen","setWasOpen","lingering","setLingering","backdropRef","timer","DiscountField","open","setValue","submit","code","onApply","applied","onRemove","error","pending","useDiscount","items","signature","setApplied","setError","setPending","latest","request","previewedSignature","clear","run","current","id","onReject","apply","delay","discount","CartSummary","subtotal","discountLabels","checkoutDisabled","onCheckout","checkoutLabel","defaultCartDrawerLabels","FOCUSABLE","CartDrawer","panelRef","presence","trigger","handleKeyDown","last","summary","CartButton","count","showCount","hideZeroCount","ariaLabel","labelHidden","icon","defaultCheckoutSuccessLabels","addressLines","region","address","CheckoutSuccessView","money","cents","order","continueProps","continueHref","continueLink","actions","showReference","showSummaryHeading","fulfillment","ClearCartOnMount","cleared"],"sources":["../src/lib/theme.ts","../src/lib/provider.tsx","../src/components/price-tag/price-tag.tsx","../src/components/availability-badge/availability-badge.tsx","../src/components/product-card/product-card.tsx","../src/components/product-grid/product-grid.tsx","../src/components/category-filter/category-filter.tsx","../src/components/product-gallery/product-gallery.tsx","../src/views/shop-view.tsx","../src/views/product-detail-view.tsx","../src/views/featured-products.tsx","../src/cart/cart-reducer.ts","../src/cart/cart-storage.ts","../src/cart/cart-provider.tsx","../src/components/cart-summary/discount-types.ts","../src/cart/use-checkout.ts","../src/components/add-to-cart-button/add-to-cart-button.tsx","../src/components/cart-line-item/cart-line-item.tsx","../src/lib/parse-duration.ts","../src/components/cart-drawer/use-drawer-presence.ts","../src/components/cart-summary/discount-field.tsx","../src/components/cart-summary/use-discount.ts","../src/components/cart-summary/cart-summary.tsx","../src/components/cart-drawer/cart-drawer.tsx","../src/components/cart-button/cart-button.tsx","../src/components/checkout-success/labels.ts","../src/components/checkout-success/checkout-success-view.tsx","../src/cart/clear-cart-on-mount.tsx"],"sourcesContent":["export interface StorefrontTheme {\n colorBg?: string;\n colorSurface?: string;\n colorFg?: string;\n colorMuted?: string;\n colorAccent?: string;\n colorAccentFg?: string;\n colorBorder?: string;\n colorSold?: string;\n radius?: string;\n fontDisplay?: string;\n fontBody?: string;\n spaceUnit?: string;\n}\n\nconst VAR_NAMES: Record<keyof StorefrontTheme, string> = {\n colorBg: '--sf-color-bg',\n colorSurface: '--sf-color-surface',\n colorFg: '--sf-color-fg',\n colorMuted: '--sf-color-muted',\n colorAccent: '--sf-color-accent',\n colorAccentFg: '--sf-color-accent-fg',\n colorBorder: '--sf-color-border',\n colorSold: '--sf-color-sold',\n radius: '--sf-radius',\n fontDisplay: '--sf-font-display',\n fontBody: '--sf-font-body',\n spaceUnit: '--sf-space-unit',\n};\n\nexport function themeToCssVars(theme: StorefrontTheme): Record<string, string> {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(VAR_NAMES) as (keyof StorefrontTheme)[]) {\n const value = theme[key];\n if (value !== undefined) vars[VAR_NAMES[key]] = value;\n }\n return vars;\n}\n","import { createContext, useContext, type ReactNode } from 'react';\n\nexport interface LinkProps {\n href: string;\n children: ReactNode;\n className?: string;\n}\n\nexport interface ImageProps {\n src: string;\n alt: string;\n sizes?: string;\n className?: string;\n priority?: boolean;\n}\n\nexport interface StorefrontConfig {\n renderLink: (props: LinkProps) => ReactNode;\n renderImage: (props: ImageProps) => ReactNode;\n locale: string;\n hrefs: {\n product: (slug: string) => string;\n category: (slug: string) => string;\n };\n}\n\nconst defaults: StorefrontConfig = {\n renderLink: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n renderImage: ({ src, alt, sizes, className, priority }) => (\n <img\n src={src}\n alt={alt}\n sizes={sizes}\n className={className}\n loading={priority ? 'eager' : 'lazy'}\n decoding=\"async\"\n fetchPriority={priority ? 'high' : undefined}\n />\n ),\n locale: 'en-US',\n hrefs: {\n product: (slug) => `/shop/${slug}`,\n category: (slug) => `/shop?category=${slug}`,\n },\n};\n\nconst StorefrontContext = createContext<StorefrontConfig>(defaults);\n\nexport interface StorefrontProviderProps extends Partial<\n Omit<StorefrontConfig, 'hrefs'>\n> {\n hrefs?: Partial<StorefrontConfig['hrefs']>;\n children: ReactNode;\n}\n\nexport function StorefrontProvider({\n children,\n hrefs,\n ...overrides\n}: StorefrontProviderProps) {\n const value: StorefrontConfig = {\n ...defaults,\n ...overrides,\n hrefs: { ...defaults.hrefs, ...hrefs },\n };\n return (\n <StorefrontContext.Provider value={value}>\n {children}\n </StorefrontContext.Provider>\n );\n}\n\nexport function useStorefront(): StorefrontConfig {\n return useContext(StorefrontContext);\n}\n","import { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface PriceTagProps {\n priceCents: number;\n currency: string;\n sold?: boolean;\n className?: string;\n}\n\nexport function PriceTag({\n priceCents,\n currency,\n sold = false,\n className,\n}: PriceTagProps) {\n const { locale } = useStorefront();\n const classes = ['sf-price', sold && 'sf-price--sold', className]\n .filter(Boolean)\n .join(' ');\n return (\n <span className={classes}>{formatPrice(priceCents, currency, locale)}</span>\n );\n}\n","import type { ProductAvailability } from '@develemit/commerce-contracts';\n\nexport interface AvailabilityLabels {\n oneOfAKind: string;\n sold: string;\n reserved: string;\n available: string;\n}\n\nexport const defaultAvailabilityLabels: AvailabilityLabels = {\n oneOfAKind: 'One of a kind',\n sold: 'Sold',\n reserved: 'On hold',\n available: 'Available',\n};\n\nexport interface AvailabilityBadgeProps {\n availability: ProductAvailability;\n quantity: number;\n labels?: Partial<AvailabilityLabels>;\n className?: string;\n}\n\nexport function AvailabilityBadge({\n availability,\n quantity,\n labels,\n className,\n}: AvailabilityBadgeProps) {\n const text = { ...defaultAvailabilityLabels, ...labels };\n let variant: string;\n let label: string;\n if (availability === 'sold') {\n variant = 'sold';\n label = text.sold;\n } else if (availability === 'reserved') {\n variant = 'reserved';\n label = text.reserved;\n } else if (quantity === 1) {\n variant = 'unique';\n label = text.oneOfAKind;\n } else {\n variant = 'available';\n label = text.available;\n }\n const classes = ['sf-badge', `sf-badge--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n return (\n <span role=\"status\" className={classes}>\n {label}\n </span>\n );\n}\n","import type { ProductCard as ProductCardData } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\nimport {\n AvailabilityBadge,\n type AvailabilityLabels,\n} from '../availability-badge/availability-badge';\nimport { PriceTag } from '../price-tag/price-tag';\n\nexport interface ProductCardProps {\n product: ProductCardData;\n labels?: Partial<AvailabilityLabels>;\n sizes?: string;\n priority?: boolean;\n}\n\nexport function ProductCard({\n product,\n labels,\n sizes,\n priority,\n}: ProductCardProps) {\n const { renderLink, renderImage, hrefs } = useStorefront();\n const sold = product.availability === 'sold';\n const image = product.images[0];\n const classes = ['sf-card', sold && 'sf-card--sold']\n .filter(Boolean)\n .join(' ');\n return (\n <article className={classes}>\n {renderLink({\n href: hrefs.product(product.slug),\n className: 'sf-card__link',\n children: (\n <>\n <div className=\"sf-card__media\">\n {image ? (\n renderImage({\n src: image.url,\n alt: image.alt,\n sizes,\n className: 'sf-card__image',\n priority,\n })\n ) : (\n <div className=\"sf-card__placeholder\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"sf-card__body\">\n <h3 className=\"sf-card__title\">{product.name}</h3>\n <PriceTag\n priceCents={product.priceCents}\n currency={product.currency}\n sold={sold}\n />\n <AvailabilityBadge\n availability={product.availability}\n quantity={product.quantity}\n labels={labels}\n />\n </div>\n </>\n ),\n })}\n </article>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { ProductCard as ProductCardData } from '@develemit/commerce-contracts';\nimport type { AvailabilityLabels } from '../availability-badge/availability-badge';\nimport { ProductCard } from '../product-card/product-card';\n\nexport interface ProductGridProps {\n products: ProductCardData[];\n emptyState?: ReactNode;\n labels?: Partial<AvailabilityLabels>;\n priorityCount?: number;\n}\n\nexport function ProductGrid({\n products,\n emptyState,\n labels,\n priorityCount = 0,\n}: ProductGridProps) {\n if (products.length === 0) {\n return (\n <div className=\"sf-grid-empty\">{emptyState ?? 'No products found.'}</div>\n );\n }\n return (\n <ul className=\"sf-grid\">\n {products.map((product, index) => (\n <li key={product.id} className=\"sf-grid__item\">\n <ProductCard\n product={product}\n labels={labels}\n priority={index < priorityCount}\n />\n </li>\n ))}\n </ul>\n );\n}\n","import type { Category } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CategoryFilterProps {\n categories: Category[];\n selected?: string;\n onSelect?: (slug: string | undefined) => void;\n allLabel?: string;\n label?: string;\n formatLabel?: (category: Category) => string;\n sortCategories?: (a: Category, b: Category) => number;\n}\n\nexport function CategoryFilter({\n categories,\n selected,\n onSelect,\n allLabel = 'All',\n label = 'Categories',\n formatLabel,\n sortCategories,\n}: CategoryFilterProps) {\n const { renderLink, hrefs } = useStorefront();\n const ordered = sortCategories\n ? [...categories].sort(sortCategories)\n : categories;\n const controlled = onSelect !== undefined;\n\n const item = (slug: string | undefined, text: string, href: string) => {\n const active = selected === slug;\n const className = `sf-filter__item${active ? ' sf-filter__item--active' : ''}`;\n return (\n <li key={slug ?? '__all'}>\n {controlled ? (\n <button\n type=\"button\"\n className={className}\n aria-pressed={active}\n onClick={() => onSelect(slug)}\n >\n {text}\n </button>\n ) : (\n renderLink({\n href,\n className,\n children: text,\n })\n )}\n </li>\n );\n };\n\n return (\n <nav aria-label={label} className=\"sf-filter\">\n <ul className=\"sf-filter__list\">\n {item(undefined, allLabel, hrefs.category(''))}\n {ordered.map((c) =>\n item(\n c.slug,\n formatLabel ? formatLabel(c) : c.label,\n hrefs.category(c.slug),\n ),\n )}\n </ul>\n </nav>\n );\n}\n","import { useState, type KeyboardEvent } from 'react';\nimport type { ProductImage } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface ProductGalleryProps {\n images: ProductImage[];\n sizes?: string;\n}\n\nexport function ProductGallery({ images, sizes }: ProductGalleryProps) {\n const { renderImage } = useStorefront();\n const [active, setActive] = useState(0);\n\n if (images.length === 0) return null;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'ArrowRight') {\n setActive((i) => (i + 1) % images.length);\n } else if (event.key === 'ArrowLeft') {\n setActive((i) => (i - 1 + images.length) % images.length);\n } else if (event.key === 'Home') {\n setActive(0);\n } else if (event.key === 'End') {\n setActive(images.length - 1);\n } else {\n return;\n }\n event.preventDefault();\n };\n\n return (\n <div\n className=\"sf-gallery\"\n role=\"group\"\n aria-roledescription=\"carousel\"\n aria-label=\"Product images\"\n tabIndex={0}\n onKeyDown={onKeyDown}\n >\n <div className=\"sf-gallery__stage\">\n {images.map((image, index) => (\n <div\n key={image.url}\n className=\"sf-gallery__slide\"\n hidden={index !== active}\n >\n {renderImage({\n src: image.url,\n alt: image.alt,\n sizes,\n priority: index === active,\n className: 'sf-gallery__image',\n })}\n </div>\n ))}\n </div>\n {images.length > 1 && (\n <div className=\"sf-gallery__thumbs\">\n {images.map((image, index) => (\n <button\n key={image.url}\n type=\"button\"\n className={`sf-gallery__thumb${index === active ? ' sf-gallery__thumb--active' : ''}`}\n aria-label={`Show image ${index + 1} of ${images.length}`}\n aria-current={index === active}\n onClick={() => setActive(index)}\n >\n {renderImage({\n src: image.url,\n alt: '',\n className: 'sf-gallery__thumb-image',\n })}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { Category, ProductCard } from '@develemit/commerce-contracts';\nimport { CategoryFilter } from '../components/category-filter/category-filter';\nimport { ProductGrid } from '../components/product-grid/product-grid';\nimport type { AvailabilityLabels } from '../components/availability-badge/availability-badge';\nimport { useStorefront } from '../lib/provider';\nimport { sortForDisplay } from './sort-for-display';\n\nexport interface ShopViewSlots {\n header?: ReactNode;\n intro?: ReactNode;\n emptyState?: ReactNode;\n}\n\nexport interface ShopViewProps {\n products: ProductCard[];\n categories: Category[];\n activeCategory?: string;\n slots?: ShopViewSlots;\n labels?: Partial<AvailabilityLabels>;\n allLabel?: string;\n formatCategoryLabel?: (category: Category) => string;\n sortCategories?: (a: Category, b: Category) => number;\n nextHref?: string;\n loadMoreLabel?: string;\n}\n\nexport function ShopView({\n products,\n categories,\n activeCategory,\n slots = {},\n labels,\n allLabel,\n formatCategoryLabel,\n sortCategories,\n nextHref,\n loadMoreLabel = 'Load more',\n}: ShopViewProps) {\n const { renderLink } = useStorefront();\n return (\n <div className=\"sf-shop\">\n {slots.header}\n {slots.intro}\n <CategoryFilter\n categories={categories}\n selected={activeCategory}\n allLabel={allLabel}\n formatLabel={formatCategoryLabel}\n sortCategories={sortCategories}\n />\n <ProductGrid\n products={sortForDisplay(products)}\n emptyState={slots.emptyState}\n labels={labels}\n priorityCount={6}\n />\n {nextHref && (\n <div className=\"sf-shop__pager\">\n {renderLink({\n href: nextHref,\n className: 'sf-shop__more',\n children: loadMoreLabel,\n })}\n </div>\n )}\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { Product } from '@develemit/commerce-contracts';\nimport {\n AvailabilityBadge,\n type AvailabilityLabels,\n} from '../components/availability-badge/availability-badge';\nimport { PriceTag } from '../components/price-tag/price-tag';\nimport { ProductGallery } from '../components/product-gallery/product-gallery';\n\nexport interface ProductDetailViewSlots {\n actions?: ReactNode;\n afterDetails?: ReactNode;\n}\n\nexport interface ProductDetailViewProps {\n product: Product;\n slots?: ProductDetailViewSlots;\n labels?: Partial<AvailabilityLabels>;\n}\n\nexport function ProductDetailView({\n product,\n slots = {},\n labels,\n}: ProductDetailViewProps) {\n return (\n <article className=\"sf-detail\">\n <ProductGallery images={product.images} />\n <div className=\"sf-detail__body\">\n <h1 className=\"sf-detail__title\">{product.name}</h1>\n <PriceTag priceCents={product.priceCents} currency={product.currency} />\n <AvailabilityBadge\n availability={product.availability}\n quantity={product.quantity}\n labels={labels}\n />\n <p className=\"sf-detail__description\">{product.description}</p>\n {(product.materials || product.dimensions) && (\n <dl className=\"sf-detail__specs\">\n {product.materials && (\n <>\n <dt>Materials</dt>\n <dd>{product.materials}</dd>\n </>\n )}\n {product.dimensions && (\n <>\n <dt>Dimensions</dt>\n <dd>{product.dimensions}</dd>\n </>\n )}\n </dl>\n )}\n {slots.actions && (\n <div className=\"sf-detail__actions\">{slots.actions}</div>\n )}\n {slots.afterDetails}\n </div>\n </article>\n );\n}\n","import type { ProductCard } from '@develemit/commerce-contracts';\nimport type { AvailabilityLabels } from '../components/availability-badge/availability-badge';\nimport { ProductGrid } from '../components/product-grid/product-grid';\n\nexport interface FeaturedProductsProps {\n products: ProductCard[];\n heading?: string;\n labels?: Partial<AvailabilityLabels>;\n}\n\nexport function FeaturedProducts({\n products,\n heading = 'Featured',\n labels,\n}: FeaturedProductsProps) {\n if (products.length === 0) return null;\n return (\n <section className=\"sf-featured\">\n <h2 className=\"sf-featured__heading\">{heading}</h2>\n <ProductGrid products={products} labels={labels} priorityCount={6} />\n </section>\n );\n}\n","import type { CartLine } from '@develemit/commerce-contracts';\n\nexport interface CartState {\n lines: CartLine[];\n}\n\nexport type CartAction =\n | { type: 'add'; line: CartLine }\n | { type: 'remove'; productId: string }\n | { type: 'setQuantity'; productId: string; quantity: number }\n | { type: 'clear' }\n | { type: 'hydrate'; state: CartState };\n\nexport interface AddResult {\n state: CartState;\n added: boolean;\n capped: boolean;\n reason?: 'currency-mismatch';\n}\n\nexport const emptyCart: CartState = { lines: [] };\n\nexport function addLine(state: CartState, line: CartLine): AddResult {\n const first = state.lines[0];\n if (first && first.currency !== line.currency) {\n return { state, added: false, capped: false, reason: 'currency-mismatch' };\n }\n const existing = state.lines.find((l) => l.productId === line.productId);\n if (!existing) {\n const quantity = Math.min(line.quantity, line.maxQuantity);\n return {\n state: { lines: [...state.lines, { ...line, quantity }] },\n added: true,\n capped: quantity < line.quantity,\n };\n }\n if (existing.quantity >= existing.maxQuantity) {\n return { state, added: false, capped: true };\n }\n const quantity = Math.min(\n existing.quantity + line.quantity,\n existing.maxQuantity,\n );\n return {\n state: {\n lines: state.lines.map((l) =>\n l.productId === line.productId ? { ...l, quantity } : l,\n ),\n },\n added: true,\n capped: existing.quantity + line.quantity > existing.maxQuantity,\n };\n}\n\nexport function cartReducer(state: CartState, action: CartAction): CartState {\n switch (action.type) {\n case 'add':\n return addLine(state, action.line).state;\n case 'remove':\n return {\n lines: state.lines.filter((l) => l.productId !== action.productId),\n };\n case 'setQuantity': {\n if (action.quantity <= 0) {\n return cartReducer(state, {\n type: 'remove',\n productId: action.productId,\n });\n }\n return {\n lines: state.lines.map((l) =>\n l.productId === action.productId\n ? { ...l, quantity: Math.min(action.quantity, l.maxQuantity) }\n : l,\n ),\n };\n }\n case 'clear':\n return emptyCart;\n case 'hydrate':\n return action.state;\n }\n}\n\nexport function subtotalCents(state: CartState): number {\n return state.lines.reduce((sum, l) => sum + l.priceCents * l.quantity, 0);\n}\n\nexport function itemCount(state: CartState): number {\n return state.lines.reduce((sum, l) => sum + l.quantity, 0);\n}\n","import { cartLineSchema } from '@develemit/commerce-contracts';\nimport { emptyCart, type CartState } from './cart-reducer';\n\nexport interface CartStorage {\n load: () => CartState | Promise<CartState>;\n save: (state: CartState) => void | Promise<void>;\n}\n\nexport function localStorageCartStorage(key = 'sf-cart'): CartStorage {\n return {\n load() {\n if (typeof window === 'undefined') return emptyCart;\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) return emptyCart;\n const parsed: unknown = JSON.parse(raw);\n const lines = Array.isArray((parsed as CartState)?.lines)\n ? (parsed as CartState).lines\n : [];\n const valid = lines.flatMap((line) => {\n const result = cartLineSchema.safeParse(line);\n return result.success ? [result.data] : [];\n });\n return { lines: valid };\n } catch {\n return emptyCart;\n }\n },\n save(state) {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // storage full or blocked: the cart simply stays in memory\n }\n },\n };\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { CartLine } from '@develemit/commerce-contracts';\nimport {\n addLine,\n cartReducer,\n emptyCart,\n itemCount,\n subtotalCents,\n type AddResult,\n type CartState,\n} from './cart-reducer';\nimport { localStorageCartStorage, type CartStorage } from './cart-storage';\n\nexport interface CartContextValue {\n lines: CartLine[];\n itemCount: number;\n subtotalCents: number;\n currency: string | null;\n hydrated: boolean;\n isOpen: boolean;\n open: () => void;\n close: () => void;\n add: (line: CartLine) => AddResult;\n remove: (productId: string) => void;\n setQuantity: (productId: string, quantity: number) => void;\n clear: () => void;\n}\n\nconst CartContext = createContext<CartContextValue | null>(null);\n\nexport interface CartProviderProps {\n storage?: CartStorage;\n children: ReactNode;\n}\n\nconst defaultStorage = localStorageCartStorage();\n\nexport function CartProvider({\n storage = defaultStorage,\n children,\n}: CartProviderProps) {\n const [state, dispatch] = useReducer(cartReducer, emptyCart);\n const [hydrated, setHydrated] = useState(false);\n const [isOpen, setOpen] = useState(false);\n const stateRef = useRef<CartState>(state);\n useEffect(() => {\n stateRef.current = state;\n }, [state]);\n\n useEffect(() => {\n let cancelled = false;\n Promise.resolve(storage.load()).then((loaded) => {\n if (cancelled) return;\n dispatch({ type: 'hydrate', state: loaded });\n setHydrated(true);\n });\n return () => {\n cancelled = true;\n };\n }, [storage]);\n\n useEffect(() => {\n if (hydrated) void storage.save(state);\n }, [hydrated, state, storage]);\n\n const add = useCallback((line: CartLine) => {\n const result = addLine(stateRef.current, line);\n if (result.added) {\n stateRef.current = result.state;\n dispatch({ type: 'add', line });\n }\n return result;\n }, []);\n\n const value = useMemo<CartContextValue>(\n () => ({\n lines: state.lines,\n itemCount: itemCount(state),\n subtotalCents: subtotalCents(state),\n currency: state.lines[0]?.currency ?? null,\n hydrated,\n isOpen,\n open: () => setOpen(true),\n close: () => setOpen(false),\n add,\n remove: (productId) => dispatch({ type: 'remove', productId }),\n setQuantity: (productId, quantity) =>\n dispatch({ type: 'setQuantity', productId, quantity }),\n clear: () => dispatch({ type: 'clear' }),\n }),\n [state, hydrated, isOpen, add],\n );\n\n return <CartContext.Provider value={value}>{children}</CartContext.Provider>;\n}\n\nexport function useCart(): CartContextValue {\n const ctx = useContext(CartContext);\n if (!ctx) throw new Error('useCart must be used inside <CartProvider>');\n return ctx;\n}\n","import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\n\nexport type DiscountPreviewResult =\n | { ok: true; code: string; discountCents: number }\n | { ok: false; reason: DiscountRejectionReason };\n\nexport interface CartSummaryDiscount {\n onPreview: (\n code: string,\n items: { productId: string; quantity: number }[],\n ) => Promise<DiscountPreviewResult>;\n onApply?: (code: string | null) => void;\n debounceMs?: number;\n}\n\nexport interface DiscountLabels {\n toggle: string;\n field: string;\n apply: string;\n remove: string;\n line: string;\n reasons: Record<DiscountRejectionReason, string>;\n unavailable: string;\n}\n\nexport const defaultDiscountLabels: DiscountLabels = {\n toggle: 'Have a code?',\n field: 'Discount code',\n apply: 'Apply',\n remove: 'Remove',\n line: 'Discount',\n unavailable: 'We could not check that code. Please try again.',\n reasons: {\n not_found: 'That code is not valid.',\n inactive: 'That code is not valid.',\n not_started: 'That code is not active yet.',\n expired: 'That code has expired.',\n min_subtotal: 'Your cart is below the minimum for that code.',\n exhausted: 'That code is no longer available.',\n currency: 'That code does not apply to this cart.',\n },\n};\n\nexport function discountReasonMessage(\n reason: DiscountRejectionReason,\n overrides?: Partial<DiscountLabels['reasons']>,\n): string {\n return overrides?.[reason] ?? defaultDiscountLabels.reasons[reason];\n}\n","import { useCallback, useState } from 'react';\nimport type { DiscountRejectionReason } from '@develemit/commerce-contracts';\nimport type { CheckoutResult } from '../client/checkout-result';\nimport { discountReasonMessage } from '../components/cart-summary/discount-types';\nimport { useCart } from './cart-provider';\n\nexport interface CheckoutLabels {\n outOfStock: string;\n unavailable: string;\n discountReasons?: Partial<Record<DiscountRejectionReason, string>>;\n}\n\nexport const defaultCheckoutLabels: CheckoutLabels = {\n outOfStock:\n 'Some items just sold or are on hold, so we removed them from your cart.',\n unavailable: 'Checkout is unavailable right now. Please try again.',\n};\n\nexport interface StartCheckoutOptions {\n discountCode?: string;\n}\n\nexport interface UseCheckoutOptions {\n startCheckout: (\n items: { productId: string; quantity: number }[],\n options: StartCheckoutOptions,\n ) => Promise<CheckoutResult>;\n onRedirect?: (url: string) => void;\n onDiscountInvalid?: (reason: DiscountRejectionReason) => void;\n labels?: Partial<CheckoutLabels>;\n}\n\nexport interface UseCheckoutValue {\n checkout: (options?: StartCheckoutOptions) => Promise<void>;\n busy: boolean;\n message: string | null;\n clearMessage: () => void;\n}\n\nconst assign = (url: string) => window.location.assign(url);\n\nexport function useCheckout({\n startCheckout,\n onRedirect = assign,\n onDiscountInvalid,\n labels,\n}: UseCheckoutOptions): UseCheckoutValue {\n const cart = useCart();\n const [busy, setBusy] = useState(false);\n const [message, setMessage] = useState<string | null>(null);\n const { lines, remove } = cart;\n const text = { ...defaultCheckoutLabels, ...labels };\n\n const checkout = useCallback(\n async (options: StartCheckoutOptions = {}) => {\n setBusy(true);\n setMessage(null);\n try {\n const result = await startCheckout(\n lines.map((l) => ({ productId: l.productId, quantity: l.quantity })),\n options,\n );\n if ('url' in result) {\n onRedirect(result.url);\n return;\n }\n if (result.error === 'discount_invalid') {\n setMessage(\n discountReasonMessage(result.reason, text.discountReasons),\n );\n onDiscountInvalid?.(result.reason);\n } else {\n result.productIds.forEach(remove);\n setMessage(text.outOfStock);\n }\n } catch {\n setMessage(text.unavailable);\n }\n setBusy(false);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [startCheckout, onRedirect, onDiscountInvalid, lines, remove, labels],\n );\n\n const clearMessage = useCallback(() => setMessage(null), []);\n\n return { checkout, busy, message, clearMessage };\n}\n","import type { Product } from '@develemit/commerce-contracts';\nimport { useCart } from '../../cart/cart-provider';\n\nexport interface AddToCartLabels {\n add: string;\n inCart: string;\n unavailable: string;\n}\n\nexport const defaultAddToCartLabels: AddToCartLabels = {\n add: 'Add to cart',\n inCart: 'In your cart',\n unavailable: 'Unavailable',\n};\n\nexport interface AddToCartButtonProps {\n product: Product;\n labels?: Partial<AddToCartLabels>;\n openOnAdd?: boolean;\n className?: string;\n}\n\nexport function AddToCartButton({\n product,\n labels,\n openOnAdd = true,\n className,\n}: AddToCartButtonProps) {\n const cart = useCart();\n const text = { ...defaultAddToCartLabels, ...labels };\n const inCart =\n cart.lines.find((l) => l.productId === product.id)?.quantity ?? 0;\n const unavailable = product.availability !== 'available';\n const atCap = inCart >= product.quantity;\n const disabled = !cart.hydrated || unavailable || atCap;\n const label = unavailable ? text.unavailable : atCap ? text.inCart : text.add;\n\n function handleClick() {\n const result = cart.add({\n productId: product.id,\n slug: product.slug,\n name: product.name,\n priceCents: product.priceCents,\n currency: product.currency,\n imageUrl: product.images[0]?.url,\n quantity: 1,\n maxQuantity: product.quantity,\n });\n if (result.added && openOnAdd) cart.open();\n }\n\n return (\n <button\n type=\"button\"\n className={['sf-button', className].filter(Boolean).join(' ')}\n disabled={disabled}\n onClick={handleClick}\n >\n {label}\n </button>\n );\n}\n","import type { CartLine } from '@develemit/commerce-contracts';\nimport { useCart } from '../../cart/cart-provider';\nimport { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CartLineItemLabels {\n remove: string;\n quantity: string;\n}\n\nexport const defaultCartLineItemLabels: CartLineItemLabels = {\n remove: 'Remove',\n quantity: 'Quantity',\n};\n\nexport interface CartLineItemProps {\n line: CartLine;\n labels?: Partial<CartLineItemLabels>;\n}\n\nexport function CartLineItem({ line, labels }: CartLineItemProps) {\n const { remove, setQuantity } = useCart();\n const { locale, renderLink, renderImage, hrefs } = useStorefront();\n const text = { ...defaultCartLineItemLabels, ...labels };\n const editable = line.maxQuantity > 1;\n return (\n <li className=\"sf-cart-line\">\n {line.imageUrl &&\n renderImage({\n src: line.imageUrl,\n alt: '',\n sizes: '80px',\n className: 'sf-cart-line__image',\n })}\n <div className=\"sf-cart-line__body\">\n {renderLink({\n href: hrefs.product(line.slug),\n className: 'sf-cart-line__name',\n children: line.name,\n })}\n <span className=\"sf-cart-line__price\">\n {formatPrice(line.priceCents * line.quantity, line.currency, locale)}\n </span>\n {editable && (\n <label className=\"sf-cart-line__qty\">\n {text.quantity}\n <input\n type=\"number\"\n min={1}\n max={line.maxQuantity}\n value={line.quantity}\n onChange={(e) =>\n setQuantity(line.productId, Number(e.target.value))\n }\n />\n </label>\n )}\n <button\n type=\"button\"\n className=\"sf-link-button sf-cart-line__remove\"\n onClick={() => remove(line.productId)}\n aria-label={`${text.remove} ${line.name}`}\n >\n {text.remove}\n </button>\n </div>\n </li>\n );\n}\n","export function parseDurationMs(value: string): number {\n const match = /^\\s*(-?\\d*\\.?\\d+)\\s*(ms|s)\\s*$/.exec(value);\n if (!match) return 0;\n const amount = Number(match[1]);\n return Math.max(0, match[2] === 's' ? amount * 1000 : amount);\n}\n","import { useEffect, useState, type RefObject } from 'react';\nimport { parseDurationMs } from '../../lib/parse-duration';\n\nexport function useDrawerPresence(\n isOpen: boolean,\n backdropRef: RefObject<HTMLElement | null>,\n) {\n const [durationMs, setDurationMs] = useState(0);\n const [wasOpen, setWasOpen] = useState(isOpen);\n const [lingering, setLingering] = useState(false);\n\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n setLingering(!isOpen && durationMs > 0);\n }\n\n useEffect(() => {\n if (!isOpen || !backdropRef.current) return;\n setDurationMs(\n parseDurationMs(\n getComputedStyle(backdropRef.current).getPropertyValue(\n '--sf-motion-duration',\n ),\n ),\n );\n }, [isOpen, backdropRef]);\n\n useEffect(() => {\n if (!lingering) return;\n const timer = setTimeout(() => setLingering(false), durationMs);\n return () => clearTimeout(timer);\n }, [lingering, durationMs]);\n\n return { mounted: isOpen || lingering, state: isOpen ? 'open' : 'closed' };\n}\n","import { useState, type FormEvent } from 'react';\nimport { formatPrice } from '../../lib/format-price';\nimport type { DiscountLabels } from './discount-types';\nimport type { AppliedDiscount, DiscountError } from './use-discount';\n\nexport interface DiscountFieldProps {\n labels: DiscountLabels;\n applied: AppliedDiscount | null;\n error: DiscountError | null;\n pending: boolean;\n currency: string;\n locale: string;\n onApply: (code: string) => void;\n onRemove: () => void;\n}\n\nexport function DiscountField({\n labels,\n applied,\n error,\n pending,\n currency,\n locale,\n onApply,\n onRemove,\n}: DiscountFieldProps) {\n const [open, setOpen] = useState(false);\n const [value, setValue] = useState('');\n\n function submit(event: FormEvent) {\n event.preventDefault();\n const code = value.trim();\n if (code) onApply(code);\n }\n\n if (applied) {\n return (\n <p className=\"sf-cart-summary__row sf-cart-summary__discount\">\n <span>\n {labels.line} ({applied.code}){' '}\n <button type=\"button\" className=\"sf-link-button\" onClick={onRemove}>\n {labels.remove}\n </button>\n </span>\n <span className=\"sf-price\">\n −{formatPrice(applied.discountCents, currency, locale)}\n </span>\n </p>\n );\n }\n\n const message =\n error === 'unavailable'\n ? labels.unavailable\n : error\n ? labels.reasons[error]\n : null;\n return (\n <div className=\"sf-cart-summary__code\">\n {open ? (\n <form onSubmit={submit} className=\"sf-cart-summary__code-form\">\n <input\n aria-label={labels.field}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n autoCapitalize=\"characters\"\n autoComplete=\"off\"\n />\n <button type=\"submit\" className=\"sf-button\" disabled={pending}>\n {labels.apply}\n </button>\n </form>\n ) : (\n <button\n type=\"button\"\n className=\"sf-link-button\"\n aria-expanded={false}\n onClick={() => setOpen(true)}\n >\n {labels.toggle}\n </button>\n )}\n {message && (\n <p role=\"alert\" className=\"sf-cart-summary__code-error\">\n {message}\n </p>\n )}\n </div>\n );\n}\n","import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useCart } from '../../cart/cart-provider';\nimport type { CartSummaryDiscount } from './discount-types';\n\nexport interface AppliedDiscount {\n code: string;\n discountCents: number;\n}\n\nexport type DiscountError = DiscountRejectionReason | 'unavailable';\n\nexport function useDiscount(discount: CartSummaryDiscount | undefined) {\n const { lines } = useCart();\n const items = lines.map((l) => ({\n productId: l.productId,\n quantity: l.quantity,\n }));\n const signature = items.map((i) => `${i.productId}:${i.quantity}`).join(',');\n const [applied, setApplied] = useState<AppliedDiscount | null>(null);\n const [error, setError] = useState<DiscountError | null>(null);\n const [pending, setPending] = useState(false);\n const latest = useRef({ discount, items });\n useEffect(() => {\n latest.current = { discount, items };\n });\n const request = useRef(0);\n const previewedSignature = useRef(signature);\n\n const clear = useCallback(() => {\n request.current += 1;\n setApplied(null);\n latest.current.discount?.onApply?.(null);\n }, []);\n\n const run = useCallback(\n async (code: string, onReject: (reason: DiscountError) => void) => {\n const current = latest.current;\n if (!current.discount) return;\n const id = ++request.current;\n previewedSignature.current = current.items\n .map((i) => `${i.productId}:${i.quantity}`)\n .join(',');\n setPending(true);\n let result;\n try {\n result = await current.discount.onPreview(code, current.items);\n } catch {\n result = { ok: false as const, reason: 'unavailable' as const };\n }\n if (id !== request.current) return;\n setPending(false);\n if (result.ok) {\n setError(null);\n setApplied({ code: result.code, discountCents: result.discountCents });\n current.discount.onApply?.(result.code);\n } else {\n onReject(result.reason);\n }\n },\n [],\n );\n\n const apply = useCallback(\n (code: string) => run(code, (reason) => setError(reason)),\n [run],\n );\n\n const remove = useCallback(() => {\n setError(null);\n setPending(false);\n clear();\n }, [clear]);\n\n const code = applied?.code;\n const delay = discount?.debounceMs ?? 300;\n useEffect(() => {\n if (!code || signature === previewedSignature.current) return;\n const timer = setTimeout(() => {\n if (latest.current.items.length === 0) {\n clear();\n return;\n }\n void run(code, (reason) => {\n setError(reason);\n clear();\n });\n }, delay);\n return () => clearTimeout(timer);\n }, [code, signature, delay, run, clear]);\n\n return { applied, error, pending, apply, remove };\n}\n","import { useCart } from '../../cart/cart-provider';\nimport { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\nimport { DiscountField } from './discount-field';\nimport {\n defaultDiscountLabels,\n type CartSummaryDiscount,\n type DiscountLabels,\n} from './discount-types';\nimport { useDiscount } from './use-discount';\n\nexport type {\n CartSummaryDiscount,\n DiscountPreviewResult,\n} from './discount-types';\nexport { discountReasonMessage } from './discount-types';\n\nexport interface CartSummaryLabels {\n subtotal: string;\n discount: Partial<Omit<DiscountLabels, 'reasons'>> & {\n reasons?: Partial<DiscountLabels['reasons']>;\n };\n}\n\nexport interface CartSummaryProps {\n onCheckout?: () => void;\n checkoutLabel?: string;\n checkoutDisabled?: boolean;\n labels?: Partial<CartSummaryLabels>;\n discount?: CartSummaryDiscount;\n}\n\nexport function CartSummary({\n onCheckout,\n checkoutLabel = 'Checkout',\n checkoutDisabled = false,\n labels,\n discount,\n}: CartSummaryProps) {\n const cart = useCart();\n const { locale } = useStorefront();\n const subtotal = labels?.subtotal ?? 'Subtotal';\n const state = useDiscount(discount);\n const discountLabels: DiscountLabels = {\n ...defaultDiscountLabels,\n ...labels?.discount,\n reasons: {\n ...defaultDiscountLabels.reasons,\n ...labels?.discount?.reasons,\n },\n };\n return (\n <div className=\"sf-cart-summary\">\n <p className=\"sf-cart-summary__row\">\n <span>{subtotal}</span>\n <span className=\"sf-price\">\n {formatPrice(cart.subtotalCents, cart.currency ?? 'USD', locale)}\n </span>\n </p>\n {discount && cart.lines.length > 0 && (\n <DiscountField\n labels={discountLabels}\n currency={cart.currency ?? 'USD'}\n locale={locale}\n onApply={(code) => void state.apply(code)}\n onRemove={state.remove}\n applied={state.applied}\n error={state.error}\n pending={state.pending}\n />\n )}\n <button\n type=\"button\"\n className=\"sf-button\"\n disabled={checkoutDisabled || !onCheckout || cart.lines.length === 0}\n onClick={onCheckout}\n >\n {checkoutLabel}\n </button>\n </div>\n );\n}\n","import { useEffect, useRef, type KeyboardEvent, type ReactNode } from 'react';\nimport { useCart } from '../../cart/cart-provider';\nimport { CartLineItem } from '../cart-line-item/cart-line-item';\nimport { useDrawerPresence } from './use-drawer-presence';\nimport {\n CartSummary,\n type CartSummaryProps,\n} from '../cart-summary/cart-summary';\n\nexport interface CartDrawerLabels {\n title: string;\n close: string;\n empty: string;\n}\n\nexport const defaultCartDrawerLabels: CartDrawerLabels = {\n title: 'Your cart',\n close: 'Close',\n empty: 'Your cart is empty.',\n};\n\nexport interface CartDrawerProps {\n labels?: Partial<CartDrawerLabels>;\n summary?: CartSummaryProps;\n slots?: CartDrawerSlots;\n}\n\nexport interface CartDrawerSlots {\n empty?: ReactNode;\n summaryNote?: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport function CartDrawer({ labels, summary, slots }: CartDrawerProps) {\n const cart = useCart();\n const text = { ...defaultCartDrawerLabels, ...labels };\n const panelRef = useRef<HTMLDivElement>(null);\n const backdropRef = useRef<HTMLDivElement>(null);\n const { isOpen } = cart;\n const presence = useDrawerPresence(isOpen, backdropRef);\n\n useEffect(() => {\n if (!isOpen) return;\n const trigger = document.activeElement as HTMLElement | null;\n panelRef.current?.querySelector<HTMLElement>(FOCUSABLE)?.focus();\n return () => trigger?.focus();\n }, [isOpen]);\n\n if (!presence.mounted) return null;\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n if (event.key === 'Escape') {\n event.stopPropagation();\n cart.close();\n return;\n }\n if (event.key !== 'Tab') return;\n const items = Array.from(\n panelRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [],\n );\n if (items.length === 0) return;\n const first = items[0];\n const last = items[items.length - 1];\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n return (\n <div\n ref={backdropRef}\n className=\"sf-drawer-backdrop\"\n data-state={presence.state}\n onClick={cart.close}\n >\n <div\n ref={panelRef}\n className=\"sf-drawer\"\n data-state={presence.state}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sf-drawer-title\"\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"sf-drawer__header\">\n <h2 id=\"sf-drawer-title\" className=\"sf-drawer__title\">\n {text.title}\n </h2>\n <button\n type=\"button\"\n className=\"sf-drawer__close\"\n aria-label={text.close}\n onClick={cart.close}\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n {cart.lines.length === 0 ? (\n <div className=\"sf-drawer__empty\">\n {slots?.empty ?? <p>{text.empty}</p>}\n </div>\n ) : (\n <>\n <ul className=\"sf-drawer__lines\">\n {cart.lines.map((line) => (\n <CartLineItem key={line.productId} line={line} />\n ))}\n </ul>\n <CartSummary {...summary} />\n {slots?.summaryNote && (\n <div className=\"sf-drawer__note\">{slots.summaryNote}</div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { useCart } from '../../cart/cart-provider';\n\nexport interface CartButtonProps {\n label?: string;\n className?: string;\n icon?: ReactNode;\n labelHidden?: boolean;\n hideZeroCount?: boolean;\n}\n\nexport function CartButton({\n label = 'Cart',\n className,\n icon,\n labelHidden = false,\n hideZeroCount = false,\n}: CartButtonProps) {\n const cart = useCart();\n const count = cart.hydrated ? cart.itemCount : 0;\n const showCount = !(hideZeroCount && count === 0);\n const ariaLabel = showCount\n ? labelHidden\n ? `${label}, ${cart.itemCount} items`\n : undefined\n : `${label}, empty`;\n return (\n <button\n type=\"button\"\n className={['sf-cart-button', className].filter(Boolean).join(' ')}\n aria-haspopup=\"dialog\"\n aria-label={ariaLabel}\n onClick={cart.open}\n >\n {icon}\n <span className={labelHidden ? 'sf-visually-hidden' : undefined}>\n {label}\n </span>\n {showCount && (\n <span\n className=\"sf-cart-button__count\"\n aria-label={`${cart.itemCount} items`}\n >\n {count}\n </span>\n )}\n </button>\n );\n}\n","import type { OrderStatus } from '@develemit/commerce-contracts';\n\nexport interface CheckoutSuccessLabels {\n heading: string;\n summaryHeading: string;\n reference: string;\n continue: string;\n subtotal: string;\n discount: string;\n shipping: string;\n total: string;\n shipTo: string;\n tracking: string;\n status: Record<OrderStatus, string>;\n}\n\nexport const defaultCheckoutSuccessLabels: CheckoutSuccessLabels = {\n heading: 'Order received',\n summaryHeading: 'Order summary',\n reference: 'Order {id}',\n continue: 'Continue shopping',\n subtotal: 'Subtotal',\n discount: 'Discount',\n shipping: 'Shipping',\n total: 'Total',\n shipTo: 'Ship to',\n tracking: 'Track',\n status: {\n pending: \"We're confirming your payment and will email you once it's done.\",\n paid: 'Payment received. Thank you!',\n shipped: 'Your order is on its way.',\n cancelled: 'This checkout was cancelled.',\n expired: 'This checkout expired before payment.',\n refunded: 'This order has been refunded.',\n needs_attention:\n \"We couldn't confirm one of your items. We'll be in touch about a refund.\",\n },\n};\n","import type { ReactNode } from 'react';\nimport type { OrderAddress, OrderSummary } from '@develemit/commerce-contracts';\nimport { formatPrice } from '../../lib/format-price';\nimport {\n defaultCheckoutSuccessLabels,\n type CheckoutSuccessLabels,\n} from './labels';\n\nexport interface CheckoutSuccessViewProps {\n order: OrderSummary & { shippingAddress?: OrderAddress | null };\n locale?: string;\n labels?: Partial<Omit<CheckoutSuccessLabels, 'status'>> & {\n status?: Partial<CheckoutSuccessLabels['status']>;\n };\n className?: string;\n slots?: {\n header?: ReactNode;\n beforeLines?: ReactNode;\n afterTotals?: ReactNode;\n actions?: ReactNode;\n };\n showReference?: boolean;\n showSummaryHeading?: boolean;\n continueHref?: string;\n renderLink?: (props: {\n href: string;\n children: ReactNode;\n className?: string;\n }) => ReactNode;\n}\n\nfunction addressLines(address: OrderAddress): string[] {\n const region = [address.city, address.state, address.postalCode]\n .filter(Boolean)\n .join(' ');\n return [\n address.name,\n address.line1,\n address.line2,\n region,\n address.country,\n ].filter((line): line is string => Boolean(line));\n}\n\nexport function CheckoutSuccessView({\n order,\n locale,\n labels,\n className,\n slots,\n showReference,\n showSummaryHeading,\n continueHref,\n renderLink,\n}: CheckoutSuccessViewProps) {\n const text = {\n ...defaultCheckoutSuccessLabels,\n ...labels,\n status: { ...defaultCheckoutSuccessLabels.status, ...labels?.status },\n };\n const money = (cents: number) => formatPrice(cents, order.currency, locale);\n const lines = order.shippingAddress\n ? addressLines(order.shippingAddress)\n : [];\n const { fulfillment } = order;\n const continueProps = {\n href: continueHref ?? '',\n className: 'sf-checkout-success__continue',\n children: text.continue,\n };\n const continueLink = continueHref ? (\n renderLink ? (\n renderLink(continueProps)\n ) : (\n <a {...continueProps} />\n )\n ) : null;\n const actions = slots?.actions ?? continueLink;\n return (\n <section\n className={['sf-checkout-success', className].filter(Boolean).join(' ')}\n data-status={order.status}\n >\n <h1 className=\"sf-checkout-success__heading\">{text.heading}</h1>\n {showReference && (\n <p className=\"sf-checkout-success__reference\">\n {text.reference.replace('{id}', order.id)}\n </p>\n )}\n {slots?.header}\n <p className=\"sf-checkout-success__status\">{text.status[order.status]}</p>\n {showSummaryHeading && (\n <h2 className=\"sf-checkout-success__summary-heading\">\n {text.summaryHeading}\n </h2>\n )}\n {slots?.beforeLines}\n <ul className=\"sf-checkout-success__lines\">\n {order.items.map((item) => (\n <li key={item.productId} className=\"sf-checkout-success__line\">\n <span>\n {item.name} × {item.quantity}\n </span>\n <span>{money(item.priceCents * item.quantity)}</span>\n </li>\n ))}\n </ul>\n <dl className=\"sf-checkout-success__totals\">\n <div className=\"sf-checkout-success__row\">\n <dt>{text.subtotal}</dt>\n <dd>{money(order.subtotalCents)}</dd>\n </div>\n {order.discountCents > 0 && (\n <div\n className=\"sf-checkout-success__row sf-checkout-success__row--discount\"\n data-discount\n >\n <dt>\n {text.discount}\n {order.discountCode && ` (${order.discountCode})`}\n </dt>\n <dd>−{money(order.discountCents)}</dd>\n </div>\n )}\n {order.shippingLabel && (\n <div className=\"sf-checkout-success__row\">\n <dt>\n {text.shipping} ({order.shippingLabel})\n </dt>\n <dd>{money(order.shippingCents)}</dd>\n </div>\n )}\n <div className=\"sf-checkout-success__row sf-checkout-success__row--total\">\n <dt>{text.total}</dt>\n <dd>{money(order.totalCents)}</dd>\n </div>\n </dl>\n {slots?.afterTotals}\n {lines.length > 0 && (\n <address className=\"sf-checkout-success__address\">\n <strong>{text.shipTo}</strong>\n {lines.map((line) => (\n <span key={line}>{line}</span>\n ))}\n </address>\n )}\n {fulfillment?.trackingNumber && (\n <p className=\"sf-checkout-success__tracking\">\n {fulfillment.carrier && `${fulfillment.carrier} · `}\n {fulfillment.trackingUrl ? (\n <a href={fulfillment.trackingUrl}>\n {text.tracking} {fulfillment.trackingNumber}\n </a>\n ) : (\n fulfillment.trackingNumber\n )}\n </p>\n )}\n {actions && <div className=\"sf-checkout-success__actions\">{actions}</div>}\n </section>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useCart } from './cart-provider';\n\nexport function ClearCartOnMount() {\n const { hydrated, clear } = useCart();\n const cleared = useRef(false);\n useEffect(() => {\n if (!hydrated || cleared.current) return;\n cleared.current = true;\n clear();\n }, [hydrated, clear]);\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["_Fragment","_Fragment","_Fragment","formatPrice","VAR_NAMES","themeToCssVars","vars","key","value","theme","defaults","priority","slug","StorefrontContext","createContext","StorefrontProvider","overrides","hrefs","useStorefront","useContext","PriceTag","classes","sold","className","priceCents","currency","locale","defaultAvailabilityLabels","AvailabilityBadge","text","labels","variant","label","availability","quantity","ProductCard","product","image","renderLink","Fragment","renderImage","ProductGrid","products","emptyState","index","priorityCount","CategoryFilter","ordered","sortCategories","categories","controlled","onSelect","item","active","selected","allLabel","c","formatLabel","ProductGallery","setActive","useState","images","event","i","ShopView","slots","activeCategory","formatCategoryLabel","sortForDisplay","nextHref","loadMoreLabel","ProductDetailView","backHref","backLabel","FeaturedProducts","heading","emptyCart","addLine","first","state","line","existing","l","cartReducer","action","subtotalCents","sum","itemCount","localStorageCartStorage","raw","parsed","result","cartLineSchema","CartContext","defaultStorage","CartProvider","dispatch","useReducer","hydrated","setHydrated","isOpen","setOpen","stateRef","useRef","useEffect","cancelled","storage","loaded","add","useCallback","useMemo","useCart","ctx","defaultDiscountLabels","discountReasonMessage","reason","defaultCheckoutLabels","assign","url","useCheckout","cart","busy","setBusy","message","setMessage","startCheckout","lines","options","onDiscountInvalid","remove","onRedirect","defaultAddToCartLabels","AddToCartButton","inCart","unavailable","atCap","disabled","handleClick","openOnAdd","defaultCartLineItemLabels","CartLineItem","editable","setQuantity","parseDurationMs","match","amount","useDrawerPresence","durationMs","setDurationMs","wasOpen","setWasOpen","lingering","setLingering","backdropRef","timer","DiscountField","open","setValue","submit","code","onApply","applied","onRemove","error","pending","useDiscount","items","signature","setApplied","setError","setPending","latest","request","previewedSignature","clear","run","current","id","onReject","apply","delay","discount","CartSummary","subtotal","taxNote","discountLabels","showTaxNote","checkoutDisabled","onCheckout","checkoutLabel","defaultCartDrawerLabels","FOCUSABLE","CartDrawer","panelRef","presence","trigger","handleKeyDown","last","summary","CartButton","count","showCount","hideZeroCount","ariaLabel","labelHidden","icon","defaultCheckoutSuccessLabels","addressLines","region","address","CheckoutSuccessView","money","cents","order","continueProps","continueHref","continueLink","actions","showReference","formatOrderRef","showSummaryHeading","fulfillment","ClearCartOnMount","cleared"],"sources":["../src/lib/theme.ts","../src/lib/provider.tsx","../src/components/price-tag/price-tag.tsx","../src/components/availability-badge/availability-badge.tsx","../src/components/product-card/product-card.tsx","../src/components/product-grid/product-grid.tsx","../src/components/category-filter/category-filter.tsx","../src/components/product-gallery/product-gallery.tsx","../src/views/shop-view.tsx","../src/views/product-detail-view.tsx","../src/views/featured-products.tsx","../src/cart/cart-reducer.ts","../src/cart/cart-storage.ts","../src/cart/cart-provider.tsx","../src/components/cart-summary/discount-types.ts","../src/cart/use-checkout.ts","../src/components/add-to-cart-button/add-to-cart-button.tsx","../src/components/cart-line-item/cart-line-item.tsx","../src/lib/parse-duration.ts","../src/components/cart-drawer/use-drawer-presence.ts","../src/components/cart-summary/discount-field.tsx","../src/components/cart-summary/use-discount.ts","../src/components/cart-summary/cart-summary.tsx","../src/components/cart-drawer/cart-drawer.tsx","../src/components/cart-button/cart-button.tsx","../src/components/checkout-success/labels.ts","../src/components/checkout-success/checkout-success-view.tsx","../src/cart/clear-cart-on-mount.tsx"],"sourcesContent":["export interface StorefrontTheme {\n colorBg?: string;\n colorSurface?: string;\n colorFg?: string;\n colorMuted?: string;\n colorAccent?: string;\n colorAccentFg?: string;\n colorBorder?: string;\n colorSold?: string;\n radius?: string;\n fontDisplay?: string;\n fontBody?: string;\n spaceUnit?: string;\n}\n\nconst VAR_NAMES: Record<keyof StorefrontTheme, string> = {\n colorBg: '--sf-color-bg',\n colorSurface: '--sf-color-surface',\n colorFg: '--sf-color-fg',\n colorMuted: '--sf-color-muted',\n colorAccent: '--sf-color-accent',\n colorAccentFg: '--sf-color-accent-fg',\n colorBorder: '--sf-color-border',\n colorSold: '--sf-color-sold',\n radius: '--sf-radius',\n fontDisplay: '--sf-font-display',\n fontBody: '--sf-font-body',\n spaceUnit: '--sf-space-unit',\n};\n\nexport function themeToCssVars(theme: StorefrontTheme): Record<string, string> {\n const vars: Record<string, string> = {};\n for (const key of Object.keys(VAR_NAMES) as (keyof StorefrontTheme)[]) {\n const value = theme[key];\n if (value !== undefined) vars[VAR_NAMES[key]] = value;\n }\n return vars;\n}\n","import { createContext, useContext, type ReactNode } from 'react';\n\nexport interface LinkProps {\n href: string;\n children: ReactNode;\n className?: string;\n}\n\nexport interface ImageProps {\n src: string;\n alt: string;\n sizes?: string;\n className?: string;\n priority?: boolean;\n}\n\nexport interface StorefrontConfig {\n renderLink: (props: LinkProps) => ReactNode;\n renderImage: (props: ImageProps) => ReactNode;\n locale: string;\n hrefs: {\n product: (slug: string) => string;\n category: (slug: string) => string;\n };\n}\n\nconst defaults: StorefrontConfig = {\n renderLink: ({ href, children, className }) => (\n <a href={href} className={className}>\n {children}\n </a>\n ),\n renderImage: ({ src, alt, sizes, className, priority }) => (\n <img\n src={src}\n alt={alt}\n sizes={sizes}\n className={className}\n loading={priority ? 'eager' : 'lazy'}\n decoding=\"async\"\n fetchPriority={priority ? 'high' : undefined}\n />\n ),\n locale: 'en-US',\n hrefs: {\n product: (slug) => `/shop/${slug}`,\n category: (slug) => `/shop?category=${slug}`,\n },\n};\n\nconst StorefrontContext = createContext<StorefrontConfig>(defaults);\n\nexport interface StorefrontProviderProps extends Partial<\n Omit<StorefrontConfig, 'hrefs'>\n> {\n hrefs?: Partial<StorefrontConfig['hrefs']>;\n children: ReactNode;\n}\n\nexport function StorefrontProvider({\n children,\n hrefs,\n ...overrides\n}: StorefrontProviderProps) {\n const value: StorefrontConfig = {\n ...defaults,\n ...overrides,\n hrefs: { ...defaults.hrefs, ...hrefs },\n };\n return (\n <StorefrontContext.Provider value={value}>\n {children}\n </StorefrontContext.Provider>\n );\n}\n\nexport function useStorefront(): StorefrontConfig {\n return useContext(StorefrontContext);\n}\n","import { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface PriceTagProps {\n priceCents: number;\n currency: string;\n sold?: boolean;\n className?: string;\n}\n\nexport function PriceTag({\n priceCents,\n currency,\n sold = false,\n className,\n}: PriceTagProps) {\n const { locale } = useStorefront();\n const classes = ['sf-price', sold && 'sf-price--sold', className]\n .filter(Boolean)\n .join(' ');\n return (\n <span className={classes}>{formatPrice(priceCents, currency, locale)}</span>\n );\n}\n","import type { ProductAvailability } from '@develemit/commerce-contracts';\n\nexport interface AvailabilityLabels {\n oneOfAKind: string;\n sold: string;\n reserved: string;\n available: string;\n}\n\nexport const defaultAvailabilityLabels: AvailabilityLabels = {\n oneOfAKind: 'One of a kind',\n sold: 'Sold',\n reserved: 'On hold',\n available: 'Available',\n};\n\nexport interface AvailabilityBadgeProps {\n availability: ProductAvailability;\n quantity: number;\n labels?: Partial<AvailabilityLabels>;\n className?: string;\n}\n\nexport function AvailabilityBadge({\n availability,\n quantity,\n labels,\n className,\n}: AvailabilityBadgeProps) {\n const text = { ...defaultAvailabilityLabels, ...labels };\n let variant: string;\n let label: string;\n if (availability === 'sold') {\n variant = 'sold';\n label = text.sold;\n } else if (availability === 'reserved') {\n variant = 'reserved';\n label = text.reserved;\n } else if (quantity === 1) {\n variant = 'unique';\n label = text.oneOfAKind;\n } else {\n variant = 'available';\n label = text.available;\n }\n const classes = ['sf-badge', `sf-badge--${variant}`, className]\n .filter(Boolean)\n .join(' ');\n return (\n <span role=\"status\" className={classes}>\n {label}\n </span>\n );\n}\n","import type { ProductCard as ProductCardData } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\nimport {\n AvailabilityBadge,\n type AvailabilityLabels,\n} from '../availability-badge/availability-badge';\nimport { PriceTag } from '../price-tag/price-tag';\n\nexport interface ProductCardProps {\n product: ProductCardData;\n labels?: Partial<AvailabilityLabels>;\n sizes?: string;\n priority?: boolean;\n}\n\nexport function ProductCard({\n product,\n labels,\n sizes,\n priority,\n}: ProductCardProps) {\n const { renderLink, renderImage, hrefs } = useStorefront();\n const sold = product.availability === 'sold';\n const image = product.images[0];\n const classes = ['sf-card', sold && 'sf-card--sold']\n .filter(Boolean)\n .join(' ');\n return (\n <article className={classes}>\n {renderLink({\n href: hrefs.product(product.slug),\n className: 'sf-card__link',\n children: (\n <>\n <div className=\"sf-card__media\">\n {image ? (\n renderImage({\n src: image.url,\n alt: image.alt,\n sizes,\n className: 'sf-card__image',\n priority,\n })\n ) : (\n <div className=\"sf-card__placeholder\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"sf-card__body\">\n <h3 className=\"sf-card__title\">{product.name}</h3>\n <PriceTag\n priceCents={product.priceCents}\n currency={product.currency}\n sold={sold}\n />\n <AvailabilityBadge\n availability={product.availability}\n quantity={product.quantity}\n labels={labels}\n />\n </div>\n </>\n ),\n })}\n </article>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { ProductCard as ProductCardData } from '@develemit/commerce-contracts';\nimport type { AvailabilityLabels } from '../availability-badge/availability-badge';\nimport { ProductCard } from '../product-card/product-card';\n\nexport interface ProductGridProps {\n products: ProductCardData[];\n emptyState?: ReactNode;\n labels?: Partial<AvailabilityLabels>;\n priorityCount?: number;\n}\n\nexport function ProductGrid({\n products,\n emptyState,\n labels,\n priorityCount = 0,\n}: ProductGridProps) {\n if (products.length === 0) {\n return (\n <div className=\"sf-grid-empty\">{emptyState ?? 'No products found.'}</div>\n );\n }\n return (\n <ul className=\"sf-grid\">\n {products.map((product, index) => (\n <li key={product.id} className=\"sf-grid__item\">\n <ProductCard\n product={product}\n labels={labels}\n priority={index < priorityCount}\n />\n </li>\n ))}\n </ul>\n );\n}\n","import type { Category } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CategoryFilterProps {\n categories: Category[];\n selected?: string;\n onSelect?: (slug: string | undefined) => void;\n allLabel?: string;\n label?: string;\n formatLabel?: (category: Category) => string;\n sortCategories?: (a: Category, b: Category) => number;\n}\n\nexport function CategoryFilter({\n categories,\n selected,\n onSelect,\n allLabel = 'All',\n label = 'Categories',\n formatLabel,\n sortCategories,\n}: CategoryFilterProps) {\n const { renderLink, hrefs } = useStorefront();\n const ordered = sortCategories\n ? [...categories].sort(sortCategories)\n : categories;\n const controlled = onSelect !== undefined;\n\n const item = (slug: string | undefined, text: string, href: string) => {\n const active = selected === slug;\n const className = `sf-filter__item${active ? ' sf-filter__item--active' : ''}`;\n return (\n <li key={slug ?? '__all'}>\n {controlled ? (\n <button\n type=\"button\"\n className={className}\n aria-pressed={active}\n onClick={() => onSelect(slug)}\n >\n {text}\n </button>\n ) : (\n renderLink({\n href,\n className,\n children: text,\n })\n )}\n </li>\n );\n };\n\n return (\n <nav aria-label={label} className=\"sf-filter\">\n <ul className=\"sf-filter__list\">\n {item(undefined, allLabel, hrefs.category(''))}\n {ordered.map((c) =>\n item(\n c.slug,\n formatLabel ? formatLabel(c) : c.label,\n hrefs.category(c.slug),\n ),\n )}\n </ul>\n </nav>\n );\n}\n","import { useState, type KeyboardEvent } from 'react';\nimport type { ProductImage } from '@develemit/commerce-contracts';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface ProductGalleryProps {\n images: ProductImage[];\n sizes?: string;\n}\n\nexport function ProductGallery({ images, sizes }: ProductGalleryProps) {\n const { renderImage } = useStorefront();\n const [active, setActive] = useState(0);\n\n if (images.length === 0) return null;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'ArrowRight') {\n setActive((i) => (i + 1) % images.length);\n } else if (event.key === 'ArrowLeft') {\n setActive((i) => (i - 1 + images.length) % images.length);\n } else if (event.key === 'Home') {\n setActive(0);\n } else if (event.key === 'End') {\n setActive(images.length - 1);\n } else {\n return;\n }\n event.preventDefault();\n };\n\n return (\n <div\n className=\"sf-gallery\"\n role=\"group\"\n aria-roledescription=\"carousel\"\n aria-label=\"Product images\"\n tabIndex={0}\n onKeyDown={onKeyDown}\n >\n <div className=\"sf-gallery__stage\">\n {images.map((image, index) => (\n <div\n key={image.url}\n className=\"sf-gallery__slide\"\n hidden={index !== active}\n >\n {renderImage({\n src: image.url,\n alt: image.alt,\n sizes,\n priority: index === active,\n className: 'sf-gallery__image',\n })}\n </div>\n ))}\n </div>\n {images.length > 1 && (\n <div className=\"sf-gallery__thumbs\">\n {images.map((image, index) => (\n <button\n key={image.url}\n type=\"button\"\n className={`sf-gallery__thumb${index === active ? ' sf-gallery__thumb--active' : ''}`}\n aria-label={`Show image ${index + 1} of ${images.length}`}\n aria-current={index === active}\n onClick={() => setActive(index)}\n >\n {renderImage({\n src: image.url,\n alt: '',\n className: 'sf-gallery__thumb-image',\n })}\n </button>\n ))}\n </div>\n )}\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { Category, ProductCard } from '@develemit/commerce-contracts';\nimport { CategoryFilter } from '../components/category-filter/category-filter';\nimport { ProductGrid } from '../components/product-grid/product-grid';\nimport type { AvailabilityLabels } from '../components/availability-badge/availability-badge';\nimport { useStorefront } from '../lib/provider';\nimport { sortForDisplay } from './sort-for-display';\n\nexport interface ShopViewSlots {\n header?: ReactNode;\n intro?: ReactNode;\n emptyState?: ReactNode;\n}\n\nexport interface ShopViewProps {\n products: ProductCard[];\n categories: Category[];\n activeCategory?: string;\n slots?: ShopViewSlots;\n labels?: Partial<AvailabilityLabels>;\n allLabel?: string;\n formatCategoryLabel?: (category: Category) => string;\n sortCategories?: (a: Category, b: Category) => number;\n nextHref?: string;\n loadMoreLabel?: string;\n}\n\nexport function ShopView({\n products,\n categories,\n activeCategory,\n slots = {},\n labels,\n allLabel,\n formatCategoryLabel,\n sortCategories,\n nextHref,\n loadMoreLabel = 'Load more',\n}: ShopViewProps) {\n const { renderLink } = useStorefront();\n return (\n <div className=\"sf-shop\">\n {slots.header}\n {slots.intro}\n <CategoryFilter\n categories={categories}\n selected={activeCategory}\n allLabel={allLabel}\n formatLabel={formatCategoryLabel}\n sortCategories={sortCategories}\n />\n <ProductGrid\n products={sortForDisplay(products)}\n emptyState={slots.emptyState}\n labels={labels}\n priorityCount={6}\n />\n {nextHref && (\n <div className=\"sf-shop__pager\">\n {renderLink({\n href: nextHref,\n className: 'sf-shop__more',\n children: loadMoreLabel,\n })}\n </div>\n )}\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport type { Product } from '@develemit/commerce-contracts';\nimport {\n AvailabilityBadge,\n type AvailabilityLabels,\n} from '../components/availability-badge/availability-badge';\nimport { PriceTag } from '../components/price-tag/price-tag';\nimport { useStorefront } from '../lib/provider';\nimport { ProductGallery } from '../components/product-gallery/product-gallery';\n\nexport interface ProductDetailViewSlots {\n actions?: ReactNode;\n afterDetails?: ReactNode;\n}\n\nexport interface ProductDetailViewProps {\n product: Product;\n slots?: ProductDetailViewSlots;\n labels?: Partial<AvailabilityLabels>;\n backHref?: string;\n backLabel?: string;\n}\n\nexport function ProductDetailView({\n product,\n slots = {},\n labels,\n backHref,\n backLabel = 'Back to the shop',\n}: ProductDetailViewProps) {\n const { renderLink } = useStorefront();\n const sold = product.availability === 'sold';\n return (\n <article className={sold ? 'sf-detail sf-detail--sold' : 'sf-detail'}>\n {backHref &&\n renderLink({\n href: backHref,\n className: 'sf-detail__back',\n children: backLabel,\n })}\n <ProductGallery images={product.images} />\n <div className=\"sf-detail__body\">\n <h1 className=\"sf-detail__title\">{product.name}</h1>\n <PriceTag priceCents={product.priceCents} currency={product.currency} />\n <AvailabilityBadge\n availability={product.availability}\n quantity={product.quantity}\n labels={labels}\n />\n <p className=\"sf-detail__description\">{product.description}</p>\n {(product.materials || product.dimensions) && (\n <dl className=\"sf-detail__specs\">\n {product.materials && (\n <>\n <dt>Materials</dt>\n <dd>{product.materials}</dd>\n </>\n )}\n {product.dimensions && (\n <>\n <dt>Dimensions</dt>\n <dd>{product.dimensions}</dd>\n </>\n )}\n </dl>\n )}\n {slots.actions && (\n <div className=\"sf-detail__actions\">{slots.actions}</div>\n )}\n {slots.afterDetails}\n </div>\n </article>\n );\n}\n","import type { ProductCard } from '@develemit/commerce-contracts';\nimport type { AvailabilityLabels } from '../components/availability-badge/availability-badge';\nimport { ProductGrid } from '../components/product-grid/product-grid';\n\nexport interface FeaturedProductsProps {\n products: ProductCard[];\n heading?: string;\n labels?: Partial<AvailabilityLabels>;\n}\n\nexport function FeaturedProducts({\n products,\n heading = 'Featured',\n labels,\n}: FeaturedProductsProps) {\n if (products.length === 0) return null;\n return (\n <section className=\"sf-featured\">\n <h2 className=\"sf-featured__heading\">{heading}</h2>\n <ProductGrid products={products} labels={labels} priorityCount={6} />\n </section>\n );\n}\n","import type { CartLine } from '@develemit/commerce-contracts';\n\nexport interface CartState {\n lines: CartLine[];\n}\n\nexport type CartAction =\n | { type: 'add'; line: CartLine }\n | { type: 'remove'; productId: string }\n | { type: 'setQuantity'; productId: string; quantity: number }\n | { type: 'clear' }\n | { type: 'hydrate'; state: CartState };\n\nexport interface AddResult {\n state: CartState;\n added: boolean;\n capped: boolean;\n reason?: 'currency-mismatch';\n}\n\nexport const emptyCart: CartState = { lines: [] };\n\nexport function addLine(state: CartState, line: CartLine): AddResult {\n const first = state.lines[0];\n if (first && first.currency !== line.currency) {\n return { state, added: false, capped: false, reason: 'currency-mismatch' };\n }\n const existing = state.lines.find((l) => l.productId === line.productId);\n if (!existing) {\n const quantity = Math.min(line.quantity, line.maxQuantity);\n return {\n state: { lines: [...state.lines, { ...line, quantity }] },\n added: true,\n capped: quantity < line.quantity,\n };\n }\n if (existing.quantity >= existing.maxQuantity) {\n return { state, added: false, capped: true };\n }\n const quantity = Math.min(\n existing.quantity + line.quantity,\n existing.maxQuantity,\n );\n return {\n state: {\n lines: state.lines.map((l) =>\n l.productId === line.productId ? { ...l, quantity } : l,\n ),\n },\n added: true,\n capped: existing.quantity + line.quantity > existing.maxQuantity,\n };\n}\n\nexport function cartReducer(state: CartState, action: CartAction): CartState {\n switch (action.type) {\n case 'add':\n return addLine(state, action.line).state;\n case 'remove':\n return {\n lines: state.lines.filter((l) => l.productId !== action.productId),\n };\n case 'setQuantity': {\n if (action.quantity <= 0) {\n return cartReducer(state, {\n type: 'remove',\n productId: action.productId,\n });\n }\n return {\n lines: state.lines.map((l) =>\n l.productId === action.productId\n ? { ...l, quantity: Math.min(action.quantity, l.maxQuantity) }\n : l,\n ),\n };\n }\n case 'clear':\n return emptyCart;\n case 'hydrate':\n return action.state;\n }\n}\n\nexport function subtotalCents(state: CartState): number {\n return state.lines.reduce((sum, l) => sum + l.priceCents * l.quantity, 0);\n}\n\nexport function itemCount(state: CartState): number {\n return state.lines.reduce((sum, l) => sum + l.quantity, 0);\n}\n","import { cartLineSchema } from '@develemit/commerce-contracts';\nimport { emptyCart, type CartState } from './cart-reducer';\n\nexport interface CartStorage {\n load: () => CartState | Promise<CartState>;\n save: (state: CartState) => void | Promise<void>;\n}\n\nexport function localStorageCartStorage(key = 'sf-cart'): CartStorage {\n return {\n load() {\n if (typeof window === 'undefined') return emptyCart;\n try {\n const raw = window.localStorage.getItem(key);\n if (!raw) return emptyCart;\n const parsed: unknown = JSON.parse(raw);\n const lines = Array.isArray((parsed as CartState)?.lines)\n ? (parsed as CartState).lines\n : [];\n const valid = lines.flatMap((line) => {\n const result = cartLineSchema.safeParse(line);\n return result.success ? [result.data] : [];\n });\n return { lines: valid };\n } catch {\n return emptyCart;\n }\n },\n save(state) {\n if (typeof window === 'undefined') return;\n try {\n window.localStorage.setItem(key, JSON.stringify(state));\n } catch {\n // storage full or blocked: the cart simply stays in memory\n }\n },\n };\n}\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\nimport type { CartLine } from '@develemit/commerce-contracts';\nimport {\n addLine,\n cartReducer,\n emptyCart,\n itemCount,\n subtotalCents,\n type AddResult,\n type CartState,\n} from './cart-reducer';\nimport { localStorageCartStorage, type CartStorage } from './cart-storage';\n\nexport interface CartContextValue {\n lines: CartLine[];\n itemCount: number;\n subtotalCents: number;\n currency: string | null;\n hydrated: boolean;\n isOpen: boolean;\n open: () => void;\n close: () => void;\n add: (line: CartLine) => AddResult;\n remove: (productId: string) => void;\n setQuantity: (productId: string, quantity: number) => void;\n clear: () => void;\n}\n\nconst CartContext = createContext<CartContextValue | null>(null);\n\nexport interface CartProviderProps {\n storage?: CartStorage;\n children: ReactNode;\n}\n\nconst defaultStorage = localStorageCartStorage();\n\nexport function CartProvider({\n storage = defaultStorage,\n children,\n}: CartProviderProps) {\n const [state, dispatch] = useReducer(cartReducer, emptyCart);\n const [hydrated, setHydrated] = useState(false);\n const [isOpen, setOpen] = useState(false);\n const stateRef = useRef<CartState>(state);\n useEffect(() => {\n stateRef.current = state;\n }, [state]);\n\n useEffect(() => {\n let cancelled = false;\n Promise.resolve(storage.load()).then((loaded) => {\n if (cancelled) return;\n dispatch({ type: 'hydrate', state: loaded });\n setHydrated(true);\n });\n return () => {\n cancelled = true;\n };\n }, [storage]);\n\n useEffect(() => {\n if (hydrated) void storage.save(state);\n }, [hydrated, state, storage]);\n\n const add = useCallback((line: CartLine) => {\n const result = addLine(stateRef.current, line);\n if (result.added) {\n stateRef.current = result.state;\n dispatch({ type: 'add', line });\n }\n return result;\n }, []);\n\n const value = useMemo<CartContextValue>(\n () => ({\n lines: state.lines,\n itemCount: itemCount(state),\n subtotalCents: subtotalCents(state),\n currency: state.lines[0]?.currency ?? null,\n hydrated,\n isOpen,\n open: () => setOpen(true),\n close: () => setOpen(false),\n add,\n remove: (productId) => dispatch({ type: 'remove', productId }),\n setQuantity: (productId, quantity) =>\n dispatch({ type: 'setQuantity', productId, quantity }),\n clear: () => dispatch({ type: 'clear' }),\n }),\n [state, hydrated, isOpen, add],\n );\n\n return <CartContext.Provider value={value}>{children}</CartContext.Provider>;\n}\n\nexport function useCart(): CartContextValue {\n const ctx = useContext(CartContext);\n if (!ctx) throw new Error('useCart must be used inside <CartProvider>');\n return ctx;\n}\n","import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\n\nexport type DiscountPreviewResult =\n | { ok: true; code: string; discountCents: number }\n | { ok: false; reason: DiscountRejectionReason };\n\nexport interface CartSummaryDiscount {\n onPreview: (\n code: string,\n items: { productId: string; quantity: number }[],\n ) => Promise<DiscountPreviewResult>;\n onApply?: (code: string | null) => void;\n debounceMs?: number;\n}\n\nexport interface DiscountLabels {\n toggle: string;\n field: string;\n apply: string;\n remove: string;\n line: string;\n reasons: Record<DiscountRejectionReason, string>;\n unavailable: string;\n}\n\nexport const defaultDiscountLabels: DiscountLabels = {\n toggle: 'Have a code?',\n field: 'Discount code',\n apply: 'Apply',\n remove: 'Remove',\n line: 'Discount',\n unavailable: 'We could not check that code. Please try again.',\n reasons: {\n not_found: 'That code is not valid.',\n inactive: 'That code is not valid.',\n not_started: 'That code is not active yet.',\n expired: 'That code has expired.',\n min_subtotal: 'Your cart is below the minimum for that code.',\n exhausted: 'That code is no longer available.',\n currency: 'That code does not apply to this cart.',\n },\n};\n\nexport function discountReasonMessage(\n reason: DiscountRejectionReason,\n overrides?: Partial<DiscountLabels['reasons']>,\n): string {\n return overrides?.[reason] ?? defaultDiscountLabels.reasons[reason];\n}\n","import { useCallback, useState } from 'react';\nimport type { DiscountRejectionReason } from '@develemit/commerce-contracts';\nimport type { CheckoutResult } from '../client/checkout-result';\nimport { discountReasonMessage } from '../components/cart-summary/discount-types';\nimport { useCart } from './cart-provider';\n\nexport interface CheckoutLabels {\n outOfStock: string;\n unavailable: string;\n discountReasons?: Partial<Record<DiscountRejectionReason, string>>;\n}\n\nexport const defaultCheckoutLabels: CheckoutLabels = {\n outOfStock:\n 'Some items just sold or are on hold, so we removed them from your cart.',\n unavailable: 'Checkout is unavailable right now. Please try again.',\n};\n\nexport interface StartCheckoutOptions {\n discountCode?: string;\n}\n\nexport interface UseCheckoutOptions {\n startCheckout: (\n items: { productId: string; quantity: number }[],\n options: StartCheckoutOptions,\n ) => Promise<CheckoutResult>;\n onRedirect?: (url: string) => void;\n onDiscountInvalid?: (reason: DiscountRejectionReason) => void;\n labels?: Partial<CheckoutLabels>;\n}\n\nexport interface UseCheckoutValue {\n checkout: (options?: StartCheckoutOptions) => Promise<void>;\n busy: boolean;\n message: string | null;\n clearMessage: () => void;\n}\n\nconst assign = (url: string) => window.location.assign(url);\n\nexport function useCheckout({\n startCheckout,\n onRedirect = assign,\n onDiscountInvalid,\n labels,\n}: UseCheckoutOptions): UseCheckoutValue {\n const cart = useCart();\n const [busy, setBusy] = useState(false);\n const [message, setMessage] = useState<string | null>(null);\n const { lines, remove } = cart;\n const text = { ...defaultCheckoutLabels, ...labels };\n\n const checkout = useCallback(\n async (options: StartCheckoutOptions = {}) => {\n setBusy(true);\n setMessage(null);\n try {\n const result = await startCheckout(\n lines.map((l) => ({ productId: l.productId, quantity: l.quantity })),\n options,\n );\n if ('url' in result) {\n onRedirect(result.url);\n return;\n }\n if (result.error === 'discount_invalid') {\n setMessage(\n discountReasonMessage(result.reason, text.discountReasons),\n );\n onDiscountInvalid?.(result.reason);\n } else {\n result.productIds.forEach(remove);\n setMessage(text.outOfStock);\n }\n } catch {\n setMessage(text.unavailable);\n }\n setBusy(false);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [startCheckout, onRedirect, onDiscountInvalid, lines, remove, labels],\n );\n\n const clearMessage = useCallback(() => setMessage(null), []);\n\n return { checkout, busy, message, clearMessage };\n}\n","import type { Product } from '@develemit/commerce-contracts';\nimport { useCart } from '../../cart/cart-provider';\n\nexport interface AddToCartLabels {\n add: string;\n inCart: string;\n unavailable: string;\n sold: string;\n}\n\nexport const defaultAddToCartLabels: AddToCartLabels = {\n add: 'Add to cart',\n inCart: 'In your cart',\n unavailable: 'Unavailable',\n sold: 'Sold',\n};\n\nexport interface AddToCartButtonProps {\n product: Product;\n labels?: Partial<AddToCartLabels>;\n openOnAdd?: boolean;\n className?: string;\n}\n\nexport function AddToCartButton({\n product,\n labels,\n openOnAdd = true,\n className,\n}: AddToCartButtonProps) {\n const cart = useCart();\n const text = { ...defaultAddToCartLabels, ...labels };\n const inCart =\n cart.lines.find((l) => l.productId === product.id)?.quantity ?? 0;\n const sold = product.availability === 'sold';\n const unavailable = product.availability !== 'available';\n const atCap = inCart >= product.quantity;\n const disabled = !cart.hydrated || unavailable || atCap;\n const label = unavailable ? text.unavailable : atCap ? text.inCart : text.add;\n\n function handleClick() {\n const result = cart.add({\n productId: product.id,\n slug: product.slug,\n name: product.name,\n priceCents: product.priceCents,\n currency: product.currency,\n imageUrl: product.images[0]?.url,\n quantity: 1,\n maxQuantity: product.quantity,\n });\n if (result.added && openOnAdd) cart.open();\n }\n\n if (sold) {\n return (\n <span className={['sf-sold-note', className].filter(Boolean).join(' ')}>\n {text.sold}\n </span>\n );\n }\n\n return (\n <button\n type=\"button\"\n className={['sf-button', className].filter(Boolean).join(' ')}\n disabled={disabled}\n onClick={handleClick}\n >\n {label}\n </button>\n );\n}\n","import type { CartLine } from '@develemit/commerce-contracts';\nimport { useCart } from '../../cart/cart-provider';\nimport { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\n\nexport interface CartLineItemLabels {\n remove: string;\n quantity: string;\n}\n\nexport const defaultCartLineItemLabels: CartLineItemLabels = {\n remove: 'Remove',\n quantity: 'Quantity',\n};\n\nexport interface CartLineItemProps {\n line: CartLine;\n labels?: Partial<CartLineItemLabels>;\n}\n\nexport function CartLineItem({ line, labels }: CartLineItemProps) {\n const { remove, setQuantity } = useCart();\n const { locale, renderLink, renderImage, hrefs } = useStorefront();\n const text = { ...defaultCartLineItemLabels, ...labels };\n const editable = line.maxQuantity > 1;\n return (\n <li className=\"sf-cart-line\">\n {line.imageUrl &&\n renderImage({\n src: line.imageUrl,\n alt: '',\n sizes: '80px',\n className: 'sf-cart-line__image',\n })}\n <div className=\"sf-cart-line__body\">\n {renderLink({\n href: hrefs.product(line.slug),\n className: 'sf-cart-line__name',\n children: line.name,\n })}\n <span className=\"sf-cart-line__price\">\n {formatPrice(line.priceCents * line.quantity, line.currency, locale)}\n </span>\n {editable && (\n <label className=\"sf-cart-line__qty\">\n {text.quantity}\n <input\n type=\"number\"\n min={1}\n max={line.maxQuantity}\n value={line.quantity}\n onChange={(e) =>\n setQuantity(line.productId, Number(e.target.value))\n }\n />\n </label>\n )}\n <button\n type=\"button\"\n className=\"sf-link-button sf-cart-line__remove\"\n onClick={() => remove(line.productId)}\n aria-label={`${text.remove} ${line.name}`}\n >\n {text.remove}\n </button>\n </div>\n </li>\n );\n}\n","export function parseDurationMs(value: string): number {\n const match = /^\\s*(-?\\d*\\.?\\d+)\\s*(ms|s)\\s*$/.exec(value);\n if (!match) return 0;\n const amount = Number(match[1]);\n return Math.max(0, match[2] === 's' ? amount * 1000 : amount);\n}\n","import { useEffect, useState, type RefObject } from 'react';\nimport { parseDurationMs } from '../../lib/parse-duration';\n\nexport function useDrawerPresence(\n isOpen: boolean,\n backdropRef: RefObject<HTMLElement | null>,\n) {\n const [durationMs, setDurationMs] = useState(0);\n const [wasOpen, setWasOpen] = useState(isOpen);\n const [lingering, setLingering] = useState(false);\n\n if (wasOpen !== isOpen) {\n setWasOpen(isOpen);\n setLingering(!isOpen && durationMs > 0);\n }\n\n useEffect(() => {\n if (!isOpen || !backdropRef.current) return;\n setDurationMs(\n parseDurationMs(\n getComputedStyle(backdropRef.current).getPropertyValue(\n '--sf-motion-duration',\n ),\n ),\n );\n }, [isOpen, backdropRef]);\n\n useEffect(() => {\n if (!lingering) return;\n const timer = setTimeout(() => setLingering(false), durationMs);\n return () => clearTimeout(timer);\n }, [lingering, durationMs]);\n\n return { mounted: isOpen || lingering, state: isOpen ? 'open' : 'closed' };\n}\n","import { useState, type FormEvent } from 'react';\nimport { formatPrice } from '../../lib/format-price';\nimport type { DiscountLabels } from './discount-types';\nimport type { AppliedDiscount, DiscountError } from './use-discount';\n\nexport interface DiscountFieldProps {\n labels: DiscountLabels;\n applied: AppliedDiscount | null;\n error: DiscountError | null;\n pending: boolean;\n currency: string;\n locale: string;\n onApply: (code: string) => void;\n onRemove: () => void;\n}\n\nexport function DiscountField({\n labels,\n applied,\n error,\n pending,\n currency,\n locale,\n onApply,\n onRemove,\n}: DiscountFieldProps) {\n const [open, setOpen] = useState(false);\n const [value, setValue] = useState('');\n\n function submit(event: FormEvent) {\n event.preventDefault();\n const code = value.trim();\n if (code) onApply(code);\n }\n\n if (applied) {\n return (\n <p className=\"sf-cart-summary__row sf-cart-summary__discount\">\n <span>\n {labels.line} ({applied.code}){' '}\n <button type=\"button\" className=\"sf-link-button\" onClick={onRemove}>\n {labels.remove}\n </button>\n </span>\n <span className=\"sf-price\">\n −{formatPrice(applied.discountCents, currency, locale)}\n </span>\n </p>\n );\n }\n\n const message =\n error === 'unavailable'\n ? labels.unavailable\n : error\n ? labels.reasons[error]\n : null;\n return (\n <div className=\"sf-cart-summary__code\">\n {open ? (\n <form onSubmit={submit} className=\"sf-cart-summary__code-form\">\n <input\n aria-label={labels.field}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n autoCapitalize=\"characters\"\n autoComplete=\"off\"\n />\n <button type=\"submit\" className=\"sf-button\" disabled={pending}>\n {labels.apply}\n </button>\n </form>\n ) : (\n <button\n type=\"button\"\n className=\"sf-link-button\"\n aria-expanded={false}\n onClick={() => setOpen(true)}\n >\n {labels.toggle}\n </button>\n )}\n {message && (\n <p role=\"alert\" className=\"sf-cart-summary__code-error\">\n {message}\n </p>\n )}\n </div>\n );\n}\n","import type { DiscountRejectionReason } from '@develemit/commerce-contracts';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useCart } from '../../cart/cart-provider';\nimport type { CartSummaryDiscount } from './discount-types';\n\nexport interface AppliedDiscount {\n code: string;\n discountCents: number;\n}\n\nexport type DiscountError = DiscountRejectionReason | 'unavailable';\n\nexport function useDiscount(discount: CartSummaryDiscount | undefined) {\n const { lines } = useCart();\n const items = lines.map((l) => ({\n productId: l.productId,\n quantity: l.quantity,\n }));\n const signature = items.map((i) => `${i.productId}:${i.quantity}`).join(',');\n const [applied, setApplied] = useState<AppliedDiscount | null>(null);\n const [error, setError] = useState<DiscountError | null>(null);\n const [pending, setPending] = useState(false);\n const latest = useRef({ discount, items });\n useEffect(() => {\n latest.current = { discount, items };\n });\n const request = useRef(0);\n const previewedSignature = useRef(signature);\n\n const clear = useCallback(() => {\n request.current += 1;\n setApplied(null);\n latest.current.discount?.onApply?.(null);\n }, []);\n\n const run = useCallback(\n async (code: string, onReject: (reason: DiscountError) => void) => {\n const current = latest.current;\n if (!current.discount) return;\n const id = ++request.current;\n previewedSignature.current = current.items\n .map((i) => `${i.productId}:${i.quantity}`)\n .join(',');\n setPending(true);\n let result;\n try {\n result = await current.discount.onPreview(code, current.items);\n } catch {\n result = { ok: false as const, reason: 'unavailable' as const };\n }\n if (id !== request.current) return;\n setPending(false);\n if (result.ok) {\n setError(null);\n setApplied({ code: result.code, discountCents: result.discountCents });\n current.discount.onApply?.(result.code);\n } else {\n onReject(result.reason);\n }\n },\n [],\n );\n\n const apply = useCallback(\n (code: string) => run(code, (reason) => setError(reason)),\n [run],\n );\n\n const remove = useCallback(() => {\n setError(null);\n setPending(false);\n clear();\n }, [clear]);\n\n const code = applied?.code;\n const delay = discount?.debounceMs ?? 300;\n useEffect(() => {\n if (!code || signature === previewedSignature.current) return;\n const timer = setTimeout(() => {\n if (latest.current.items.length === 0) {\n clear();\n return;\n }\n void run(code, (reason) => {\n setError(reason);\n clear();\n });\n }, delay);\n return () => clearTimeout(timer);\n }, [code, signature, delay, run, clear]);\n\n return { applied, error, pending, apply, remove };\n}\n","import { useCart } from '../../cart/cart-provider';\nimport { formatPrice } from '../../lib/format-price';\nimport { useStorefront } from '../../lib/provider';\nimport { DiscountField } from './discount-field';\nimport {\n defaultDiscountLabels,\n type CartSummaryDiscount,\n type DiscountLabels,\n} from './discount-types';\nimport { useDiscount } from './use-discount';\n\nexport type {\n CartSummaryDiscount,\n DiscountPreviewResult,\n} from './discount-types';\nexport { discountReasonMessage } from './discount-types';\n\nexport interface CartSummaryLabels {\n subtotal: string;\n taxNote: string;\n discount: Partial<Omit<DiscountLabels, 'reasons'>> & {\n reasons?: Partial<DiscountLabels['reasons']>;\n };\n}\n\nexport interface CartSummaryProps {\n onCheckout?: () => void;\n checkoutLabel?: string;\n checkoutDisabled?: boolean;\n labels?: Partial<CartSummaryLabels>;\n discount?: CartSummaryDiscount;\n showTaxNote?: boolean;\n}\n\nexport function CartSummary({\n onCheckout,\n checkoutLabel = 'Checkout',\n checkoutDisabled = false,\n labels,\n discount,\n showTaxNote = false,\n}: CartSummaryProps) {\n const cart = useCart();\n const { locale } = useStorefront();\n const subtotal = labels?.subtotal ?? 'Subtotal';\n const taxNote = labels?.taxNote ?? 'Tax calculated at checkout';\n const state = useDiscount(discount);\n const discountLabels: DiscountLabels = {\n ...defaultDiscountLabels,\n ...labels?.discount,\n reasons: {\n ...defaultDiscountLabels.reasons,\n ...labels?.discount?.reasons,\n },\n };\n return (\n <div className=\"sf-cart-summary\">\n <p className=\"sf-cart-summary__row\">\n <span>{subtotal}</span>\n <span className=\"sf-price\">\n {formatPrice(cart.subtotalCents, cart.currency ?? 'USD', locale)}\n </span>\n </p>\n {showTaxNote && cart.lines.length > 0 && (\n <p className=\"sf-cart-summary__tax-note\">{taxNote}</p>\n )}\n {discount && cart.lines.length > 0 && (\n <DiscountField\n labels={discountLabels}\n currency={cart.currency ?? 'USD'}\n locale={locale}\n onApply={(code) => void state.apply(code)}\n onRemove={state.remove}\n applied={state.applied}\n error={state.error}\n pending={state.pending}\n />\n )}\n <button\n type=\"button\"\n className=\"sf-button\"\n disabled={checkoutDisabled || !onCheckout || cart.lines.length === 0}\n onClick={onCheckout}\n >\n {checkoutLabel}\n </button>\n </div>\n );\n}\n","import { useEffect, useRef, type KeyboardEvent, type ReactNode } from 'react';\nimport { useCart } from '../../cart/cart-provider';\nimport { CartLineItem } from '../cart-line-item/cart-line-item';\nimport { useDrawerPresence } from './use-drawer-presence';\nimport {\n CartSummary,\n type CartSummaryProps,\n} from '../cart-summary/cart-summary';\n\nexport interface CartDrawerLabels {\n title: string;\n close: string;\n empty: string;\n}\n\nexport const defaultCartDrawerLabels: CartDrawerLabels = {\n title: 'Your cart',\n close: 'Close',\n empty: 'Your cart is empty.',\n};\n\nexport interface CartDrawerProps {\n labels?: Partial<CartDrawerLabels>;\n summary?: CartSummaryProps;\n slots?: CartDrawerSlots;\n}\n\nexport interface CartDrawerSlots {\n empty?: ReactNode;\n summaryNote?: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport function CartDrawer({ labels, summary, slots }: CartDrawerProps) {\n const cart = useCart();\n const text = { ...defaultCartDrawerLabels, ...labels };\n const panelRef = useRef<HTMLDivElement>(null);\n const backdropRef = useRef<HTMLDivElement>(null);\n const { isOpen } = cart;\n const presence = useDrawerPresence(isOpen, backdropRef);\n\n useEffect(() => {\n if (!isOpen) return;\n const trigger = document.activeElement as HTMLElement | null;\n panelRef.current?.querySelector<HTMLElement>(FOCUSABLE)?.focus();\n return () => trigger?.focus();\n }, [isOpen]);\n\n if (!presence.mounted) return null;\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n if (event.key === 'Escape') {\n event.stopPropagation();\n cart.close();\n return;\n }\n if (event.key !== 'Tab') return;\n const items = Array.from(\n panelRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [],\n );\n if (items.length === 0) return;\n const first = items[0];\n const last = items[items.length - 1];\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n return (\n <div\n ref={backdropRef}\n className=\"sf-drawer-backdrop\"\n data-state={presence.state}\n onClick={cart.close}\n >\n <div\n ref={panelRef}\n className=\"sf-drawer\"\n data-state={presence.state}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sf-drawer-title\"\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"sf-drawer__header\">\n <h2 id=\"sf-drawer-title\" className=\"sf-drawer__title\">\n {text.title}\n </h2>\n <button\n type=\"button\"\n className=\"sf-drawer__close\"\n aria-label={text.close}\n onClick={cart.close}\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n {cart.lines.length === 0 ? (\n <div className=\"sf-drawer__empty\">\n {slots?.empty ?? <p>{text.empty}</p>}\n </div>\n ) : (\n <>\n <ul className=\"sf-drawer__lines\">\n {cart.lines.map((line) => (\n <CartLineItem key={line.productId} line={line} />\n ))}\n </ul>\n <CartSummary {...summary} />\n {slots?.summaryNote && (\n <div className=\"sf-drawer__note\">{slots.summaryNote}</div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n","import type { ReactNode } from 'react';\nimport { useCart } from '../../cart/cart-provider';\n\nexport interface CartButtonProps {\n label?: string;\n className?: string;\n icon?: ReactNode;\n labelHidden?: boolean;\n hideZeroCount?: boolean;\n}\n\nexport function CartButton({\n label = 'Cart',\n className,\n icon,\n labelHidden = false,\n hideZeroCount = false,\n}: CartButtonProps) {\n const cart = useCart();\n const count = cart.hydrated ? cart.itemCount : 0;\n const showCount = !(hideZeroCount && count === 0);\n const ariaLabel = showCount\n ? labelHidden\n ? `${label}, ${cart.itemCount} items`\n : undefined\n : `${label}, empty`;\n return (\n <button\n type=\"button\"\n className={['sf-cart-button', className].filter(Boolean).join(' ')}\n aria-haspopup=\"dialog\"\n aria-label={ariaLabel}\n onClick={cart.open}\n >\n {icon}\n <span className={labelHidden ? 'sf-visually-hidden' : undefined}>\n {label}\n </span>\n {showCount && (\n <span\n className=\"sf-cart-button__count\"\n aria-label={`${cart.itemCount} items`}\n >\n {count}\n </span>\n )}\n </button>\n );\n}\n","import type { OrderStatus } from '@develemit/commerce-contracts';\n\nexport interface CheckoutSuccessLabels {\n heading: string;\n summaryHeading: string;\n reference: string;\n continue: string;\n subtotal: string;\n discount: string;\n shipping: string;\n tax: string;\n total: string;\n shipTo: string;\n tracking: string;\n status: Record<OrderStatus, string>;\n}\n\nexport const defaultCheckoutSuccessLabels: CheckoutSuccessLabels = {\n heading: 'Order received',\n summaryHeading: 'Order summary',\n reference: 'Order {id}',\n continue: 'Continue shopping',\n subtotal: 'Subtotal',\n discount: 'Discount',\n shipping: 'Shipping',\n tax: 'Tax',\n total: 'Total',\n shipTo: 'Ship to',\n tracking: 'Track',\n status: {\n pending: \"We're confirming your payment and will email you once it's done.\",\n paid: 'Payment received. Thank you!',\n shipped: 'Your order is on its way.',\n cancelled: 'This checkout was cancelled.',\n expired: 'This checkout expired before payment.',\n refunded: 'This order has been refunded.',\n needs_attention:\n \"We couldn't confirm one of your items. We'll be in touch about a refund.\",\n },\n};\n","import type { ReactNode } from 'react';\nimport {\n formatOrderRef,\n type OrderAddress,\n type OrderSummary,\n} from '@develemit/commerce-contracts';\nimport { formatPrice } from '../../lib/format-price';\nimport {\n defaultCheckoutSuccessLabels,\n type CheckoutSuccessLabels,\n} from './labels';\n\nexport interface CheckoutSuccessViewProps {\n order: OrderSummary & { shippingAddress?: OrderAddress | null };\n locale?: string;\n labels?: Partial<Omit<CheckoutSuccessLabels, 'status'>> & {\n status?: Partial<CheckoutSuccessLabels['status']>;\n };\n className?: string;\n slots?: {\n header?: ReactNode;\n beforeLines?: ReactNode;\n afterTotals?: ReactNode;\n actions?: ReactNode;\n };\n showReference?: boolean;\n showSummaryHeading?: boolean;\n continueHref?: string;\n renderLink?: (props: {\n href: string;\n children: ReactNode;\n className?: string;\n }) => ReactNode;\n}\n\nfunction addressLines(address: OrderAddress): string[] {\n const region = [address.city, address.state, address.postalCode]\n .filter(Boolean)\n .join(' ');\n return [\n address.name,\n address.line1,\n address.line2,\n region,\n address.country,\n ].filter((line): line is string => Boolean(line));\n}\n\nexport function CheckoutSuccessView({\n order,\n locale,\n labels,\n className,\n slots,\n showReference,\n showSummaryHeading,\n continueHref,\n renderLink,\n}: CheckoutSuccessViewProps) {\n const text = {\n ...defaultCheckoutSuccessLabels,\n ...labels,\n status: { ...defaultCheckoutSuccessLabels.status, ...labels?.status },\n };\n const money = (cents: number) => formatPrice(cents, order.currency, locale);\n const lines = order.shippingAddress\n ? addressLines(order.shippingAddress)\n : [];\n const { fulfillment } = order;\n const continueProps = {\n href: continueHref ?? '',\n className: 'sf-checkout-success__continue',\n children: text.continue,\n };\n const continueLink = continueHref ? (\n renderLink ? (\n renderLink(continueProps)\n ) : (\n <a {...continueProps} />\n )\n ) : null;\n const actions = slots?.actions ?? continueLink;\n return (\n <section\n className={['sf-checkout-success', className].filter(Boolean).join(' ')}\n data-status={order.status}\n >\n <h1 className=\"sf-checkout-success__heading\">{text.heading}</h1>\n {showReference && (\n <p className=\"sf-checkout-success__reference\">\n {text.reference.replace('{id}', formatOrderRef(order.id))}\n </p>\n )}\n {slots?.header}\n <p className=\"sf-checkout-success__status\">{text.status[order.status]}</p>\n {showSummaryHeading && (\n <h2 className=\"sf-checkout-success__summary-heading\">\n {text.summaryHeading}\n </h2>\n )}\n {slots?.beforeLines}\n <ul className=\"sf-checkout-success__lines\">\n {order.items.map((item) => (\n <li key={item.productId} className=\"sf-checkout-success__line\">\n <span>\n {item.name} × {item.quantity}\n </span>\n <span>{money(item.priceCents * item.quantity)}</span>\n </li>\n ))}\n </ul>\n <dl className=\"sf-checkout-success__totals\">\n <div className=\"sf-checkout-success__row\">\n <dt>{text.subtotal}</dt>\n <dd>{money(order.subtotalCents)}</dd>\n </div>\n {order.discountCents > 0 && (\n <div\n className=\"sf-checkout-success__row sf-checkout-success__row--discount\"\n data-discount\n >\n <dt>\n {text.discount}\n {order.discountCode && ` (${order.discountCode})`}\n </dt>\n <dd>−{money(order.discountCents)}</dd>\n </div>\n )}\n {order.shippingLabel && (\n <div className=\"sf-checkout-success__row\">\n <dt>\n {text.shipping} ({order.shippingLabel})\n </dt>\n <dd>{money(order.shippingCents)}</dd>\n </div>\n )}\n {order.taxCents > 0 && (\n <div className=\"sf-checkout-success__row\" data-tax>\n <dt>{text.tax}</dt>\n <dd>{money(order.taxCents)}</dd>\n </div>\n )}\n <div className=\"sf-checkout-success__row sf-checkout-success__row--total\">\n <dt>{text.total}</dt>\n <dd>{money(order.totalCents)}</dd>\n </div>\n </dl>\n {slots?.afterTotals}\n {lines.length > 0 && (\n <address className=\"sf-checkout-success__address\">\n <strong>{text.shipTo}</strong>\n {lines.map((line) => (\n <span key={line}>{line}</span>\n ))}\n </address>\n )}\n {fulfillment?.trackingNumber && (\n <p className=\"sf-checkout-success__tracking\">\n {fulfillment.carrier && `${fulfillment.carrier} · `}\n {fulfillment.trackingUrl ? (\n <a href={fulfillment.trackingUrl}>\n {text.tracking} {fulfillment.trackingNumber}\n </a>\n ) : (\n fulfillment.trackingNumber\n )}\n </p>\n )}\n {actions && <div className=\"sf-checkout-success__actions\">{actions}</div>}\n </section>\n );\n}\n","import { useEffect, useRef } from 'react';\nimport { useCart } from './cart-provider';\n\nexport function ClearCartOnMount() {\n const { hydrated, clear } = useCart();\n const cleared = useRef(false);\n useEffect(() => {\n if (!hydrated || cleared.current) return;\n cleared.current = true;\n clear();\n }, [hydrated, clear]);\n return null;\n}\n"],"mappings":"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"}
package/dist/styles.css CHANGED
@@ -68,13 +68,22 @@
68
68
  grid-template-columns: repeat(3, 1fr);
69
69
  }
70
70
  }
71
+ .sf-grid__item {
72
+ display: flex;
73
+ }
71
74
  .sf-grid-empty {
75
+ display: flex;
76
+ flex-direction: column;
77
+ align-items: center;
72
78
  padding: calc(var(--sf-space-unit) * 12) 0;
73
79
  text-align: center;
74
80
  color: var(--sf-color-muted);
75
81
  }
76
82
 
77
83
  .sf-card {
84
+ display: flex;
85
+ flex-direction: column;
86
+ flex: 1;
78
87
  background: var(--sf-color-surface);
79
88
  border-radius: var(--sf-radius);
80
89
  overflow: hidden;
@@ -92,7 +101,9 @@
92
101
  transform: scale(var(--sf-card-image-hover-scale));
93
102
  }
94
103
  .sf-card__link {
95
- display: block;
104
+ display: flex;
105
+ flex: 1;
106
+ flex-direction: column;
96
107
  color: inherit;
97
108
  text-decoration: none;
98
109
  }
@@ -109,6 +120,7 @@
109
120
  transition: transform var(--sf-motion-duration) ease;
110
121
  }
111
122
  .sf-card__body {
123
+ flex: 1;
112
124
  display: flex;
113
125
  flex-direction: column;
114
126
  align-items: flex-start;
@@ -218,6 +230,9 @@
218
230
  display: grid;
219
231
  gap: calc(var(--sf-space-unit) * 3);
220
232
  }
233
+ .sf-shop {
234
+ gap: calc(var(--sf-space-unit) * 6);
235
+ }
221
236
 
222
237
  .sf-featured__heading {
223
238
  font-family: var(--sf-font-display);
@@ -226,20 +241,54 @@
226
241
 
227
242
  .sf-detail {
228
243
  display: grid;
229
- gap: calc(var(--sf-space-unit) * 3);
244
+ gap: calc(var(--sf-space-unit) * 4);
245
+ }
246
+ .sf-detail__back {
247
+ grid-column: 1 / -1;
248
+ justify-self: start;
249
+ color: var(--sf-color-muted);
250
+ font-family: var(--sf-font-body);
251
+ text-decoration: none;
252
+ }
253
+ .sf-detail__back:hover {
254
+ text-decoration: underline;
230
255
  }
231
256
 
232
257
  .sf-detail__body {
233
258
  display: grid;
234
- gap: calc(var(--sf-space-unit) * 2);
259
+ gap: calc(var(--sf-space-unit) * 3);
235
260
  align-content: start;
236
261
  }
237
262
 
238
263
  .sf-detail__title {
239
264
  font-family: var(--sf-font-display);
265
+ font-size: 2rem;
266
+ font-weight: 700;
267
+ line-height: 1.15;
268
+ margin: 0;
269
+ }
270
+
271
+ .sf-detail__description {
240
272
  margin: 0;
241
273
  }
242
274
 
275
+ .sf-detail__actions {
276
+ margin-top: calc(var(--sf-space-unit) * 3);
277
+ }
278
+
279
+ .sf-detail--sold .sf-gallery__image {
280
+ opacity: var(--sf-sold-opacity);
281
+ filter: var(--sf-sold-filter);
282
+ }
283
+
284
+ .sf-sold-note {
285
+ display: inline-block;
286
+ font-family: var(--sf-font-body);
287
+ letter-spacing: 0.04em;
288
+ text-transform: uppercase;
289
+ color: var(--sf-color-sold);
290
+ }
291
+
243
292
  .sf-detail__specs {
244
293
  display: grid;
245
294
  grid-template-columns: max-content 1fr;
@@ -468,6 +517,12 @@
468
517
  justify-content: space-between;
469
518
  margin: 0;
470
519
  }
520
+ .sf-cart-summary__tax-note {
521
+ margin: 0;
522
+ font-size: 0.8125rem;
523
+ color: var(--sf-color-muted);
524
+ }
525
+
471
526
  .sf-cart-summary__code {
472
527
  display: flex;
473
528
  flex-direction: column;
@@ -9,5 +9,7 @@ export interface ProductDetailViewProps {
9
9
  product: Product;
10
10
  slots?: ProductDetailViewSlots;
11
11
  labels?: Partial<AvailabilityLabels>;
12
+ backHref?: string;
13
+ backLabel?: string;
12
14
  }
13
- export declare function ProductDetailView({ product, slots, labels, }: ProductDetailViewProps): import('react').JSX.Element;
15
+ export declare function ProductDetailView({ product, slots, labels, backHref, backLabel, }: ProductDetailViewProps): import('react').JSX.Element;
package/docs/cart.md CHANGED
@@ -49,7 +49,7 @@ through `labels` (`outOfStock`, `unavailable`, `discountReasons`).
49
49
 
50
50
  `<CheckoutSuccessView order={order} locale? labels? />` renders an order from
51
51
  `getOrderBySession`: lines, subtotal, discount row (only when applied),
52
- shipping, total, ship-to address (only when you pass an order that includes
52
+ shipping, tax row (only when `taxCents` is above 0, `labels.tax`), total, ship-to address (only when you pass an order that includes
53
53
  `shippingAddress`; the public order-by-session route never returns it),
54
54
  tracking, and a message per `OrderStatus` (`pending` tells the buyer to expect
55
55
  an email). It has no hooks, so a server component can render it. Labels live in
@@ -59,12 +59,19 @@ once, even under React strict mode. Styles are `.sf-checkout-success*`.
59
59
  Extension points, all optional: `slots={{ header, beforeLines, afterTotals,
60
60
  actions }}` (ReactNode) put your own content under the heading, above the
61
61
  lines, below the totals, and in the footer. `showReference` adds a
62
- `labels.reference` row (default `Order {id}`), and `showSummaryHeading` adds an
62
+ `labels.reference` row (default `Order {id}`; `{id}` becomes the short
63
+ reference such as `#A1B2C3D4`), and `showSummaryHeading` adds an
63
64
  `<h2>` from `labels.summaryHeading`. `continueHref` renders a link labelled
64
65
  `labels.continue` unless `slots.actions` is given; pass `renderLink` to route it
65
66
  through your framework's link (the view uses no hooks, so it can't read the
66
67
  provider's).
67
68
 
69
+ ## Tax note
70
+
71
+ Tax isn't known before Stripe checkout. Pass `showTaxNote` to `CartSummary` (or
72
+ through `CartDrawer`'s `summary`) to show `labels.taxNote` ("Tax calculated at
73
+ checkout") under the subtotal. It is off by default.
74
+
68
75
  ## Discount codes
69
76
 
70
77
  `CartSummary` (and `CartDrawer` through `summary`) takes an optional `discount`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@develemit/storefront",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Brand-agnostic React storefront: components, page views, cart, checkout and a typed API client.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -53,6 +53,6 @@
53
53
  "access": "public"
54
54
  },
55
55
  "dependencies": {
56
- "@develemit/commerce-contracts": "0.7.0"
56
+ "@develemit/commerce-contracts": "0.9.0"
57
57
  }
58
58
  }