@burdenoff/microfe-billing 2026.1026.3 → 2026.1027.2

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.
@@ -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 { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\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 <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.addons.listPurpose',\n 'Manage the catalog of add-ons customers can buy alongside their subscriptions — including inactive ones. Create new add-ons, edit their price and the quotas they grant, and activate or deactivate them to control availability.'\n )}\n </PagePurpose>\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 <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.addons.noAddonsFound', 'No addons found')}\n description={\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 }\n action={\n permissions.canManageAddons ? (\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons/new')}\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 {tr('billing.addons.createFirstAddon', 'Create your first addon')}\n </button>\n ) : undefined\n }\n />\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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"}
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 { createPortal } from 'react-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ShoppingCart, Plus, Minus, Trash2, X } from 'lucide-react';\nimport { ASSISTANT_BOTTOM_CHROME_CLEARANCE } from '@burdenoff/fe-libs/shared/assistant';\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 { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\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 <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.addons.listPurpose',\n 'Manage the catalog of add-ons customers can buy alongside their subscriptions — including inactive ones. Create new add-ons, edit their price and the quotas they grant, and activate or deactivate them to control availability.'\n )}\n </PagePurpose>\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 <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.addons.noAddonsFound', 'No addons found')}\n description={\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 }\n action={\n permissions.canManageAddons ? (\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons/new')}\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 {tr('billing.addons.createFirstAddon', 'Create your first addon')}\n </button>\n ) : undefined\n }\n />\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 — portaled to <body> so it stays anchored to\n the real viewport, not a transformed route-transition ancestor on\n the installed app (see AddonsBrowsePage's FloatingCartDrawer for\n the full explanation). Bottom-left on phone widths only so it\n doesn't compete with the AI Assistant launcher's bottom-right\n corner (BOFF-9854); unchanged bottom-right at `sm:` and up. */}\n {!isCartOpen &&\n items.length > 0 &&\n createPortal(\n <button\n type=\"button\"\n onClick={toggleCart}\n style={{ bottom: `calc(1.5rem + ${ASSISTANT_BOTTOM_CHROME_CLEARANCE})` }}\n className=\"fixed left-6 sm:left-auto sm: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 document.body\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,16 +1,44 @@
1
1
  //#region src/billing/modules/plans/constants/enterprise.ts
2
2
  var e = {
3
3
  highlights: [
4
- "Custom usage volumes",
5
- "SSO / SCIM provisioning",
6
- "Custom SLA & support contract",
7
- "Volume-based custom pricing",
8
- "Audit log with custom retention",
9
- "Priority onboarding & migration"
4
+ "Pricing tailored to your organization",
5
+ "Volume and multi-year terms",
6
+ "Contract and invoicing that fit your procurement process",
7
+ "Talk to our team about your requirements"
10
8
  ],
11
- summary: "SSO/SCIM · Custom SLA · Custom pricing",
9
+ summary: "Custom contract · Volume pricing",
12
10
  contactEmail: "contact@burdenoff.com"
13
11
  }, t = {
12
+ bigconsole: {
13
+ highlights: [
14
+ "Everything in Professional",
15
+ "Dedicated support",
16
+ "SLA guarantee",
17
+ "Unlimited storage",
18
+ "Advanced security",
19
+ "Built-in integrations",
20
+ "SSO & SAML",
21
+ "Audit logs",
22
+ "SCIM user provisioning",
23
+ "Dedicated account manager"
24
+ ],
25
+ summary: "SSO/SCIM · SLA guarantee · Dedicated account manager",
26
+ contactEmail: "contact@bigconsole.com"
27
+ },
28
+ botmanor: {
29
+ highlights: [
30
+ "Everything in Pro",
31
+ "Unlimited scale",
32
+ "Multi-agent orchestration",
33
+ "SSO & SAML",
34
+ "Advanced RBAC & audit exports",
35
+ "Your own MCP servers & LLM endpoints",
36
+ "Configurable quotas",
37
+ "Custom SLA & dedicated CSM"
38
+ ],
39
+ summary: "Multi-agent orchestration · SSO · Custom SLA",
40
+ contactEmail: "contact@botmanor.com"
41
+ },
14
42
  fluidgrids: {
15
43
  highlights: [
16
44
  "Custom workflow and run volumes",
@@ -25,6 +53,46 @@ var e = {
25
53
  summary: "Custom volumes · SSO/SCIM · OEM builder SDK",
26
54
  contactEmail: "contact@fluidgrids.com"
27
55
  },
56
+ hookmovies: {
57
+ highlights: [
58
+ "Everything in higher consumer tiers",
59
+ "Studio / channel production-ops",
60
+ "FAST channels & live events",
61
+ "Advanced audience & revenue analytics",
62
+ "Rights & distribution manager",
63
+ "Multi-seat with org-wide RBAC",
64
+ "API, embeddable player & webhooks",
65
+ "SSO & configurable rev-share"
66
+ ],
67
+ summary: "Studio production-ops · FAST channels · SSO",
68
+ contactEmail: "contact@hookmovies.com"
69
+ },
70
+ labsofscience: {
71
+ highlights: [
72
+ "Contracted per-seat pricing that declines with volume",
73
+ "Academic and commercial rate cards",
74
+ "Unlimited experiments, runs, notebook entries and datasets",
75
+ "Annual contract, minimum $25,000/yr",
76
+ "Dedicated tenant or self-hosting (priced separately)",
77
+ "Data residency and customer-managed keys (priced separately)"
78
+ ],
79
+ summary: "Contracted seats · Academic & commercial rates · Deployment add-ons",
80
+ contactEmail: "contact@labsofscience.com"
81
+ },
82
+ subscriberbot: {
83
+ highlights: [
84
+ "Everything in Pro",
85
+ "Enterprise Control Tower",
86
+ "License & usage analytics",
87
+ "Contract Intelligence",
88
+ "Risk & shadow-IT detection",
89
+ "SSO, SCIM & advanced RBAC",
90
+ "Immutable audit log",
91
+ "Dedicated support & SLA"
92
+ ],
93
+ summary: "Control Tower · SSO/SCIM · Dedicated support & SLA",
94
+ contactEmail: "contact@subscriberbot.com"
95
+ },
28
96
  vibecontrols: {
29
97
  highlights: [
30
98
  "Everything in Pro",
@@ -37,14 +105,16 @@ var e = {
37
105
  summary: "Dedicated tenant · SSO/SCIM · Custom SLA",
38
106
  contactEmail: "contact@vibecontrols.com"
39
107
  }
40
- };
41
- function n(n) {
42
- return n ? t[n.toLowerCase()] ?? e : e;
108
+ }, n = { botlit: "botmanor" };
109
+ function r(r) {
110
+ if (!r) return e;
111
+ let i = r.trim().toLowerCase(), a = Object.hasOwn(n, i) ? n[i] : i;
112
+ return Object.hasOwn(t, a) ? t[a] : e;
43
113
  }
44
- function r(e) {
114
+ function i(e) {
45
115
  return `mailto:${e.contactEmail}?subject=Enterprise%20Plan%20Inquiry`;
46
116
  }
47
117
  //#endregion
48
- export { r as enterpriseMailto, n as getEnterpriseOffer };
118
+ export { i as enterpriseMailto, r as getEnterpriseOffer };
49
119
 
50
120
  //# sourceMappingURL=enterprise.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"enterprise.js","names":[],"sources":["../../../../../src/billing/modules/plans/constants/enterprise.ts"],"sourcesContent":["/**\n * Enterprise \"Contact Us\" copy for the plans browse page.\n *\n * Enterprise is contact-sales in every product and has no backend plan\n * record, so its card is static. The highlights are product-specific and must\n * match the product website's Enterprise card, so a FluidGrids shell never\n * shows VibeControls wording (and vice versa). Products without curated copy\n * fall back to product-neutral highlights.\n */\n\nexport interface EnterpriseOffer {\n /** Bullet points on the grid card, in display order. */\n highlights: readonly string[];\n /** One-line summary for the compact list row. */\n summary: string;\n /** Sales inbox the Contact Us button opens. */\n contactEmail: string;\n}\n\nconst DEFAULT_ENTERPRISE_OFFER: EnterpriseOffer = {\n highlights: [\n 'Custom usage volumes',\n 'SSO / SCIM provisioning',\n 'Custom SLA & support contract',\n 'Volume-based custom pricing',\n 'Audit log with custom retention',\n 'Priority onboarding & migration',\n ],\n summary: 'SSO/SCIM · Custom SLA · Custom pricing',\n contactEmail: 'contact@burdenoff.com',\n};\n\n/** Keyed by billing product id. Mirrors each product website's Enterprise card. */\nconst ENTERPRISE_OFFERS: Readonly<Record<string, EnterpriseOffer>> = {\n // fluidgrids-website src/config/product.ts (plan key \"enterprise\")\n fluidgrids: {\n highlights: [\n 'Custom workflow and run volumes',\n 'Custom storage and retention',\n 'SSO & SAML',\n 'SCIM provisioning',\n '99.95%+ uptime SLA',\n 'Dedicated support with SLA',\n 'OEM embedding of the builder SDK',\n 'Data residency options',\n ],\n summary: 'Custom volumes · SSO/SCIM · OEM builder SDK',\n contactEmail: 'contact@fluidgrids.com',\n },\n // vibecontrols-website src/config/product.ts VC_ENTERPRISE_FEATURES\n vibecontrols: {\n highlights: [\n 'Everything in Pro',\n 'Unlimited agents & seats',\n 'Production SSO/SCIM · SIEM streaming',\n 'Data residency · customer-managed keys',\n 'Dedicated tenant / self-host',\n 'Custom SLA · dedicated support',\n ],\n summary: 'Dedicated tenant · SSO/SCIM · Custom SLA',\n contactEmail: 'contact@vibecontrols.com',\n },\n};\n\nexport function getEnterpriseOffer(productId: string | null | undefined): EnterpriseOffer {\n if (!productId) return DEFAULT_ENTERPRISE_OFFER;\n return ENTERPRISE_OFFERS[productId.toLowerCase()] ?? DEFAULT_ENTERPRISE_OFFER;\n}\n\nexport function enterpriseMailto(offer: EnterpriseOffer): string {\n return `mailto:${offer.contactEmail}?subject=${encodeURIComponent('Enterprise Plan Inquiry')}`;\n}\n"],"mappings":";AAmBA,IAAM,IAA4C;CAChD,YAAY;EACV;EACA;EACA;EACA;EACA;EACA;CACF;CACA,SAAS;CACT,cAAc;AAChB,GAGM,IAA+D;CAEnE,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,cAAc;EACZ,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;AACF;AAEA,SAAgB,EAAmB,GAAuD;CAExF,OADK,IACE,EAAkB,EAAU,YAAY,MAAM,IAD9B;AAEzB;AAEA,SAAgB,EAAiB,GAAgC;CAC/D,OAAO,UAAU,EAAM,aAAa;AACtC"}
1
+ {"version":3,"file":"enterprise.js","names":[],"sources":["../../../../../src/billing/modules/plans/constants/enterprise.ts"],"sourcesContent":["/**\n * Enterprise \"Contact Us\" copy for the plans browse page.\n *\n * Enterprise is contact-sales in every product and has no backend plan\n * record, so its card is static. The highlights are product-specific and must\n * match the product website's Enterprise card, so a FluidGrids shell never\n * shows VibeControls wording (and vice versa). Products without curated copy\n * fall back to product-neutral highlights.\n */\n\nexport interface EnterpriseOffer {\n /** Bullet points on the grid card, in display order. */\n highlights: readonly string[];\n /** One-line summary for the compact list row. */\n summary: string;\n /** Sales inbox the Contact Us button opens. */\n contactEmail: string;\n}\n\n/**\n * Fallback for products without curated copy. It deliberately makes no\n * capability claims (no SSO, SLA, residency, dedicated infra, ...): those\n * differ per product, and an unverified claim on a page where customers pay\n * is misleading. It only describes the commercial shape of a sales contract.\n */\nconst DEFAULT_ENTERPRISE_OFFER: EnterpriseOffer = {\n highlights: [\n 'Pricing tailored to your organization',\n 'Volume and multi-year terms',\n 'Contract and invoicing that fit your procurement process',\n 'Talk to our team about your requirements',\n ],\n summary: 'Custom contract · Volume pricing',\n contactEmail: 'contact@burdenoff.com',\n};\n\n/** Keyed by billing product id. Mirrors each product website's Enterprise card. */\nconst ENTERPRISE_OFFERS: Readonly<Record<string, EnterpriseOffer>> = {\n // bigconsole-website src/config/product.ts (tier \"enterprise\")\n bigconsole: {\n highlights: [\n 'Everything in Professional',\n 'Dedicated support',\n 'SLA guarantee',\n 'Unlimited storage',\n 'Advanced security',\n 'Built-in integrations',\n 'SSO & SAML',\n 'Audit logs',\n 'SCIM user provisioning',\n 'Dedicated account manager',\n ],\n summary: 'SSO/SCIM · SLA guarantee · Dedicated account manager',\n contactEmail: 'contact@bigconsole.com',\n },\n // botmanor-website src/config/product.ts (tier \"enterprise\")\n botmanor: {\n highlights: [\n 'Everything in Pro',\n 'Unlimited scale',\n 'Multi-agent orchestration',\n 'SSO & SAML',\n 'Advanced RBAC & audit exports',\n 'Your own MCP servers & LLM endpoints',\n 'Configurable quotas',\n 'Custom SLA & dedicated CSM',\n ],\n summary: 'Multi-agent orchestration · SSO · Custom SLA',\n contactEmail: 'contact@botmanor.com',\n },\n // fluidgrids-website src/config/product.ts (plan key \"enterprise\")\n fluidgrids: {\n highlights: [\n 'Custom workflow and run volumes',\n 'Custom storage and retention',\n 'SSO & SAML',\n 'SCIM provisioning',\n '99.95%+ uptime SLA',\n 'Dedicated support with SLA',\n 'OEM embedding of the builder SDK',\n 'Data residency options',\n ],\n summary: 'Custom volumes · SSO/SCIM · OEM builder SDK',\n contactEmail: 'contact@fluidgrids.com',\n },\n // hookmovies-website src/config/product.ts (tier \"enterprise\")\n hookmovies: {\n highlights: [\n 'Everything in higher consumer tiers',\n 'Studio / channel production-ops',\n 'FAST channels & live events',\n 'Advanced audience & revenue analytics',\n 'Rights & distribution manager',\n 'Multi-seat with org-wide RBAC',\n 'API, embeddable player & webhooks',\n 'SSO & configurable rev-share',\n ],\n summary: 'Studio production-ops · FAST channels · SSO',\n contactEmail: 'contact@hookmovies.com',\n },\n // labsofscience-specs business/pricing/internal/01-tier-plans.md §7.1 (contracted\n // Enterprise tier). Deployment and compliance obligations are separately priced\n // line items there, so they are labelled as add-ons rather than included.\n labsofscience: {\n highlights: [\n 'Contracted per-seat pricing that declines with volume',\n 'Academic and commercial rate cards',\n 'Unlimited experiments, runs, notebook entries and datasets',\n 'Annual contract, minimum $25,000/yr',\n 'Dedicated tenant or self-hosting (priced separately)',\n 'Data residency and customer-managed keys (priced separately)',\n ],\n summary: 'Contracted seats · Academic & commercial rates · Deployment add-ons',\n contactEmail: 'contact@labsofscience.com',\n },\n // subscriberbot-website src/config/product.ts (tier \"enterprise\")\n subscriberbot: {\n highlights: [\n 'Everything in Pro',\n 'Enterprise Control Tower',\n 'License & usage analytics',\n 'Contract Intelligence',\n 'Risk & shadow-IT detection',\n 'SSO, SCIM & advanced RBAC',\n 'Immutable audit log',\n 'Dedicated support & SLA',\n ],\n summary: 'Control Tower · SSO/SCIM · Dedicated support & SLA',\n contactEmail: 'contact@subscriberbot.com',\n },\n // vibecontrols-website src/config/product.ts VC_ENTERPRISE_FEATURES\n vibecontrols: {\n highlights: [\n 'Everything in Pro',\n 'Unlimited agents & seats',\n 'Production SSO/SCIM · SIEM streaming',\n 'Data residency · customer-managed keys',\n 'Dedicated tenant / self-host',\n 'Custom SLA · dedicated support',\n ],\n summary: 'Dedicated tenant · SSO/SCIM · Custom SLA',\n contactEmail: 'contact@vibecontrols.com',\n },\n};\n\n/** Legacy product ids still accepted on input (never emitted). */\nconst PRODUCT_ID_ALIASES: Readonly<Record<string, string>> = {\n botlit: 'botmanor',\n};\n\nexport function getEnterpriseOffer(productId: string | null | undefined): EnterpriseOffer {\n if (!productId) return DEFAULT_ENTERPRISE_OFFER;\n const raw = productId.trim().toLowerCase();\n // Own-property checks so ids such as \"constructor\" never resolve to Object.prototype members.\n const key = Object.hasOwn(PRODUCT_ID_ALIASES, raw) ? PRODUCT_ID_ALIASES[raw] : raw;\n return Object.hasOwn(ENTERPRISE_OFFERS, key) ? ENTERPRISE_OFFERS[key] : DEFAULT_ENTERPRISE_OFFER;\n}\n\nexport function enterpriseMailto(offer: EnterpriseOffer): string {\n return `mailto:${offer.contactEmail}?subject=${encodeURIComponent('Enterprise Plan Inquiry')}`;\n}\n"],"mappings":";AAyBA,IAAM,IAA4C;CAChD,YAAY;EACV;EACA;EACA;EACA;CACF;CACA,SAAS;CACT,cAAc;AAChB,GAGM,IAA+D;CAEnE,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,UAAU;EACR,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAIA,eAAe;EACb,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,eAAe;EACb,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,cAAc;EACZ,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;AACF,GAGM,IAAuD,EAC3D,QAAQ,WACV;AAEA,SAAgB,EAAmB,GAAuD;CACxF,IAAI,CAAC,GAAW,OAAO;CACvB,IAAM,IAAM,EAAU,KAAK,EAAE,YAAY,GAEnC,IAAM,OAAO,OAAO,GAAoB,CAAG,IAAI,EAAmB,KAAO;CAC/E,OAAO,OAAO,OAAO,GAAmB,CAAG,IAAI,EAAkB,KAAO;AAC1E;AAEA,SAAgB,EAAiB,GAAgC;CAC/D,OAAO,UAAU,EAAM,aAAa;AACtC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.1026.3",
3
+ "version": "2026.1027.2",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@apollo/client": "^4.1.4",
47
- "@burdenoff/fe-libs": "2026.1012.1",
47
+ "@burdenoff/fe-libs": "2026.1027.13",
48
48
  "@tanstack/react-query": "^5.90.16",
49
49
  "clsx": "^2.1.1",
50
50
  "graphql": "^16.10.0",