@burdenoff/microfe-billing 2026.623.2 → 2026.624.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/billing/BillingUserRoutes.js +3 -3
  2. package/dist/billing/BillingUserRoutes.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +34 -34
  4. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  5. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +94 -94
  6. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  7. package/dist/billing/modules/addons/pages/AddonsListPage.js +1 -1
  8. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  9. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +40 -40
  10. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  11. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +10 -10
  12. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  13. package/dist/billing/modules/billing/pages/TransactionsPage.js +62 -62
  14. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  15. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +7 -7
  16. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  17. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +1 -1
  18. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  19. package/dist/billing/modules/earnings/pages/EarningsPage.js +21 -21
  20. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  21. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +4 -4
  22. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  23. package/dist/billing/modules/earnings/pages/PayoutsPage.js +33 -33
  24. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  25. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +4 -4
  26. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  27. package/dist/billing/modules/plans/pages/PlansBrowsePage.js +146 -142
  28. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  29. package/dist/billing/modules/plans/pages/PlansListPage.js +1 -1
  30. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  31. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
  32. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  33. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +1 -1
  34. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  35. package/dist/billing/modules/settings/pages/SettingsPage.js +57 -57
  36. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  37. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +33 -33
  38. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  39. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +1 -1
  40. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  41. package/dist/billing/modules/usage/pages/UsagePage.js +1 -1
  42. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  43. package/dist/billing/shared/components/AccessDenied.js +4 -4
  44. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  45. package/dist/billing/shared/components/ActorIdentity.js +8 -8
  46. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  47. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +11 -11
  48. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  49. package/dist/billing/shared/ui/Card.js +1 -1
  50. package/dist/billing/shared/ui/Card.js.map +1 -1
  51. package/dist/billing/shared/ui/MetricCard.js +1 -1
  52. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  53. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AddonsBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonsBrowsePage.tsx"],"sourcesContent":["/**\n * Addons Module - Addons Browse Page (User-facing)\n * Browse available addons and add to cart. No create/edit/toggle actions.\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useDefaultBillingCurrency } from '../../../hooks/useDefaultBillingCurrency';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { ShoppingCart, Plus, Minus, Trash2, X, Package } from 'lucide-react';\nimport { useAddons } from '../hooks';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n} from '../../../shared/utils/format';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\n\ntype ViewMode = 'grid' | 'list';\n\n// ---------------------------------------------------------------------------\n// Grouping logic\n// Addons named \"Group — Variant\" (em dash) are grouped together.\n// Addons with a unique prefix or no em dash are shown standalone.\n// ---------------------------------------------------------------------------\n\ntype AddonGroupItem =\n | { type: 'group'; name: string; addons: Addon[] }\n | { type: 'standalone'; addon: Addon };\n\nfunction groupAddons(addons: Addon[]): AddonGroupItem[] {\n const groups = new Map<string, Addon[]>();\n for (const addon of addons) {\n const sepIdx = addon.name.indexOf(' — ');\n const key = sepIdx !== -1 ? addon.name.slice(0, sepIdx).trim() : addon.name;\n const bucket = groups.get(key) ?? [];\n bucket.push(addon);\n groups.set(key, bucket);\n }\n\n const items: AddonGroupItem[] = [];\n for (const [name, groupAddons] of groups) {\n if (groupAddons.length > 1) {\n items.push({ type: 'group', name, addons: groupAddons });\n } else {\n items.push({ type: 'standalone', addon: groupAddons[0] });\n }\n }\n return items;\n}\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nfunction getAddonDisplayPrice(\n addon: Addon,\n displayCurrency: string = 'USD'\n): { price: number; currency: string } {\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const currencyPricesObj = addon.currencyPrices as Record<string, number>;\n if (\n currencyPricesObj[displayCurrency] !== undefined &&\n currencyPricesObj[displayCurrency] !== null\n ) {\n return { price: currencyPricesObj[displayCurrency], currency: displayCurrency };\n }\n }\n return { price: addon.price, currency: addon.currency || 'USD' };\n}\n\nexport const AddonsBrowsePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const { productId } = useBilling();\n const { addons, isLoading, error, refetch } = useAddons({\n includeFree: false,\n productId: productId ?? undefined,\n });\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n const displayCurrency = useDefaultBillingCurrency();\n\n // Only show active addons to users\n const filteredAddons = addons.filter((addon) => {\n if (!addon.isActive) return false;\n if (billingInterval !== 'all' && addon.duration !== billingInterval) return false;\n return true;\n });\n\n const groupedItems = useMemo(() => groupAddons(filteredAddons), [filteredAddons]);\n\n if (isLoading && addons.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.addons.failedToLoad', 'Failed to load addons')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.addons.title', 'Addons')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.addons.subtitle', 'Enhance your subscription with additional features')}\n </p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n {(['all', 'monthly', 'yearly'] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval as PlanDuration | 'all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === interval\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {interval === 'all' ? 'All' : interval === 'monthly' ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Addons */}\n {filteredAddons.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"size-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-muted-foreground\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.addons.noAddonsAvailable', 'No addons available')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.addons.checkBackLater', 'Check back later for new addons.')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-8\">\n {groupedItems.map((item) => {\n if (item.type === 'group') {\n return (\n <AddonGroupSection\n key={item.name}\n groupName={item.name}\n addons={item.addons}\n viewMode={viewMode}\n getCartItem={getCartItem}\n isInCart={isInCart}\n addToCart={addToCart}\n updateQuantity={updateQuantity}\n removeFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n navigateTo={navigateTo}\n />\n );\n }\n const addon = item.addon;\n const cartItem = getCartItem(addon.id);\n return viewMode === 'grid' ? (\n <BrowseAddonCard\n key={addon.id}\n addon={addon}\n onView={() => navigateTo(`/addons/${addon.id}`)}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n ) : (\n <BrowseAddonRow\n key={addon.id}\n addon={addon}\n onView={() => navigateTo(`/addons/${addon.id}`)}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n );\n })}\n </div>\n )}\n\n <FloatingCartDrawer displayCurrency={displayCurrency} />\n </div>\n );\n};\n\n// ============================================================================\n// Addon Group Section\n// ============================================================================\n\ninterface AddonGroupSectionProps {\n groupName: string;\n addons: Addon[];\n viewMode: ViewMode;\n getCartItem: (id: string) => { quantity: number } | undefined;\n isInCart: (id: string) => boolean;\n addToCart: (addon: Addon) => void;\n updateQuantity: (id: string, qty: number) => void;\n removeFromCart: (id: string) => void;\n displayCurrency: string;\n navigateTo: (path: string) => void;\n}\n\nconst AddonGroupSection: FC<AddonGroupSectionProps> = ({\n groupName,\n addons,\n viewMode,\n getCartItem,\n isInCart,\n addToCart,\n updateQuantity,\n removeFromCart,\n displayCurrency,\n navigateTo,\n}) => {\n const cartCount = addons.filter((a) => isInCart(a.id)).length;\n\n return (\n <div className=\"border border-border rounded-xl overflow-hidden\">\n {/* Group header */}\n <div className=\"flex items-center justify-between px-5 py-3 bg-muted/40 border-b border-border\">\n <div className=\"flex items-center gap-2\">\n <Package className=\"size-4 text-muted-foreground\" />\n <h2 className=\"font-semibold text-foreground\">{groupName}</h2>\n <span className=\"text-xs text-muted-foreground\">\n {addons.length} option{addons.length !== 1 ? 's' : ''}\n </span>\n </div>\n {cartCount > 0 && (\n <span className=\"text-xs font-medium px-2 py-0.5 bg-primary text-primary-foreground rounded-full\">\n {cartCount} in cart\n </span>\n )}\n </div>\n\n {/* Variants */}\n {viewMode === 'grid' ? (\n <div className=\"p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">\n {addons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n const variantName = addon.name.includes(' — ')\n ? addon.name.slice(addon.name.indexOf(' — ') + 3).trim()\n : addon.name;\n const inCart = isInCart(addon.id);\n const qty = cartItem?.quantity ?? 0;\n const { price, currency } = (() => {\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const prices = addon.currencyPrices as Record<string, number>;\n if (prices[displayCurrency] != null)\n return { price: prices[displayCurrency], currency: displayCurrency };\n }\n return { price: addon.price, currency: addon.currency || 'USD' };\n })();\n\n return (\n <div\n key={addon.id}\n className={`relative flex flex-col rounded-lg border p-4 transition-all ${\n inCart\n ? 'border-primary bg-primary/5'\n : 'border-border bg-card hover:border-primary/40 hover:shadow-sm'\n }`}\n >\n {inCart && (\n <span className=\"absolute top-2 right-2 text-xs font-medium bg-primary text-primary-foreground px-1.5 py-0.5 rounded\">\n ×{qty}\n </span>\n )}\n <p className=\"font-semibold text-foreground text-sm mb-1\">{variantName}</p>\n <p className=\"text-xs text-muted-foreground mb-3\">\n {formatPlanDurationLabel(addon.duration)}\n </p>\n <p className=\"text-lg font-bold text-foreground mb-3\">\n {formatCurrency(price, currency)}\n <span className=\"text-xs font-normal text-muted-foreground ml-1\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </p>\n <div className=\"mt-auto flex flex-col gap-2\">\n {inCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty - 1)}\n className=\"p-1 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"flex-1 text-center text-sm font-medium\">{qty}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty + 1)}\n className=\"p-1 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1 rounded text-destructive hover:bg-destructive/10 transition-colors\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(addon)}\n className=\"w-full py-1.5 text-xs font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors flex items-center justify-center gap-1\"\n >\n <ShoppingCart className=\"size-3\" />\n Add to Cart\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}`)}\n className=\"w-full py-1.5 text-xs font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Details\n </button>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <div className=\"divide-y divide-border\">\n {addons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n const variantName = addon.name.includes(' — ')\n ? addon.name.slice(addon.name.indexOf(' — ') + 3).trim()\n : addon.name;\n const inCart = isInCart(addon.id);\n const qty = cartItem?.quantity ?? 0;\n const { price, currency } = (() => {\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const prices = addon.currencyPrices as Record<string, number>;\n if (prices[displayCurrency] != null)\n return { price: prices[displayCurrency], currency: displayCurrency };\n }\n return { price: addon.price, currency: addon.currency || 'USD' };\n })();\n\n return (\n <div\n key={addon.id}\n className={`flex items-center gap-4 px-5 py-3 transition-colors ${\n inCart ? 'bg-primary/5' : 'hover:bg-muted/30'\n }`}\n >\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-foreground text-sm\">{variantName}</p>\n <p className=\"text-xs text-muted-foreground\">\n {formatPlanDurationLabel(addon.duration)}\n </p>\n </div>\n <p className=\"font-semibold text-foreground text-sm whitespace-nowrap\">\n {formatCurrency(price, currency)}\n <span className=\"text-xs font-normal text-muted-foreground ml-1\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </p>\n {inCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty - 1)}\n className=\"p-1 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"w-6 text-center text-sm font-medium\">{qty}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty + 1)}\n className=\"p-1 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1 rounded text-destructive hover:bg-destructive/10 transition-colors\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(addon)}\n className=\"px-2 py-1 text-xs font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors flex items-center gap-1\"\n >\n <ShoppingCart className=\"size-3\" />\n Add\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}`)}\n className=\"px-2 py-1 text-xs font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Details\n </button>\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Browse Addon Card (no edit/toggle)\n// ============================================================================\n\ninterface BrowseAddonCardProps {\n addon: Addon;\n onView: () => void;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst BrowseAddonCard: FC<BrowseAddonCardProps> = ({\n addon,\n onView,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n displayCurrency,\n}) => {\n const combinedQuotas = useMemo(() => combineQuotas(addon.features || []), [addon.features]);\n\n return (\n <div\n className={`relative border rounded-lg p-6 transition-all hover:shadow-md flex flex-col h-full ${\n isInCart ? 'border-primary bg-primary/5' : 'border-border bg-card'\n }`}\n >\n {isInCart && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-primary text-primary-foreground rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center mb-4\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-foreground\">{addon.name}</h3>\n <span className=\"text-sm text-muted-foreground\">\n {formatPlanDurationLabel(addon.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-muted-foreground text-sm\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4\">\n {combinedQuotas.slice(0, 4).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 4 && (\n <li className=\"text-sm text-muted-foreground pl-6\">\n +{combinedQuotas.length - 4} more quotas\n </li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n {isInCart ? (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-10 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-destructive hover:bg-destructive/10 transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors flex items-center justify-center gap-2\"\n >\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </button>\n )}\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View Details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Addon Row (no edit/toggle)\n// ============================================================================\n\ninterface BrowseAddonRowProps {\n addon: Addon;\n onView: () => void;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst BrowseAddonRow: FC<BrowseAddonRowProps> = ({\n addon,\n onView,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n displayCurrency,\n}) => {\n const combinedQuotas = useMemo(() => combineQuotas(addon.features || []), [addon.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-lg transition-all hover:shadow-sm ${\n isInCart ? 'border-primary bg-primary/5' : 'border-border bg-card'\n }`}\n >\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground truncate\">{addon.name}</h3>\n {isInCart && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-primary text-primary-foreground rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''}\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-muted-foreground\">\n per {formatPlanDuration(addon.duration)}\n </div>\n </div>\n\n {isInCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-destructive hover:bg-destructive/10 transition-colors ml-1\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors flex items-center gap-1.5\"\n >\n <ShoppingCart className=\"size-4\" />\n Add\n </button>\n )}\n\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View\n </button>\n </div>\n );\n};\n\n// ============================================================================\n// Floating Cart Drawer\n// ============================================================================\n\nconst FloatingCartDrawer: FC<{ displayCurrency: string }> = ({ displayCurrency }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const { items, isCartOpen, toggleCart, closeCart, removeFromCart, updateQuantity, clearCart } =\n useAddonCartStore();\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n\n if (items.length === 0 && !isCartOpen) return null;\n\n return (\n <>\n {!isCartOpen && items.length > 0 && (\n <button\n type=\"button\"\n onClick={toggleCart}\n className=\"fixed bottom-6 right-6 z-50 flex items-center gap-2 px-4 py-3 bg-primary text-primary-foreground rounded-full shadow-lg hover:bg-primary/90 transition-all\"\n >\n <ShoppingCart className=\"size-5\" />\n <span className=\"font-medium\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n <span className=\"px-2 py-0.5 bg-primary-foreground/20 rounded-full text-sm\">\n {(() => {\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </button>\n )}\n\n {isCartOpen && (\n <div className=\"fixed inset-0 z-50 flex items-end sm:items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={closeCart} />\n <div className=\"relative w-full max-w-md max-h-[80vh] bg-card border border-border rounded-t-xl sm:rounded-xl shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border bg-muted/30\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"size-5 text-foreground\" />\n <h3 className=\"font-semibold text-foreground\">\n {tr('billing.addons.yourCart', 'Your Cart')}\n </h3>\n <span className=\"px-2 py-0.5 text-xs bg-primary text-primary-foreground rounded-full\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={closeCart}\n className=\"p-1.5 rounded hover:bg-muted transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <div className=\"overflow-y-auto max-h-[50vh] divide-y divide-border\">\n {items.map(({ addon, quantity }) => (\n <div key={addon.id} className=\"p-4 flex items-center gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-foreground truncate\">{addon.name}</h4>\n <p className=\"text-sm text-muted-foreground\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}{' '}\n / {formatPlanDuration(addon.duration)}\n </p>\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity - 1)}\n className=\"p-1 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"w-6 text-center text-sm font-medium\">{quantity}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity + 1)}\n className=\"p-1 rounded border border-border hover:bg-muted transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n </div>\n <p className=\"font-medium text-foreground\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price * quantity, currency);\n })()}\n </p>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1.5 rounded text-destructive hover:bg-destructive/10 transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ))}\n </div>\n\n <div className=\"border-t border-border p-4 space-y-3 bg-muted/20\">\n <div className=\"flex items-center justify-between text-lg font-semibold\">\n <span className=\"text-foreground\">{tr('billing.addons.total', 'Total')}</span>\n <span className=\"text-foreground\">\n {(() => {\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </div>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={clearCart}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Clear Cart\n </button>\n <button\n type=\"button\"\n onClick={() => {\n closeCart();\n navigateTo('/checkout/addons');\n }}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Checkout\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddonsBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonsBrowsePage.tsx"],"sourcesContent":["/**\n * Addons Module - Addons Browse Page (User-facing)\n * Browse available addons and add to cart. No create/edit/toggle actions.\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useDefaultBillingCurrency } from '../../../hooks/useDefaultBillingCurrency';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { ShoppingCart, Plus, Minus, Trash2, X, Package } from 'lucide-react';\nimport { useAddons } from '../hooks';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n} from '../../../shared/utils/format';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\n\ntype ViewMode = 'grid' | 'list';\n\n// ---------------------------------------------------------------------------\n// Grouping logic\n// Addons named \"Group — Variant\" (em dash) are grouped together.\n// Addons with a unique prefix or no em dash are shown standalone.\n// ---------------------------------------------------------------------------\n\ntype AddonGroupItem =\n | { type: 'group'; name: string; addons: Addon[] }\n | { type: 'standalone'; addon: Addon };\n\nfunction groupAddons(addons: Addon[]): AddonGroupItem[] {\n const groups = new Map<string, Addon[]>();\n for (const addon of addons) {\n const sepIdx = addon.name.indexOf(' — ');\n const key = sepIdx !== -1 ? addon.name.slice(0, sepIdx).trim() : addon.name;\n const bucket = groups.get(key) ?? [];\n bucket.push(addon);\n groups.set(key, bucket);\n }\n\n const items: AddonGroupItem[] = [];\n for (const [name, groupAddons] of groups) {\n if (groupAddons.length > 1) {\n items.push({ type: 'group', name, addons: groupAddons });\n } else {\n items.push({ type: 'standalone', addon: groupAddons[0] });\n }\n }\n return items;\n}\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nfunction getAddonDisplayPrice(\n addon: Addon,\n displayCurrency: string = 'USD'\n): { price: number; currency: string } {\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const currencyPricesObj = addon.currencyPrices as Record<string, number>;\n if (\n currencyPricesObj[displayCurrency] !== undefined &&\n currencyPricesObj[displayCurrency] !== null\n ) {\n return { price: currencyPricesObj[displayCurrency], currency: displayCurrency };\n }\n }\n return { price: addon.price, currency: addon.currency || 'USD' };\n}\n\nexport const AddonsBrowsePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const { productId } = useBilling();\n const { addons, isLoading, error, refetch } = useAddons({\n includeFree: false,\n productId: productId ?? undefined,\n });\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n const displayCurrency = useDefaultBillingCurrency();\n\n // Only show active addons to users\n const filteredAddons = addons.filter((addon) => {\n if (!addon.isActive) return false;\n if (billingInterval !== 'all' && addon.duration !== billingInterval) return false;\n return true;\n });\n\n const groupedItems = useMemo(() => groupAddons(filteredAddons), [filteredAddons]);\n\n if (isLoading && addons.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.failedToLoad', 'Failed to load addons')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.addons.title', 'Addons')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.addons.subtitle', 'Enhance your subscription with additional features')}\n </p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n {(['all', 'monthly', 'yearly'] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval as PlanDuration | 'all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === interval\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {interval === 'all' ? 'All' : interval === 'monthly' ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Addons */}\n {filteredAddons.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border-subtle rounded-lg\">\n <div className=\"size-12 rounded-full bg-bg-sunken flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr('billing.addons.noAddonsAvailable', 'No addons available')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.addons.checkBackLater', 'Check back later for new addons.')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-8\">\n {groupedItems.map((item) => {\n if (item.type === 'group') {\n return (\n <AddonGroupSection\n key={item.name}\n groupName={item.name}\n addons={item.addons}\n viewMode={viewMode}\n getCartItem={getCartItem}\n isInCart={isInCart}\n addToCart={addToCart}\n updateQuantity={updateQuantity}\n removeFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n navigateTo={navigateTo}\n />\n );\n }\n const addon = item.addon;\n const cartItem = getCartItem(addon.id);\n return viewMode === 'grid' ? (\n <BrowseAddonCard\n key={addon.id}\n addon={addon}\n onView={() => navigateTo(`/addons/${addon.id}`)}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n ) : (\n <BrowseAddonRow\n key={addon.id}\n addon={addon}\n onView={() => navigateTo(`/addons/${addon.id}`)}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n );\n })}\n </div>\n )}\n\n <FloatingCartDrawer displayCurrency={displayCurrency} />\n </div>\n );\n};\n\n// ============================================================================\n// Addon Group Section\n// ============================================================================\n\ninterface AddonGroupSectionProps {\n groupName: string;\n addons: Addon[];\n viewMode: ViewMode;\n getCartItem: (id: string) => { quantity: number } | undefined;\n isInCart: (id: string) => boolean;\n addToCart: (addon: Addon) => void;\n updateQuantity: (id: string, qty: number) => void;\n removeFromCart: (id: string) => void;\n displayCurrency: string;\n navigateTo: (path: string) => void;\n}\n\nconst AddonGroupSection: FC<AddonGroupSectionProps> = ({\n groupName,\n addons,\n viewMode,\n getCartItem,\n isInCart,\n addToCart,\n updateQuantity,\n removeFromCart,\n displayCurrency,\n navigateTo,\n}) => {\n const cartCount = addons.filter((a) => isInCart(a.id)).length;\n\n return (\n <div className=\"border border-border-subtle rounded-xl overflow-hidden\">\n {/* Group header */}\n <div className=\"flex items-center justify-between px-5 py-3 bg-bg-sunken/40 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Package className=\"size-4 text-text-secondary\" />\n <h2 className=\"font-semibold text-text-primary\">{groupName}</h2>\n <span className=\"text-xs text-text-secondary\">\n {addons.length} option{addons.length !== 1 ? 's' : ''}\n </span>\n </div>\n {cartCount > 0 && (\n <span className=\"text-xs font-medium px-2 py-0.5 bg-action-primary-bg text-action-primary-text rounded-full\">\n {cartCount} in cart\n </span>\n )}\n </div>\n\n {/* Variants */}\n {viewMode === 'grid' ? (\n <div className=\"p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4\">\n {addons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n const variantName = addon.name.includes(' — ')\n ? addon.name.slice(addon.name.indexOf(' — ') + 3).trim()\n : addon.name;\n const inCart = isInCart(addon.id);\n const qty = cartItem?.quantity ?? 0;\n const { price, currency } = (() => {\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const prices = addon.currencyPrices as Record<string, number>;\n if (prices[displayCurrency] != null)\n return { price: prices[displayCurrency], currency: displayCurrency };\n }\n return { price: addon.price, currency: addon.currency || 'USD' };\n })();\n\n return (\n <div\n key={addon.id}\n className={`relative flex flex-col rounded-lg border p-4 transition-all ${\n inCart\n ? 'border-primary bg-action-primary-bg/5'\n : 'border-border-subtle bg-bg-surface hover:border-primary/40 hover:shadow-sm'\n }`}\n >\n {inCart && (\n <span className=\"absolute top-2 right-2 text-xs font-medium bg-action-primary-bg text-action-primary-text px-1.5 py-0.5 rounded\">\n ×{qty}\n </span>\n )}\n <p className=\"font-semibold text-text-primary text-sm mb-1\">{variantName}</p>\n <p className=\"text-xs text-text-secondary mb-3\">\n {formatPlanDurationLabel(addon.duration)}\n </p>\n <p className=\"text-lg font-bold text-text-primary mb-3\">\n {formatCurrency(price, currency)}\n <span className=\"text-xs font-normal text-text-secondary ml-1\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </p>\n <div className=\"mt-auto flex flex-col gap-2\">\n {inCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty - 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"flex-1 text-center text-sm font-medium\">{qty}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty + 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(addon)}\n className=\"w-full py-1.5 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center justify-center gap-1\"\n >\n <ShoppingCart className=\"size-3\" />\n Add to Cart\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}`)}\n className=\"w-full py-1.5 text-xs font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n Details\n </button>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <div className=\"divide-y divide-border\">\n {addons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n const variantName = addon.name.includes(' — ')\n ? addon.name.slice(addon.name.indexOf(' — ') + 3).trim()\n : addon.name;\n const inCart = isInCart(addon.id);\n const qty = cartItem?.quantity ?? 0;\n const { price, currency } = (() => {\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const prices = addon.currencyPrices as Record<string, number>;\n if (prices[displayCurrency] != null)\n return { price: prices[displayCurrency], currency: displayCurrency };\n }\n return { price: addon.price, currency: addon.currency || 'USD' };\n })();\n\n return (\n <div\n key={addon.id}\n className={`flex items-center gap-4 px-5 py-3 transition-colors ${\n inCart ? 'bg-action-primary-bg/5' : 'hover:bg-bg-sunken/30'\n }`}\n >\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-text-primary text-sm\">{variantName}</p>\n <p className=\"text-xs text-text-secondary\">\n {formatPlanDurationLabel(addon.duration)}\n </p>\n </div>\n <p className=\"font-semibold text-text-primary text-sm whitespace-nowrap\">\n {formatCurrency(price, currency)}\n <span className=\"text-xs font-normal text-text-secondary ml-1\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </p>\n {inCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty - 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"w-6 text-center text-sm font-medium\">{qty}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, qty + 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n >\n <Trash2 className=\"size-3\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(addon)}\n className=\"px-2 py-1 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center gap-1\"\n >\n <ShoppingCart className=\"size-3\" />\n Add\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}`)}\n className=\"px-2 py-1 text-xs font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n Details\n </button>\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Browse Addon Card (no edit/toggle)\n// ============================================================================\n\ninterface BrowseAddonCardProps {\n addon: Addon;\n onView: () => void;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst BrowseAddonCard: FC<BrowseAddonCardProps> = ({\n addon,\n onView,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n displayCurrency,\n}) => {\n const combinedQuotas = useMemo(() => combineQuotas(addon.features || []), [addon.features]);\n\n return (\n <div\n className={`relative border rounded-lg p-6 transition-all hover:shadow-md flex flex-col h-full ${\n isInCart ? 'border-primary bg-action-primary-bg/5' : 'border-border-subtle bg-bg-surface'\n }`}\n >\n {isInCart && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n\n <div className=\"size-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center mb-4\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-text-primary\">{addon.name}</h3>\n <span className=\"text-sm text-text-secondary\">\n {formatPlanDurationLabel(addon.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-bold text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-text-secondary text-sm\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4\">\n {combinedQuotas.slice(0, 4).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-text-primary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 4 && (\n <li className=\"text-sm text-text-secondary pl-6\">\n +{combinedQuotas.length - 4} more quotas\n </li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n {isInCart ? (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-10 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center justify-center gap-2\"\n >\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </button>\n )}\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n View Details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Addon Row (no edit/toggle)\n// ============================================================================\n\ninterface BrowseAddonRowProps {\n addon: Addon;\n onView: () => void;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst BrowseAddonRow: FC<BrowseAddonRowProps> = ({\n addon,\n onView,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n displayCurrency,\n}) => {\n const combinedQuotas = useMemo(() => combineQuotas(addon.features || []), [addon.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-lg transition-all hover:shadow-sm ${\n isInCart ? 'border-primary bg-action-primary-bg/5' : 'border-border-subtle bg-bg-surface'\n }`}\n >\n <div className=\"size-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center shrink-0\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{addon.name}</h3>\n {isInCart && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''}\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-secondary\">\n per {formatPlanDuration(addon.duration)}\n </div>\n </div>\n\n {isInCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors ml-1\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors flex items-center gap-1.5\"\n >\n <ShoppingCart className=\"size-4\" />\n Add\n </button>\n )}\n\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n View\n </button>\n </div>\n );\n};\n\n// ============================================================================\n// Floating Cart Drawer\n// ============================================================================\n\nconst FloatingCartDrawer: FC<{ displayCurrency: string }> = ({ displayCurrency }) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const { items, isCartOpen, toggleCart, closeCart, removeFromCart, updateQuantity, clearCart } =\n useAddonCartStore();\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n\n if (items.length === 0 && !isCartOpen) return null;\n\n return (\n <>\n {!isCartOpen && items.length > 0 && (\n <button\n type=\"button\"\n onClick={toggleCart}\n className=\"fixed bottom-6 right-6 z-50 flex items-center gap-2 px-4 py-3 bg-action-primary-bg text-action-primary-text rounded-full shadow-lg hover:bg-action-primary-bg/90 transition-all\"\n >\n <ShoppingCart className=\"size-5\" />\n <span className=\"font-medium\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n <span className=\"px-2 py-0.5 bg-primary-foreground/20 rounded-full text-sm\">\n {(() => {\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </button>\n )}\n\n {isCartOpen && (\n <div className=\"fixed inset-0 z-50 flex items-end sm:items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={closeCart} />\n <div className=\"relative w-full max-w-md max-h-[80vh] bg-bg-surface border border-border-subtle rounded-t-xl sm:rounded-xl shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-subtle bg-bg-sunken/30\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"size-5 text-text-primary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.yourCart', 'Your Cart')}\n </h3>\n <span className=\"px-2 py-0.5 text-xs bg-action-primary-bg text-action-primary-text rounded-full\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={closeCart}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <div className=\"overflow-y-auto max-h-[50vh] divide-y divide-border\">\n {items.map(({ addon, quantity }) => (\n <div key={addon.id} className=\"p-4 flex items-center gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-text-primary truncate\">{addon.name}</h4>\n <p className=\"text-sm text-text-secondary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}{' '}\n / {formatPlanDuration(addon.duration)}\n </p>\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity - 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"w-6 text-center text-sm font-medium\">{quantity}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity + 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n </div>\n <p className=\"font-medium text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price * quantity, currency);\n })()}\n </p>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg/10 transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ))}\n </div>\n\n <div className=\"border-t border-border-subtle p-4 space-y-3 bg-bg-sunken/20\">\n <div className=\"flex items-center justify-between text-lg font-semibold\">\n <span className=\"text-text-primary\">{tr('billing.addons.total', 'Total')}</span>\n <span className=\"text-text-primary\">\n {(() => {\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </div>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={clearCart}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium border border-border-subtle text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n Clear Cart\n </button>\n <button\n type=\"button\"\n onClick={() => {\n closeCart();\n navigateTo('/checkout/addons');\n }}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Checkout\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </>\n );\n};\n"],"mappings":"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"}
@@ -329,7 +329,7 @@ var C = () => {
329
329
  }, w = ({ addon: r, onView: i, onEdit: a, onToggle: o, canManage: s, displayCurrency: c, isToggling: l, isInCart: d, cartQuantity: g, onAddToCart: _, onUpdateQuantity: b, onRemoveFromCart: C }) => {
330
330
  let w = u(() => x(r.features || []), [r.features]);
331
331
  return /* @__PURE__ */ y("div", {
332
- className: `relative border rounded-card p-6 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:-translate-y-0.5 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] flex flex-col h-full ${r.isActive ? d ? "border-action-primary-bg bg-[var(--color-accent-soft)]" : "border-border-subtle bg-bg-surface" : "border-dashed border-text-muted/30 bg-bg-sunken/30"}`,
332
+ className: `relative border rounded-card p-6 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] flex flex-col h-full ${r.isActive ? d ? "border-action-primary-bg bg-[var(--color-accent-soft)]" : "border-border-subtle bg-bg-surface" : "border-dashed border-text-muted/30 bg-bg-sunken/30"}`,
333
333
  children: [
334
334
  !r.isActive && /* @__PURE__ */ v("span", {
335
335
  className: "absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-bg-sunken text-text-muted rounded",
@@ -1 +1 @@
1
- {"version":3,"file":"AddonsListPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonsListPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addons List Page\n * Displays all available addons with cart functionality\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ShoppingCart, Plus, Minus, Trash2, X } from 'lucide-react';\nimport { useAddons, useAddonMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n} from '../../../shared/utils/format';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useDefaultBillingCurrency } from '../../../hooks/useDefaultBillingCurrency';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\n/**\n * Get the display price and currency for an addon\n * Uses currencyPrices if available, otherwise falls back to base price\n */\nfunction getAddonDisplayPrice(\n addon: Addon,\n displayCurrency: string = 'USD'\n): { price: number; currency: string } {\n // If currencyPrices exists and has the requested currency, use it\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const currencyPricesObj = addon.currencyPrices as Record<string, number>;\n if (currencyPricesObj[displayCurrency]) {\n return {\n price: currencyPricesObj[displayCurrency],\n currency: displayCurrency,\n };\n }\n }\n\n // Fallback to base USD price\n return {\n price: addon.price,\n currency: addon.currency || 'USD',\n };\n}\n\nexport const AddonsListPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { addons, isLoading, error, refetch } = useAddons();\n const { toggleAddon, isToggling } = useAddonMutations();\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showInactive, setShowInactive] = useState(false);\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n const displayCurrency = useDefaultBillingCurrency();\n\n // Permission check\n if (!permissions.canViewAddons) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-muted max-w-sm\">\n {tr('billing.addons.noViewPermission', \"You don't have permission to view addons.\")}\n </p>\n </div>\n </div>\n );\n }\n\n // Filter addons\n const filteredAddons = addons.filter((addon) => {\n if (!showInactive && !addon.isActive) return false;\n if (billingInterval !== 'all' && addon.duration !== billingInterval) return false;\n return true;\n });\n\n const handleToggleAddon = async (addon: Addon) => {\n if (!permissions.canManageAddons) return;\n try {\n await toggleAddon(addon.id, !addon.isActive);\n await refetch();\n } catch (err) {\n console.error('Failed to toggle addon:', err);\n }\n };\n\n const handleViewAddon = (addon: Addon) => {\n navigateTo(`/addons/${addon.id}`);\n };\n\n const handleEditAddon = (addon: Addon) => {\n navigateTo(`/addons/${addon.id}/edit`);\n };\n\n // Loading state\n if (isLoading && addons.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.failedToLoad', 'Failed to load addons')}\n </h2>\n <p className=\"text-sm text-text-muted\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.addons.title', 'Addons')}\n </h1>\n <p className=\"text-sm text-text-muted mt-1\">\n {tr('billing.addons.subtitle', 'Enhance your subscription with additional features')}\n </p>\n </div>\n\n {permissions.canManageAddons && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons/new')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Create Addon\n </button>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Billing Interval Toggle */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setBillingInterval('all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'all'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('monthly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'monthly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Monthly\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('yearly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'yearly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Yearly\n </button>\n </div>\n\n {/* Show Inactive Toggle */}\n {permissions.canManageAddons && (\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-text-muted\">\n {tr('billing.addons.showInactive', 'Show inactive addons')}\n </span>\n </label>\n )}\n\n {/* View Mode Toggle */}\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Addons Grid/List */}\n {filteredAddons.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-16 border border-dashed border-border-subtle rounded-card bg-bg-surface\">\n <div className=\"size-12 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-text-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr('billing.addons.noAddonsFound', 'No addons found')}\n </h3>\n <p className=\"text-sm text-text-muted mt-1\">\n {showInactive\n ? tr('billing.addons.noAddonsFiltered', 'No addons match your current filters.')\n : tr('billing.addons.noActiveAddons', 'There are no active addons available.')}\n </p>\n {permissions.canManageAddons && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons/new')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.addons.createFirstAddon', 'Create your first addon')}\n </button>\n )}\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredAddons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n return (\n <AddonCard\n key={addon.id}\n addon={addon}\n onView={handleViewAddon}\n onEdit={handleEditAddon}\n onToggle={handleToggleAddon}\n canManage={permissions.canManageAddons}\n isToggling={isToggling}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n );\n })}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredAddons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n return (\n <AddonRow\n key={addon.id}\n addon={addon}\n onView={handleViewAddon}\n onEdit={handleEditAddon}\n onToggle={handleToggleAddon}\n canManage={permissions.canManageAddons}\n isToggling={isToggling}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n );\n })}\n </div>\n )}\n\n {/* Floating Cart Drawer */}\n <FloatingCartDrawer displayCurrency={displayCurrency} />\n </div>\n );\n};\n\n// ============================================================================\n// Addon Card Component\n// ============================================================================\n\ninterface AddonCardProps {\n addon: Addon;\n onView: (addon: Addon) => void;\n onEdit: (addon: Addon) => void;\n onToggle: (addon: Addon) => void;\n canManage: boolean;\n isToggling: boolean;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst AddonCard: FC<AddonCardProps> = ({\n addon,\n onView,\n onEdit,\n onToggle,\n canManage,\n displayCurrency,\n isToggling,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features);\n }, [addon.features]);\n\n return (\n <div\n className={`relative border rounded-card p-6 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:-translate-y-0.5 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] flex flex-col h-full ${\n addon.isActive\n ? isInCart\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle bg-bg-surface'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30'\n }`}\n >\n {/* Status Badge */}\n {!addon.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n\n {/* In Cart Badge */}\n {isInCart && addon.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n\n {/* Addon Icon */}\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n {/* Addon Name */}\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-text-primary\">{addon.name}</h3>\n <span className=\"text-sm text-text-muted\">{formatPlanDurationLabel(addon.duration)}</span>\n </div>\n\n {/* Price */}\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-bold text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-text-muted text-sm\">/ {formatPlanDuration(addon.duration)}</span>\n </div>\n\n {/* Combined Quotas */}\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4\">\n {combinedQuotas.slice(0, 4).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-text-primary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 4 && (\n <li className=\"text-sm text-text-muted pl-6\">\n +{combinedQuotas.length - 4} more quotas\n </li>\n )}\n </ul>\n )}\n\n {/* Actions - pushed to bottom with mt-auto */}\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n {/* Add to Cart / Quantity Controls */}\n {addon.isActive &&\n (isInCart ? (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-10 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors flex items-center justify-center gap-2\"\n >\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </button>\n ))}\n\n <button\n type=\"button\"\n onClick={() => onView(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View Details\n </button>\n\n {canManage && (\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => onEdit(addon)}\n className=\"flex-1 px-3 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n onClick={() => onToggle(addon)}\n disabled={isToggling}\n className={`px-3 py-2 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n addon.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {addon.isActive ? 'Deactivate' : 'Activate'}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Addon Row Component (List View)\n// ============================================================================\n\ninterface AddonRowProps {\n addon: Addon;\n onView: (addon: Addon) => void;\n onEdit: (addon: Addon) => void;\n onToggle: (addon: Addon) => void;\n canManage: boolean;\n isToggling: boolean;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst AddonRow: FC<AddonRowProps> = ({\n addon,\n onView,\n onEdit,\n onToggle,\n canManage,\n displayCurrency,\n isToggling,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features);\n }, [addon.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-card shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] ${\n addon.isActive\n ? isInCart\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle bg-bg-surface'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30'\n }`}\n >\n {/* Addon Icon */}\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n {/* Addon Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{addon.name}</h3>\n {!addon.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n {isInCart && addon.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-muted\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''}\n </p>\n </div>\n\n {/* Price */}\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-muted\">per {formatPlanDuration(addon.duration)}</div>\n </div>\n\n {/* Cart Actions */}\n {addon.isActive &&\n (isInCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg-subtle transition-colors ml-1\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors flex items-center gap-1.5\"\n >\n <ShoppingCart className=\"size-4\" />\n Add\n </button>\n ))}\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => onView(addon)}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View\n </button>\n {canManage && (\n <>\n <button\n type=\"button\"\n onClick={() => onEdit(addon)}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n onClick={() => onToggle(addon)}\n disabled={isToggling}\n className={`px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n addon.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {addon.isActive ? 'Deactivate' : 'Activate'}\n </button>\n </>\n )}\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Floating Cart Drawer\n// ============================================================================\n\ninterface FloatingCartDrawerProps {\n displayCurrency: string;\n}\n\nconst FloatingCartDrawer: FC<FloatingCartDrawerProps> = ({ displayCurrency }) => {\n const navigateTo = useBillingNavigate();\n const { items, isCartOpen, toggleCart, closeCart, removeFromCart, updateQuantity, clearCart } =\n useAddonCartStore();\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n\n if (items.length === 0 && !isCartOpen) return null;\n\n return (\n <>\n {/* Floating Cart Button */}\n {!isCartOpen && items.length > 0 && (\n <button\n type=\"button\"\n onClick={toggleCart}\n className=\"fixed bottom-6 right-6 z-50 flex items-center gap-2 px-4 py-3 bg-action-primary-bg text-action-primary-text rounded-full shadow-[var(--shadow-elevation-3)] hover:bg-action-primary-bgHover transition-all duration-200\"\n >\n <ShoppingCart className=\"size-5\" />\n <span className=\"font-medium\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n <span className=\"px-2 py-0.5 bg-[var(--color-accent-soft)] rounded-full text-sm\">\n {(() => {\n // Calculate cart total using display currency prices\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </button>\n )}\n\n {/* Cart Drawer */}\n {isCartOpen && (\n <div className=\"fixed inset-0 z-50 flex items-end sm:items-center justify-center\">\n {/* Backdrop */}\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={closeCart} />\n\n {/* Drawer */}\n <div className=\"relative w-full max-w-md max-h-[80vh] bg-bg-elevated border border-border-subtle rounded-t-card sm:rounded-card shadow-[var(--shadow-elevation-4)] overflow-hidden\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-subtle bg-bg-sunken/30\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"size-5 text-text-primary\" />\n <h3 className=\"font-semibold text-text-primary\">Your Cart</h3>\n <span className=\"px-2 py-0.5 text-xs bg-action-primary-bg text-action-primary-text rounded-full\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={closeCart}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Cart Items */}\n <div className=\"overflow-y-auto max-h-[50vh] divide-y divide-border-subtle\">\n {items.map(({ addon, quantity }) => (\n <div key={addon.id} className=\"p-4 flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-text-primary truncate\">{addon.name}</h4>\n <p className=\"text-sm text-text-muted\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}{' '}\n / {formatPlanDuration(addon.duration)}\n </p>\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity - 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"w-6 text-center text-sm font-medium\">{quantity}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity + 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n </div>\n <div className=\"text-right\">\n <p className=\"font-medium text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price * quantity, currency);\n })()}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ))}\n </div>\n\n {/* Footer */}\n <div className=\"border-t border-border-subtle p-4 space-y-3 bg-bg-sunken/20\">\n <div className=\"flex items-center justify-between text-lg font-semibold\">\n <span className=\"text-text-primary\">Total</span>\n <span className=\"text-text-primary\">\n {(() => {\n // Calculate cart total using display currency prices\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </div>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={clearCart}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Clear Cart\n </button>\n <button\n type=\"button\"\n onClick={() => {\n closeCart();\n navigateTo('/checkout/addons');\n }}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Checkout\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddonsListPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonsListPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addons List Page\n * Displays all available addons with cart functionality\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ShoppingCart, Plus, Minus, Trash2, X } from 'lucide-react';\nimport { useAddons, useAddonMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n} from '../../../shared/utils/format';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useDefaultBillingCurrency } from '../../../hooks/useDefaultBillingCurrency';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\n/**\n * Get the display price and currency for an addon\n * Uses currencyPrices if available, otherwise falls back to base price\n */\nfunction getAddonDisplayPrice(\n addon: Addon,\n displayCurrency: string = 'USD'\n): { price: number; currency: string } {\n // If currencyPrices exists and has the requested currency, use it\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const currencyPricesObj = addon.currencyPrices as Record<string, number>;\n if (currencyPricesObj[displayCurrency]) {\n return {\n price: currencyPricesObj[displayCurrency],\n currency: displayCurrency,\n };\n }\n }\n\n // Fallback to base USD price\n return {\n price: addon.price,\n currency: addon.currency || 'USD',\n };\n}\n\nexport const AddonsListPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { addons, isLoading, error, refetch } = useAddons();\n const { toggleAddon, isToggling } = useAddonMutations();\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showInactive, setShowInactive] = useState(false);\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n const displayCurrency = useDefaultBillingCurrency();\n\n // Permission check\n if (!permissions.canViewAddons) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-muted max-w-sm\">\n {tr('billing.addons.noViewPermission', \"You don't have permission to view addons.\")}\n </p>\n </div>\n </div>\n );\n }\n\n // Filter addons\n const filteredAddons = addons.filter((addon) => {\n if (!showInactive && !addon.isActive) return false;\n if (billingInterval !== 'all' && addon.duration !== billingInterval) return false;\n return true;\n });\n\n const handleToggleAddon = async (addon: Addon) => {\n if (!permissions.canManageAddons) return;\n try {\n await toggleAddon(addon.id, !addon.isActive);\n await refetch();\n } catch (err) {\n console.error('Failed to toggle addon:', err);\n }\n };\n\n const handleViewAddon = (addon: Addon) => {\n navigateTo(`/addons/${addon.id}`);\n };\n\n const handleEditAddon = (addon: Addon) => {\n navigateTo(`/addons/${addon.id}/edit`);\n };\n\n // Loading state\n if (isLoading && addons.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.failedToLoad', 'Failed to load addons')}\n </h2>\n <p className=\"text-sm text-text-muted\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.addons.title', 'Addons')}\n </h1>\n <p className=\"text-sm text-text-muted mt-1\">\n {tr('billing.addons.subtitle', 'Enhance your subscription with additional features')}\n </p>\n </div>\n\n {permissions.canManageAddons && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons/new')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Create Addon\n </button>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Billing Interval Toggle */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setBillingInterval('all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'all'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('monthly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'monthly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Monthly\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('yearly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'yearly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Yearly\n </button>\n </div>\n\n {/* Show Inactive Toggle */}\n {permissions.canManageAddons && (\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-text-muted\">\n {tr('billing.addons.showInactive', 'Show inactive addons')}\n </span>\n </label>\n )}\n\n {/* View Mode Toggle */}\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Addons Grid/List */}\n {filteredAddons.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-16 border border-dashed border-border-subtle rounded-card bg-bg-surface\">\n <div className=\"size-12 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-text-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr('billing.addons.noAddonsFound', 'No addons found')}\n </h3>\n <p className=\"text-sm text-text-muted mt-1\">\n {showInactive\n ? tr('billing.addons.noAddonsFiltered', 'No addons match your current filters.')\n : tr('billing.addons.noActiveAddons', 'There are no active addons available.')}\n </p>\n {permissions.canManageAddons && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons/new')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.addons.createFirstAddon', 'Create your first addon')}\n </button>\n )}\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredAddons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n return (\n <AddonCard\n key={addon.id}\n addon={addon}\n onView={handleViewAddon}\n onEdit={handleEditAddon}\n onToggle={handleToggleAddon}\n canManage={permissions.canManageAddons}\n isToggling={isToggling}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n );\n })}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredAddons.map((addon) => {\n const cartItem = getCartItem(addon.id);\n return (\n <AddonRow\n key={addon.id}\n addon={addon}\n onView={handleViewAddon}\n onEdit={handleEditAddon}\n onToggle={handleToggleAddon}\n canManage={permissions.canManageAddons}\n isToggling={isToggling}\n isInCart={isInCart(addon.id)}\n cartQuantity={cartItem?.quantity || 0}\n onAddToCart={addToCart}\n onUpdateQuantity={updateQuantity}\n onRemoveFromCart={removeFromCart}\n displayCurrency={displayCurrency}\n />\n );\n })}\n </div>\n )}\n\n {/* Floating Cart Drawer */}\n <FloatingCartDrawer displayCurrency={displayCurrency} />\n </div>\n );\n};\n\n// ============================================================================\n// Addon Card Component\n// ============================================================================\n\ninterface AddonCardProps {\n addon: Addon;\n onView: (addon: Addon) => void;\n onEdit: (addon: Addon) => void;\n onToggle: (addon: Addon) => void;\n canManage: boolean;\n isToggling: boolean;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst AddonCard: FC<AddonCardProps> = ({\n addon,\n onView,\n onEdit,\n onToggle,\n canManage,\n displayCurrency,\n isToggling,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features);\n }, [addon.features]);\n\n return (\n <div\n className={`relative border rounded-card p-6 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] flex flex-col h-full ${\n addon.isActive\n ? isInCart\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle bg-bg-surface'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30'\n }`}\n >\n {/* Status Badge */}\n {!addon.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n\n {/* In Cart Badge */}\n {isInCart && addon.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n\n {/* Addon Icon */}\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n {/* Addon Name */}\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-text-primary\">{addon.name}</h3>\n <span className=\"text-sm text-text-muted\">{formatPlanDurationLabel(addon.duration)}</span>\n </div>\n\n {/* Price */}\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-bold text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-text-muted text-sm\">/ {formatPlanDuration(addon.duration)}</span>\n </div>\n\n {/* Combined Quotas */}\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4\">\n {combinedQuotas.slice(0, 4).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-text-primary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 4 && (\n <li className=\"text-sm text-text-muted pl-6\">\n +{combinedQuotas.length - 4} more quotas\n </li>\n )}\n </ul>\n )}\n\n {/* Actions - pushed to bottom with mt-auto */}\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n {/* Add to Cart / Quantity Controls */}\n {addon.isActive &&\n (isInCart ? (\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-10 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors flex items-center justify-center gap-2\"\n >\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </button>\n ))}\n\n <button\n type=\"button\"\n onClick={() => onView(addon)}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View Details\n </button>\n\n {canManage && (\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => onEdit(addon)}\n className=\"flex-1 px-3 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n onClick={() => onToggle(addon)}\n disabled={isToggling}\n className={`px-3 py-2 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n addon.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {addon.isActive ? 'Deactivate' : 'Activate'}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Addon Row Component (List View)\n// ============================================================================\n\ninterface AddonRowProps {\n addon: Addon;\n onView: (addon: Addon) => void;\n onEdit: (addon: Addon) => void;\n onToggle: (addon: Addon) => void;\n canManage: boolean;\n isToggling: boolean;\n isInCart: boolean;\n cartQuantity: number;\n onAddToCart: (addon: Addon) => void;\n onUpdateQuantity: (addonId: string, quantity: number) => void;\n onRemoveFromCart: (addonId: string) => void;\n displayCurrency: string;\n}\n\nconst AddonRow: FC<AddonRowProps> = ({\n addon,\n onView,\n onEdit,\n onToggle,\n canManage,\n displayCurrency,\n isToggling,\n isInCart,\n cartQuantity,\n onAddToCart,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features);\n }, [addon.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-card shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] ${\n addon.isActive\n ? isInCart\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle bg-bg-surface'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30'\n }`}\n >\n {/* Addon Icon */}\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n\n {/* Addon Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{addon.name}</h3>\n {!addon.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n {isInCart && addon.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-action-primary-bg text-action-primary-text rounded\">\n In Cart ({cartQuantity})\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-muted\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''}\n </p>\n </div>\n\n {/* Price */}\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-muted\">per {formatPlanDuration(addon.duration)}</div>\n </div>\n\n {/* Cart Actions */}\n {addon.isActive &&\n (isInCart ? (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity - 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(addon.id, cartQuantity + 1)}\n className=\"p-1.5 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onRemoveFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg-subtle transition-colors ml-1\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => onAddToCart(addon)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors flex items-center gap-1.5\"\n >\n <ShoppingCart className=\"size-4\" />\n Add\n </button>\n ))}\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => onView(addon)}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View\n </button>\n {canManage && (\n <>\n <button\n type=\"button\"\n onClick={() => onEdit(addon)}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Edit\n </button>\n <button\n type=\"button\"\n onClick={() => onToggle(addon)}\n disabled={isToggling}\n className={`px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n addon.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {addon.isActive ? 'Deactivate' : 'Activate'}\n </button>\n </>\n )}\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Floating Cart Drawer\n// ============================================================================\n\ninterface FloatingCartDrawerProps {\n displayCurrency: string;\n}\n\nconst FloatingCartDrawer: FC<FloatingCartDrawerProps> = ({ displayCurrency }) => {\n const navigateTo = useBillingNavigate();\n const { items, isCartOpen, toggleCart, closeCart, removeFromCart, updateQuantity, clearCart } =\n useAddonCartStore();\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n\n if (items.length === 0 && !isCartOpen) return null;\n\n return (\n <>\n {/* Floating Cart Button */}\n {!isCartOpen && items.length > 0 && (\n <button\n type=\"button\"\n onClick={toggleCart}\n className=\"fixed bottom-6 right-6 z-50 flex items-center gap-2 px-4 py-3 bg-action-primary-bg text-action-primary-text rounded-full shadow-[var(--shadow-elevation-3)] hover:bg-action-primary-bgHover transition-all duration-200\"\n >\n <ShoppingCart className=\"size-5\" />\n <span className=\"font-medium\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n <span className=\"px-2 py-0.5 bg-[var(--color-accent-soft)] rounded-full text-sm\">\n {(() => {\n // Calculate cart total using display currency prices\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </button>\n )}\n\n {/* Cart Drawer */}\n {isCartOpen && (\n <div className=\"fixed inset-0 z-50 flex items-end sm:items-center justify-center\">\n {/* Backdrop */}\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={closeCart} />\n\n {/* Drawer */}\n <div className=\"relative w-full max-w-md max-h-[80vh] bg-bg-elevated border border-border-subtle rounded-t-card sm:rounded-card shadow-[var(--shadow-elevation-4)] overflow-hidden\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-subtle bg-bg-sunken/30\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"size-5 text-text-primary\" />\n <h3 className=\"font-semibold text-text-primary\">Your Cart</h3>\n <span className=\"px-2 py-0.5 text-xs bg-action-primary-bg text-action-primary-text rounded-full\">\n {itemCount} item{itemCount !== 1 ? 's' : ''}\n </span>\n </div>\n <button\n type=\"button\"\n onClick={closeCart}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Cart Items */}\n <div className=\"overflow-y-auto max-h-[50vh] divide-y divide-border-subtle\">\n {items.map(({ addon, quantity }) => (\n <div key={addon.id} className=\"p-4 flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4\"\n />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h4 className=\"font-medium text-text-primary truncate\">{addon.name}</h4>\n <p className=\"text-sm text-text-muted\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price, currency);\n })()}{' '}\n / {formatPlanDuration(addon.duration)}\n </p>\n </div>\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity - 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Minus className=\"size-3\" />\n </button>\n <span className=\"w-6 text-center text-sm font-medium\">{quantity}</span>\n <button\n type=\"button\"\n onClick={() => updateQuantity(addon.id, quantity + 1)}\n className=\"p-1 rounded border border-border-subtle hover:bg-bg-sunken transition-colors\"\n >\n <Plus className=\"size-3\" />\n </button>\n </div>\n <div className=\"text-right\">\n <p className=\"font-medium text-text-primary\">\n {(() => {\n const { price, currency } = getAddonDisplayPrice(addon, displayCurrency);\n return formatCurrency(price * quantity, currency);\n })()}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => removeFromCart(addon.id)}\n className=\"p-1.5 rounded text-status-error-text hover:bg-status-error-bg-subtle transition-colors\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n </div>\n ))}\n </div>\n\n {/* Footer */}\n <div className=\"border-t border-border-subtle p-4 space-y-3 bg-bg-sunken/20\">\n <div className=\"flex items-center justify-between text-lg font-semibold\">\n <span className=\"text-text-primary\">Total</span>\n <span className=\"text-text-primary\">\n {(() => {\n // Calculate cart total using display currency prices\n const total = items.reduce((sum, { addon, quantity }) => {\n const { price } = getAddonDisplayPrice(addon, displayCurrency);\n return sum + price * quantity;\n }, 0);\n return formatCurrency(total, displayCurrency);\n })()}\n </span>\n </div>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={clearCart}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Clear Cart\n </button>\n <button\n type=\"button\"\n onClick={() => {\n closeCart();\n navigateTo('/checkout/addons');\n }}\n className=\"flex-1 px-4 py-2.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Checkout\n </button>\n </div>\n </div>\n </div>\n </div>\n )}\n </>\n );\n};\n"],"mappings":"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"}