@burdenoff/microfe-billing 2026.821.1 → 2026.825.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.
Files changed (145) hide show
  1. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  2. package/dist/billing/BillingRoot.js.map +1 -1
  3. package/dist/billing/BillingUserRoutes.js.map +1 -1
  4. package/dist/billing/hooks/useBillingAccountSelection.js.map +1 -1
  5. package/dist/billing/hooks/useBillingEventEmitter.js.map +1 -1
  6. package/dist/billing/hooks/useBillingNavigate.js.map +1 -1
  7. package/dist/billing/hooks/useBillingPermissions.js.map +1 -1
  8. package/dist/billing/hooks/useDefaultBillingCurrency.js.map +1 -1
  9. package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
  10. package/dist/billing/modules/addons/hooks/useAddons.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  12. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  14. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  15. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  16. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  17. package/dist/billing/modules/addons/store/addonCartStore.js.map +1 -1
  18. package/dist/billing/modules/affiliate/api.js.map +1 -1
  19. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +107 -107
  20. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  21. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +80 -81
  22. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/hooks/useInvoices.js.map +1 -1
  24. package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
  25. package/dist/billing/modules/billing/hooks/useTransactions.js.map +1 -1
  26. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  27. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  28. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  29. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  30. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  31. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  32. package/dist/billing/modules/billing/pages/UserRefundsPage.js +127 -127
  33. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  34. package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
  35. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  36. package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
  37. package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
  38. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  39. package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
  40. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  41. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
  42. package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
  43. package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
  44. package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
  45. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
  46. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  47. package/dist/billing/modules/checkout/hooks/useCheckout.js +0 -1
  48. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  49. package/dist/billing/modules/checkout/hooks/useNativeCheckout.js.map +1 -1
  50. package/dist/billing/modules/checkout/hooks/usePaymentProviders.js.map +1 -1
  51. package/dist/billing/modules/checkout/hooks/usePaymentStatusPolling.js.map +1 -1
  52. package/dist/billing/modules/checkout/hooks/useStoreShipping.js.map +1 -1
  53. package/dist/billing/modules/checkout/pages/CheckoutPage.js +412 -395
  54. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  55. package/dist/billing/modules/checkout/utils/storeOrder.js.map +1 -1
  56. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  57. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  58. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  59. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  60. package/dist/billing/modules/credits/hooks/useCredits.js.map +1 -1
  61. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  62. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  63. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  64. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  65. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
  66. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  67. package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
  68. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  69. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
  70. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  71. package/dist/billing/modules/dashboard/hooks/useBillingOverviewData.js.map +1 -1
  72. package/dist/billing/modules/dashboard/hooks/useDashboard.js.map +1 -1
  73. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  74. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  75. package/dist/billing/modules/earnings/api.js.map +1 -1
  76. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +145 -145
  77. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
  78. package/dist/billing/modules/earnings/pages/EarningsPage.js +133 -133
  79. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  80. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +7 -9
  81. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  82. package/dist/billing/modules/earnings/pages/PayoutsPage.js +95 -95
  83. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  84. package/dist/billing/modules/plans/constants/featuredQuotas.js.map +1 -1
  85. package/dist/billing/modules/plans/hooks/usePlans.js.map +1 -1
  86. package/dist/billing/modules/plans/hooks/useQuotas.js.map +1 -1
  87. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  88. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  89. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  90. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  91. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  92. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  93. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  94. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  95. package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
  96. package/dist/billing/modules/quota/hooks/useQuotaTemplates.js.map +1 -1
  97. package/dist/billing/modules/quota/pages/QuotaAssignFreePage.js.map +1 -1
  98. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  99. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  100. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  101. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  102. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  103. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  104. package/dist/billing/modules/store/pages/StoreCheckoutPage.js +5 -6
  105. package/dist/billing/modules/store/pages/StoreCheckoutPage.js.map +1 -1
  106. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  107. package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
  108. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  109. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  110. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  111. package/dist/billing/modules/usage/hooks/useUsage.js +14 -9
  112. package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
  113. package/dist/billing/modules/usage/pages/UsagePage.js +481 -453
  114. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  115. package/dist/billing/providers/BillingProvider.js.map +1 -1
  116. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  117. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  118. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  119. package/dist/billing/shared/components/EmptyState.js.map +1 -1
  120. package/dist/billing/shared/components/PageHeader.js.map +1 -1
  121. package/dist/billing/shared/components/ServerError.js.map +1 -1
  122. package/dist/billing/shared/components/StatCard.js.map +1 -1
  123. package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -1
  124. package/dist/billing/shared/types/graphql.js.map +1 -1
  125. package/dist/billing/shared/ui/Card.js.map +1 -1
  126. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  127. package/dist/billing/shared/ui/ProgressBar.js.map +1 -1
  128. package/dist/billing/shared/ui/StatusPill.js.map +1 -1
  129. package/dist/billing/shared/utils/directBillingGraphql.js.map +1 -1
  130. package/dist/billing/shared/utils/error.js.map +1 -1
  131. package/dist/billing/shared/utils/format.js.map +1 -1
  132. package/dist/billing/shared/utils/gatewayGraphql.js.map +1 -1
  133. package/dist/billing/shared/utils/navigation.js.map +1 -1
  134. package/dist/billing/shared/utils/paymentTheme.js.map +1 -1
  135. package/dist/billing/shared/utils/planFeatureQuantity.js.map +1 -1
  136. package/dist/billing/shared/utils/status.js.map +1 -1
  137. package/dist/billing/shared/utils/tokens.js.map +1 -1
  138. package/dist/generated/global-operations.js +5 -1
  139. package/dist/generated/global-operations.js.map +1 -1
  140. package/dist/generated/global-types.js.map +1 -1
  141. package/dist/generated/wspace-operations.js +1 -1
  142. package/dist/generated/wspace-operations.js.map +1 -1
  143. package/dist/node_modules/@capacitor/core/dist/index.js +1 -1
  144. package/dist/node_modules/@capacitor/core/dist/index.js.map +1 -1
  145. package/package.json +13 -10
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingCartDrawer.js","names":[],"sources":["../../../../../src/billing/modules/addons/components/FloatingCartDrawer.tsx"],"sourcesContent":["/**\n * Floating Cart Drawer Component\n * Displays the addon cart as a floating button and drawer overlay\n * Reusable across pages that need cart functionality\n */\n\nimport { type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ShoppingCart, X, Minus, Plus, Trash2 } from 'lucide-react';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// Format currency helper\nconst formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(amount);\n};\n\n// Format plan duration helper\nconst formatPlanDuration = (duration: string) => {\n return duration === 'yearly' ? 'year' : 'month';\n};\n\nexport const FloatingCartDrawer: 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 {\n items,\n isCartOpen,\n toggleCart,\n closeCart,\n removeFromCart,\n updateQuantity,\n clearCart,\n getCartTotal,\n } = useAddonCartStore();\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n const cartTotal = getCartTotal();\n\n if (items.length === 0 && !isCartOpen) return null;\n\n const handleCheckout = () => {\n closeCart();\n // Navigate to checkout - same path used in AddonsListPage\n navigateTo('/checkout/addons');\n };\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-elevation-3 hover:bg-action-primary-bgHover hover:shadow-elevation-4 transition-all duration-200\"\n aria-label={`Open addon cart with ${itemCount} item${itemCount !== 1 ? 's' : ''}`}\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-action-primary-text/20 rounded-full text-sm\">\n {formatCurrency(cartTotal, items[0]?.addon.currency || 'USD')}\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\n className=\"relative w-full max-w-md max-h-[80vh] bg-bg-surface border border-border-subtle rounded-t-card sm:rounded-card shadow-[var(--shadow-elevation-4)] overflow-hidden\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"addon-cart-title\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-subtle bg-bg-sunken\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"size-5 text-text-primary\" />\n <h3 id=\"addon-cart-title\" 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 aria-label=\"Close addon cart\"\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-secondary\">\n {formatCurrency(addon.price, addon.currency)} /{' '}\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 aria-label={`Decrease ${addon.name} quantity`}\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 aria-label={`Increase ${addon.name} quantity`}\n >\n <Plus className=\"size-3\" />\n </button>\n </div>\n <div className=\"text-right\">\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(addon.price * quantity, addon.currency)}\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 aria-label={`Remove ${addon.name} from cart`}\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\">\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 {formatCurrency(cartTotal, items[0]?.addon.currency || 'USD')}\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={handleCheckout}\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":"FloatingCartDrawer.js","names":[],"sources":["../../../../../src/billing/modules/addons/components/FloatingCartDrawer.tsx"],"sourcesContent":["/**\n * Floating Cart Drawer Component\n * Displays the addon cart as a floating button and drawer overlay\n * Reusable across pages that need cart functionality\n */\n\nimport { type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { ShoppingCart, X, Minus, Plus, Trash2 } from 'lucide-react';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// Format currency helper\nconst formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(amount);\n};\n\n// Format plan duration helper\nconst formatPlanDuration = (duration: string) => {\n return duration === 'yearly' ? 'year' : 'month';\n};\n\nexport const FloatingCartDrawer: 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 {\n items,\n isCartOpen,\n toggleCart,\n closeCart,\n removeFromCart,\n updateQuantity,\n clearCart,\n getCartTotal,\n } = useAddonCartStore();\n\n const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);\n const cartTotal = getCartTotal();\n\n if (items.length === 0 && !isCartOpen) return null;\n\n const handleCheckout = () => {\n closeCart();\n // Navigate to checkout - same path used in AddonsListPage\n navigateTo('/checkout/addons');\n };\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-elevation-3 hover:bg-action-primary-bgHover hover:shadow-elevation-4 transition-all duration-200\"\n aria-label={`Open addon cart with ${itemCount} item${itemCount !== 1 ? 's' : ''}`}\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-action-primary-text/20 rounded-full text-sm\">\n {formatCurrency(cartTotal, items[0]?.addon.currency || 'USD')}\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\n className=\"relative w-full max-w-md max-h-[80vh] bg-bg-surface border border-border-subtle rounded-t-card sm:rounded-card shadow-[var(--shadow-elevation-4)] overflow-hidden\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"addon-cart-title\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-subtle bg-bg-sunken\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"size-5 text-text-primary\" />\n <h3 id=\"addon-cart-title\" 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 aria-label=\"Close addon cart\"\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-secondary\">\n {formatCurrency(addon.price, addon.currency)} /{' '}\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 aria-label={`Decrease ${addon.name} quantity`}\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 aria-label={`Increase ${addon.name} quantity`}\n >\n <Plus className=\"size-3\" />\n </button>\n </div>\n <div className=\"text-right\">\n <p className=\"font-medium text-text-primary\">\n {formatCurrency(addon.price * quantity, addon.currency)}\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 aria-label={`Remove ${addon.name} from cart`}\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\">\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 {formatCurrency(cartTotal, items[0]?.addon.currency || 'USD')}\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={handleCheckout}\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 +1 @@
1
- {"version":3,"file":"useAddons.js","names":[],"sources":["../../../../../src/billing/modules/addons/hooks/useAddons.ts"],"sourcesContent":["/**\n * Addons Module - useAddons Hook\n * Provides access to addon data and mutations\n */\n\nimport { useApolloClient } from '@apollo/client/react';\nimport { useCallback } from 'react';\nimport {\n useGetAllAddOnsQuery,\n useGetSelectedAddonsQuery,\n useCreateAddOnsMutation,\n useUpdateAddOnDetailsMutation,\n useToggleAddOnMutation,\n GetAllAddOnsDocument,\n} from '../../../../generated/global-operations';\nimport type { Addon, CreateAddonInput, UpdateAddonInput } from '../../../shared/types';\n\ninterface UseAddonsOptions {\n onlyActive?: boolean;\n /** Filter by product ID — pushed to backend query, not client-side. */\n productId?: string;\n /**\n * When false, free addons (price = 0) are excluded at the backend.\n * Use false on user-facing browse pages; omit or set true for admin pages.\n */\n includeFree?: boolean;\n}\n\ninterface UseAddonsResult {\n addons: Addon[];\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseSelectedAddonsResult {\n addons: Addon[];\n isLoading: boolean;\n error: Error | undefined;\n}\n\ninterface UseAddonMutationsResult {\n createAddon: (input: CreateAddonInput) => Promise<boolean>;\n updateAddon: (input: UpdateAddonInput) => Promise<boolean>;\n toggleAddon: (addonId: string, status: boolean) => Promise<boolean>;\n isCreating: boolean;\n isUpdating: boolean;\n isToggling: boolean;\n}\n\n/**\n * Hook for fetching all addons\n */\nexport function useAddons(options: UseAddonsOptions = {}): UseAddonsResult {\n const { onlyActive = false, productId, includeFree } = options;\n\n const filter = {\n ...(productId !== undefined ? { productId } : {}),\n ...(includeFree !== undefined ? { includeFree } : {}),\n };\n\n const { data, loading, error, refetch } = useGetAllAddOnsQuery({\n variables: { filter: Object.keys(filter).length > 0 ? filter : undefined },\n fetchPolicy: 'cache-and-network',\n });\n\n let addons: Addon[] = data?.getAllAddOns || [];\n\n if (onlyActive) {\n addons = addons.filter((addon: Addon) => addon.isActive);\n }\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n addons,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\ninterface UseAddonResult {\n addon: Addon | null;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook for fetching a single addon by ID\n */\nexport function useAddon(addonId: string | undefined): UseAddonResult {\n const { data, loading, error, refetch } = useGetSelectedAddonsQuery({\n variables: { addonIDs: addonId ? [addonId] : [] },\n skip: !addonId,\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n addon: data?.getSelectedAddons?.[0] || null,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for fetching specific addons by IDs\n */\nexport function useSelectedAddons(addonIds: string[]): UseSelectedAddonsResult {\n const { data, loading, error } = useGetSelectedAddonsQuery({\n variables: { addonIDs: addonIds },\n skip: addonIds.length === 0,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n addons: data?.getSelectedAddons || [],\n isLoading: loading,\n error: error as Error | undefined,\n };\n}\n\n/**\n * Hook for addon mutations (create, update, toggle)\n */\nexport function useAddonMutations(): UseAddonMutationsResult {\n const client = useApolloClient();\n\n const [createAddonMutation, { loading: isCreating }] = useCreateAddOnsMutation();\n const [updateAddonMutation, { loading: isUpdating }] = useUpdateAddOnDetailsMutation();\n const [toggleAddonMutation, { loading: isToggling }] = useToggleAddOnMutation();\n\n const createAddon = useCallback(\n async (input: CreateAddonInput): Promise<boolean> => {\n try {\n const { data } = await createAddonMutation({\n variables: {\n input: input as unknown as Parameters<\n typeof createAddonMutation\n >[0]['variables']['input'],\n },\n });\n // Refetch addons list after creation\n await client.refetchQueries({ include: [GetAllAddOnsDocument] });\n return data?.createAddOns ?? false;\n } catch (error) {\n console.error('Failed to create addon:', error);\n throw error;\n }\n },\n [createAddonMutation, client]\n );\n\n const updateAddon = useCallback(\n async (input: UpdateAddonInput): Promise<boolean> => {\n try {\n const { data } = await updateAddonMutation({\n variables: {\n input: input as unknown as Parameters<\n typeof updateAddonMutation\n >[0]['variables']['input'],\n },\n });\n // Refetch addons list after update\n await client.refetchQueries({ include: [GetAllAddOnsDocument] });\n return data?.updateAddOnDetails ?? false;\n } catch (error) {\n console.error('Failed to update addon:', error);\n throw error;\n }\n },\n [updateAddonMutation, client]\n );\n\n const toggleAddon = useCallback(\n async (addonId: string, status: boolean): Promise<boolean> => {\n try {\n const { data } = await toggleAddonMutation({\n variables: { id: addonId, status },\n });\n // Refetch addons list after toggle\n await client.refetchQueries({ include: [GetAllAddOnsDocument] });\n return data?.toggleAddOn ?? false;\n } catch (error) {\n console.error('Failed to toggle addon:', error);\n throw error;\n }\n },\n [toggleAddonMutation, client]\n );\n\n return {\n createAddon,\n updateAddon,\n toggleAddon,\n isCreating,\n isUpdating,\n isToggling,\n };\n}\n"],"mappings":";;;;AAqDA,SAAgB,EAAU,IAA4B,EAAE,EAAmB;CACzE,IAAM,EAAE,gBAAa,IAAO,cAAW,mBAAgB,GAEjD,IAAS;EACb,GAAI,MAAc,KAAA,IAA4B,EAAE,GAAlB,EAAE,cAAW;EAC3C,GAAI,MAAgB,KAAA,IAA8B,EAAE,GAApB,EAAE,gBAAa;EAChD,EAEK,EAAE,SAAM,YAAS,UAAO,eAAY,EAAqB;EAC7D,WAAW,EAAE,QAAQ,OAAO,KAAK,EAAO,CAAC,SAAS,IAAI,IAAS,KAAA,GAAW;EAC1E,aAAa;EACd,CAAC,EAEE,IAAkB,GAAM,gBAAgB,EAAE;AAE9C,CAAI,MACF,IAAS,EAAO,QAAQ,MAAiB,EAAM,SAAS;CAG1D,IAAM,IAAgB,EAAY,YAAY;AAC5C,QAAM,GAAS;IACd,CAAC,EAAQ,CAAC;AAEb,QAAO;EACL;EACA,WAAW;EACJ;EACP,SAAS;EACV;;AAaH,SAAgB,EAAS,GAA6C;CACpE,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAA0B;EAClE,WAAW,EAAE,UAAU,IAAU,CAAC,EAAQ,GAAG,EAAE,EAAE;EACjD,MAAM,CAAC;EACP,aAAa;EACd,CAAC,EAEI,IAAgB,EAAY,YAAY;AAC5C,QAAM,GAAS;IACd,CAAC,EAAQ,CAAC;AAEb,QAAO;EACL,OAAO,GAAM,oBAAoB,MAAM;EACvC,WAAW;EACJ;EACP,SAAS;EACV;;AAuBH,SAAgB,IAA6C;CAC3D,IAAM,IAAS,GAAiB,EAE1B,CAAC,GAAqB,EAAE,SAAS,OAAgB,GAAyB,EAC1E,CAAC,GAAqB,EAAE,SAAS,OAAgB,GAA+B,EAChF,CAAC,GAAqB,EAAE,SAAS,OAAgB,GAAwB;AA6D/E,QAAO;EACL,aA5DkB,EAClB,OAAO,MAA8C;AACnD,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW,EACF,UAGR,EACF,CAAC;AAGF,WADA,MAAM,EAAO,eAAe,EAAE,SAAS,CAAC,EAAqB,EAAE,CAAC,EACzD,GAAM,gBAAgB;YACtB,GAAO;AAEd,UADA,QAAQ,MAAM,2BAA2B,EAAM,EACzC;;KAGV,CAAC,GAAqB,EAAO,CAC9B;EA0CC,aAxCkB,EAClB,OAAO,MAA8C;AACnD,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW,EACF,UAGR,EACF,CAAC;AAGF,WADA,MAAM,EAAO,eAAe,EAAE,SAAS,CAAC,EAAqB,EAAE,CAAC,EACzD,GAAM,sBAAsB;YAC5B,GAAO;AAEd,UADA,QAAQ,MAAM,2BAA2B,EAAM,EACzC;;KAGV,CAAC,GAAqB,EAAO,CAC9B;EAsBC,aApBkB,EAClB,OAAO,GAAiB,MAAsC;AAC5D,OAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW;KAAE,IAAI;KAAS;KAAQ,EACnC,CAAC;AAGF,WADA,MAAM,EAAO,eAAe,EAAE,SAAS,CAAC,EAAqB,EAAE,CAAC,EACzD,GAAM,eAAe;YACrB,GAAO;AAEd,UADA,QAAQ,MAAM,2BAA2B,EAAM,EACzC;;KAGV,CAAC,GAAqB,EAAO,CAC9B;EAMC;EACA;EACA;EACD"}
1
+ {"version":3,"file":"useAddons.js","names":[],"sources":["../../../../../src/billing/modules/addons/hooks/useAddons.ts"],"sourcesContent":["/**\n * Addons Module - useAddons Hook\n * Provides access to addon data and mutations\n */\n\nimport { useApolloClient } from '@apollo/client/react';\nimport { useCallback } from 'react';\nimport {\n useGetAllAddOnsQuery,\n useGetSelectedAddonsQuery,\n useCreateAddOnsMutation,\n useUpdateAddOnDetailsMutation,\n useToggleAddOnMutation,\n GetAllAddOnsDocument,\n} from '../../../../generated/global-operations';\nimport type { Addon, CreateAddonInput, UpdateAddonInput } from '../../../shared/types';\n\ninterface UseAddonsOptions {\n onlyActive?: boolean;\n /** Filter by product ID — pushed to backend query, not client-side. */\n productId?: string;\n /**\n * When false, free addons (price = 0) are excluded at the backend.\n * Use false on user-facing browse pages; omit or set true for admin pages.\n */\n includeFree?: boolean;\n}\n\ninterface UseAddonsResult {\n addons: Addon[];\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseSelectedAddonsResult {\n addons: Addon[];\n isLoading: boolean;\n error: Error | undefined;\n}\n\ninterface UseAddonMutationsResult {\n createAddon: (input: CreateAddonInput) => Promise<boolean>;\n updateAddon: (input: UpdateAddonInput) => Promise<boolean>;\n toggleAddon: (addonId: string, status: boolean) => Promise<boolean>;\n isCreating: boolean;\n isUpdating: boolean;\n isToggling: boolean;\n}\n\n/**\n * Hook for fetching all addons\n */\nexport function useAddons(options: UseAddonsOptions = {}): UseAddonsResult {\n const { onlyActive = false, productId, includeFree } = options;\n\n const filter = {\n ...(productId !== undefined ? { productId } : {}),\n ...(includeFree !== undefined ? { includeFree } : {}),\n };\n\n const { data, loading, error, refetch } = useGetAllAddOnsQuery({\n variables: { filter: Object.keys(filter).length > 0 ? filter : undefined },\n fetchPolicy: 'cache-and-network',\n });\n\n let addons: Addon[] = data?.getAllAddOns || [];\n\n if (onlyActive) {\n addons = addons.filter((addon: Addon) => addon.isActive);\n }\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n addons,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\ninterface UseAddonResult {\n addon: Addon | null;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\n/**\n * Hook for fetching a single addon by ID\n */\nexport function useAddon(addonId: string | undefined): UseAddonResult {\n const { data, loading, error, refetch } = useGetSelectedAddonsQuery({\n variables: { addonIDs: addonId ? [addonId] : [] },\n skip: !addonId,\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n addon: data?.getSelectedAddons?.[0] || null,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for fetching specific addons by IDs\n */\nexport function useSelectedAddons(addonIds: string[]): UseSelectedAddonsResult {\n const { data, loading, error } = useGetSelectedAddonsQuery({\n variables: { addonIDs: addonIds },\n skip: addonIds.length === 0,\n fetchPolicy: 'cache-and-network',\n });\n\n return {\n addons: data?.getSelectedAddons || [],\n isLoading: loading,\n error: error as Error | undefined,\n };\n}\n\n/**\n * Hook for addon mutations (create, update, toggle)\n */\nexport function useAddonMutations(): UseAddonMutationsResult {\n const client = useApolloClient();\n\n const [createAddonMutation, { loading: isCreating }] = useCreateAddOnsMutation();\n const [updateAddonMutation, { loading: isUpdating }] = useUpdateAddOnDetailsMutation();\n const [toggleAddonMutation, { loading: isToggling }] = useToggleAddOnMutation();\n\n const createAddon = useCallback(\n async (input: CreateAddonInput): Promise<boolean> => {\n try {\n const { data } = await createAddonMutation({\n variables: {\n input: input as unknown as Parameters<\n typeof createAddonMutation\n >[0]['variables']['input'],\n },\n });\n // Refetch addons list after creation\n await client.refetchQueries({ include: [GetAllAddOnsDocument] });\n return data?.createAddOns ?? false;\n } catch (error) {\n console.error('Failed to create addon:', error);\n throw error;\n }\n },\n [createAddonMutation, client]\n );\n\n const updateAddon = useCallback(\n async (input: UpdateAddonInput): Promise<boolean> => {\n try {\n const { data } = await updateAddonMutation({\n variables: {\n input: input as unknown as Parameters<\n typeof updateAddonMutation\n >[0]['variables']['input'],\n },\n });\n // Refetch addons list after update\n await client.refetchQueries({ include: [GetAllAddOnsDocument] });\n return data?.updateAddOnDetails ?? false;\n } catch (error) {\n console.error('Failed to update addon:', error);\n throw error;\n }\n },\n [updateAddonMutation, client]\n );\n\n const toggleAddon = useCallback(\n async (addonId: string, status: boolean): Promise<boolean> => {\n try {\n const { data } = await toggleAddonMutation({\n variables: { id: addonId, status },\n });\n // Refetch addons list after toggle\n await client.refetchQueries({ include: [GetAllAddOnsDocument] });\n return data?.toggleAddOn ?? false;\n } catch (error) {\n console.error('Failed to toggle addon:', error);\n throw error;\n }\n },\n [toggleAddonMutation, client]\n );\n\n return {\n createAddon,\n updateAddon,\n toggleAddon,\n isCreating,\n isUpdating,\n isToggling,\n };\n}\n"],"mappings":";;;;AAqDA,SAAgB,EAAU,IAA4B,CAAC,GAAoB;CACzE,IAAM,EAAE,gBAAa,IAAO,cAAW,mBAAgB,GAEjD,IAAS;EACb,GAAI,MAAc,KAAA,IAA4B,CAAC,IAAjB,EAAE,aAAU;EAC1C,GAAI,MAAgB,KAAA,IAA8B,CAAC,IAAnB,EAAE,eAAY;CAChD,GAEM,EAAE,SAAM,YAAS,UAAO,eAAY,EAAqB;EAC7D,WAAW,EAAE,QAAQ,OAAO,KAAK,CAAM,EAAE,SAAS,IAAI,IAAS,KAAA,EAAU;EACzE,aAAa;CACf,CAAC,GAEG,IAAkB,GAAM,gBAAgB,CAAC;CAE7C,AAAI,MACF,IAAS,EAAO,QAAQ,MAAiB,EAAM,QAAQ;CAGzD,IAAM,IAAgB,EAAY,YAAY;EAC5C,MAAM,EAAQ;CAChB,GAAG,CAAC,CAAO,CAAC;CAEZ,OAAO;EACL;EACA,WAAW;EACJ;EACP,SAAS;CACX;AACF;AAYA,SAAgB,EAAS,GAA6C;CACpE,IAAM,EAAE,SAAM,YAAS,UAAO,eAAY,EAA0B;EAClE,WAAW,EAAE,UAAU,IAAU,CAAC,CAAO,IAAI,CAAC,EAAE;EAChD,MAAM,CAAC;EACP,aAAa;CACf,CAAC,GAEK,IAAgB,EAAY,YAAY;EAC5C,MAAM,EAAQ;CAChB,GAAG,CAAC,CAAO,CAAC;CAEZ,OAAO;EACL,OAAO,GAAM,oBAAoB,MAAM;EACvC,WAAW;EACJ;EACP,SAAS;CACX;AACF;AAsBA,SAAgB,IAA6C;CAC3D,IAAM,IAAS,EAAgB,GAEzB,CAAC,GAAqB,EAAE,SAAS,OAAgB,EAAwB,GACzE,CAAC,GAAqB,EAAE,SAAS,OAAgB,EAA8B,GAC/E,CAAC,GAAqB,EAAE,SAAS,OAAgB,EAAuB;CA6D9E,OAAO;EACL,aA5DkB,EAClB,OAAO,MAA8C;GACnD,IAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW,EACF,SAGT,EACF,CAAC;IAGD,OADA,MAAM,EAAO,eAAe,EAAE,SAAS,CAAC,CAAoB,EAAE,CAAC,GACxD,GAAM,gBAAgB;GAC/B,SAAS,GAAO;IAEd,MADA,QAAQ,MAAM,2BAA2B,CAAK,GACxC;GACR;EACF,GACA,CAAC,GAAqB,CAAM,CA0C5B;EACA,aAxCkB,EAClB,OAAO,MAA8C;GACnD,IAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW,EACF,SAGT,EACF,CAAC;IAGD,OADA,MAAM,EAAO,eAAe,EAAE,SAAS,CAAC,CAAoB,EAAE,CAAC,GACxD,GAAM,sBAAsB;GACrC,SAAS,GAAO;IAEd,MADA,QAAQ,MAAM,2BAA2B,CAAK,GACxC;GACR;EACF,GACA,CAAC,GAAqB,CAAM,CAsB5B;EACA,aApBkB,EAClB,OAAO,GAAiB,MAAsC;GAC5D,IAAI;IACF,IAAM,EAAE,YAAS,MAAM,EAAoB,EACzC,WAAW;KAAE,IAAI;KAAS;IAAO,EACnC,CAAC;IAGD,OADA,MAAM,EAAO,eAAe,EAAE,SAAS,CAAC,CAAoB,EAAE,CAAC,GACxD,GAAM,eAAe;GAC9B,SAAS,GAAO;IAEd,MADA,QAAQ,MAAM,2BAA2B,CAAK,GACxC;GACR;EACF,GACA,CAAC,GAAqB,CAAM,CAM5B;EACA;EACA;EACA;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"AddonBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Browse Detail Page (User-facing)\n * View addon details and add to cart. No edit/toggle actions.\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport { ShoppingCart, Plus, Minus, Trash2 } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon } from '../hooks';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport { PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\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 limits = feature.quota.limits as { value?: number; type?: string } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\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 type: limitType,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const AddonBrowseDetailPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n const inCart = addon ? isInCart(addon.id) : false;\n const cartItem = addon ? getCartItem(addon.id) : undefined;\n const cartQuantity = cartItem?.quantity || 0;\n\n if (isLoading) {\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 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.addons.unableToLoadAddonDetails', 'Unable to load addon details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!addon) {\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.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.browseDetailPurpose',\n 'See exactly what this add-on costs and the extra capacity it gives you on top of your plan before you buy it. Add it to your cart from here when it fits your needs.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-4 border-b border-border-subtle\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(addon.price, addon.currency)}\n </span>\n <span className=\"text-lg text-text-secondary\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Quotas Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\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=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n Type: {quota.type}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-text-secondary\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Purchase Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.purchase', 'Purchase')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.price', 'Price')}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(addon.price, addon.currency)} /{' '}\n {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-text-primary capitalize\">\n {addon.duration}\n </span>\n </div>\n\n <div className=\"pt-2 space-y-2\">\n {inCart ? (\n <div className=\"space-y-2\">\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={() => updateQuantity(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={() => updateQuantity(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={() => removeFromCart(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 <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/addons')}\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\"\n >\n Proceed to Checkout\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(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 </div>\n </div>\n </div>\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,SAAS,EAAc,GAAmD;CACxE,IAAM,oBAAW,IAAI,KAA4B;AACjD,MAAK,IAAM,KAAW,GAAU;AAC9B,MAAI,CAAC,EAAQ,MAAO;EACpB,IAAM,IAAU,EAAQ,MAAM,IACxB,IAAS,EAAQ,MAAM,QACvB,IAAa,GAAQ,SAAS,GAC9B,IAAY,GAAQ,QAAQ,WAC5B,IAAW,EAAS,IAAI,EAAQ;AACtC,EAAI,IACF,EAAS,cAAc,IAEvB,EAAS,IAAI,GAAS;GACpB;GACA,MAAM,EAAQ,MAAM,QAAQ;GAC5B,YAAY;GACZ,MAAM;GACP,CAAC;;AAGN,QAAO,MAAM,KAAK,EAAS,QAAQ,CAAC;;AAGtC,IAAa,UAAkC;CAC7C,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,EAAE,eAAY,GAAgC,EAC9C,IAAa,GAAoB,EACjC,EAAE,UAAO,cAAW,UAAO,eAAY,EAAS,EAAQ,EACxD,EAAE,cAAW,mBAAgB,mBAAgB,aAAU,mBAAgB,GAAmB,EAE1F,IAAiB,QAAc,EAAc,GAAO,YAAY,EAAE,CAAC,EAAE,CAAC,GAAO,SAAS,CAAC,EACvF,IAAS,IAAQ,EAAS,EAAM,GAAG,GAAG,IAEtC,KADW,IAAQ,EAAY,EAAM,GAAG,GAAG,KAAA,IAClB,YAAY;AAwD3C,QAtDI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA,EAC/D,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACZ;KAAC;KAAG;KAAG;KAAE,CAAC,KAAK,MACd,kBAAC,OAAD,EAAa,WAAU,2CAA4C,EAAzD,EAAyD,CACnE;IACE,CAAA,EACN,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA,CACvD;KACF;MAIN,KAAS,EAAc,EAAM,GAE7B,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,OAAO,EAAG,oCAAoC,qBAAqB;GACnE,SAAS,EAAG,2CAA2C,gCAAgC;GACvF,eAAe,GAAS;GACxB,WAAA;GACA,CAAA;EACE,CAAA,GAIL,IA0BH,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAO,EAAM;IACb,aAAa,GAAG,EAAwB,EAAM,SAAS,CAAC;IACxD,CAAA;GAEF,kBAAC,GAAD,EAAA,UACG,EACC,sCACA,uKACD,EACW,CAAA;GAEd,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,GAAD;MAAe,WAAU;gBAAzB,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,0BAA0B,UAAU;QACrC,CAAA;OACD,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAe,EAAM,OAAO,EAAM,SAAS;SACvC,CAAA,EACP,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAA8C,MACzC,EAAmB,EAAM,SAAS,CAChC;WACH;WACN,kBAAC,KAAD;QAAG,WAAU;kBAAb;SACG,EAAG,yBAAyB,SAAS;SAAE;SACvC,EAAM,aAAa,YAChB,EAAG,6BAA6B,cAAc,GAC9C,EAAG,2BAA2B,WAAW;SAC3C;UACA;SACQ;SAGhB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACG,EAAG,iCAAiC,kBAAkB;SAAC;SAAG,EAAe;SAAO;SAC9E;;OACD,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAe,WAAW,IACzB,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAG,2BAA2B,wCAAwC;QACrE,CAAA,GAEJ,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAe,KAAK,MACnB,kBAAC,OAAD;SAEE,WAAU;mBAFZ,CAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,OAAD;YACE,WAAU;YACV,MAAK;YACL,SAAQ;YACR,QAAO;sBAEP,kBAAC,QAAD;aACE,eAAc;aACd,gBAAe;aACf,aAAa;aACb,GAAE;aACF,CAAA;YACE,CAAA;WACF,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAiC,EAAM;WAAS,CAAA,EAC7D,kBAAC,KAAD;WAAG,WAAU;qBAAb,CAAsD,UAC7C,EAAM,KACX;aACA,EAAA,CAAA,CACF;aACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAM,WAAW,gBAAgB;WAC7B,CAAA,EACP,kBAAC,KAAD;WAAG,WAAU;qBAA8B;WAAS,CAAA,CAChD;YACF;WAhCC,EAAM,QAgCP,CACN;QACE,CAAA;OAEJ,CAAA,CACF;QACF;QAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAG,2BAA2B,WAAW;QACvC,CAAA;OACD,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,wBAAwB,QAAQ;UAC/B,CAAA,EACP,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WACG,EAAe,EAAM,OAAO,EAAM,SAAS;WAAC;WAAG;WAC/C,EAAmB,EAAM,SAAS;WAC9B;YACH;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAG,+BAA+B,UAAU;UACxC,CAAA,EACP,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAM;UACF,CAAA,CACH;;QAEN,kBAAC,OAAD;SAAK,WAAU;mBACZ,IACC,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,EAAe,EAAM,IAAI,IAAe,EAAE;cACzD,WAAU;wBAEV,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;cACrB,CAAA;aACT,kBAAC,QAAD;cAAM,WAAU;wBAAgC;cAAoB,CAAA;aACpE,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,EAAe,EAAM,IAAI,IAAe,EAAE;cACzD,WAAU;wBAEV,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;cACpB,CAAA;aACL;eACN,kBAAC,UAAD;YACE,MAAK;YACL,eAAe,EAAe,EAAM,GAAG;YACvC,WAAU;YACV,OAAM;sBAEN,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;YACtB,CAAA,CACL;cACN,kBAAC,UAAD;WACE,MAAK;WACL,eAAe,EAAW,mBAAmB;WAC7C,WAAU;qBACX;WAEQ,CAAA,CACL;cAEN,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAU,EAAM;UAC/B,WAAU;oBAHZ,CAKE,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,EAAA,cAE5B;;SAEP,CAAA;QACF;SACF;SAGN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,UAAU;MACpC,WAAU;gBACX;MAEQ,CAAA,CACL;OACF;;GACF;MAnNJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,gCAAgC,kBAAkB;KACnD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,uCACA,kEACD;KACC,CAAA;IACJ,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,UAAU;KACpC,WAAU;eACX;KAEQ,CAAA;IACL;;EACF,CAAA"}
1
+ {"version":3,"file":"AddonBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Browse Detail Page (User-facing)\n * View addon details and add to cart. No edit/toggle actions.\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport { ShoppingCart, Plus, Minus, Trash2 } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon } from '../hooks';\nimport { useAddonCartStore } from '../store/addonCartStore';\nimport { PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\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 limits = feature.quota.limits as { value?: number; type?: string } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\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 type: limitType,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const AddonBrowseDetailPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { addToCart, removeFromCart, updateQuantity, isInCart, getCartItem } = useAddonCartStore();\n\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n const inCart = addon ? isInCart(addon.id) : false;\n const cartItem = addon ? getCartItem(addon.id) : undefined;\n const cartQuantity = cartItem?.quantity || 0;\n\n if (isLoading) {\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 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.addons.unableToLoadAddonDetails', 'Unable to load addon details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!addon) {\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.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.browseDetailPurpose',\n 'See exactly what this add-on costs and the extra capacity it gives you on top of your plan before you buy it. Add it to your cart from here when it fits your needs.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-4 border-b border-border-subtle\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(addon.price, addon.currency)}\n </span>\n <span className=\"text-lg text-text-secondary\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Quotas Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-secondary py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\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=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <p className=\"text-xs text-text-secondary capitalize\">\n Type: {quota.type}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-text-secondary\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Purchase Card */}\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.purchase', 'Purchase')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.price', 'Price')}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(addon.price, addon.currency)} /{' '}\n {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-secondary\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-text-primary capitalize\">\n {addon.duration}\n </span>\n </div>\n\n <div className=\"pt-2 space-y-2\">\n {inCart ? (\n <div className=\"space-y-2\">\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={() => updateQuantity(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={() => updateQuantity(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={() => removeFromCart(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 <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/addons')}\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\"\n >\n Proceed to Checkout\n </button>\n </div>\n ) : (\n <button\n type=\"button\"\n onClick={() => addToCart(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 </div>\n </div>\n </div>\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AddonCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonCreatePage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Create Page\n * Form to create a new addon\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n PlanDuration,\n type CreateAddonInput,\n type CurrencyPriceOverrideInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const AddonCreatePage: 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 { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { createAddon, isCreating } = useAddonMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [productId, setProductId] = useState('');\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noCreatePermission',\n \"You don't have permission to create addons.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isQuotasLoading) {\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=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Addon name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (!productId.trim()) {\n setError('Product ID is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: CreateAddonInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await createAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/addons'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.addons.createNewAddon', 'Create New Addon')}\n description={tr('billing.addons.createNewAddonDesc', 'Set up a new addon with quotas')}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.createPurpose',\n 'Create an add-on that customers can buy on top of a subscription — extra seats, storage, usage credits and so on. Set its price (with optional per-currency overrides) and the quotas it grants, then make it available to purchase.'\n )}\n </PagePurpose>\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.addons.createSuccess', 'Addon created successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.addons.addonNameLabel', 'Addon Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.addons.addonNamePlaceholder', 'e.g., Extra Storage')}\n required\n />\n </div>\n\n {/* Product ID */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.productId', 'Product ID')} *\n </label>\n <input\n id=\"productId\"\n type=\"text\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.addons.productIdPlaceholder', 'e.g., prod_abc123')}\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr('billing.addons.productIdDesc', 'The product this addon belongs to')}\n </p>\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"9.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.quotaAllocations', 'Quota Allocations')} *\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\n ) : quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n const totalValue = limitValue * allocation.quantity;\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n allocation.quotaId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.addons.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\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=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"text-text-muted\">\n <span className=\"capitalize\">Type: {limitType}</span>\n <span className=\"mx-2\">•</span>\n <span>\n Value per unit:{' '}\n <span className=\"font-medium text-text-primary\">\n {limitValue.toLocaleString()}\n </span>\n </span>\n </div>\n </div>\n\n {/* Quantity & Total */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.quantity', 'Quantity')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) =>\n handleQuotaChange(index, 'quantity', e.target.value)\n }\n className=\"w-24 px-3 py-1.5 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-center\"\n />\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-text-muted\">=</span>\n <span className=\"font-semibold text-status-success-text\">\n {totalValue.toLocaleString()} total\n </span>\n </div>\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isCreating || quotas.length === 0}\n className=\"px-6 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isCreating\n ? tr('billing.addons.creating', 'Creating...')\n : tr('billing.addons.createAddonBtn', 'Create Addon')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\n className=\"px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddonCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonCreatePage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Create Page\n * Form to create a new addon\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport {\n PlanDuration,\n type CreateAddonInput,\n type CurrencyPriceOverrideInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const AddonCreatePage: 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 { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { createAddon, isCreating } = useAddonMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [productId, setProductId] = useState('');\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noCreatePermission',\n \"You don't have permission to create addons.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isQuotasLoading) {\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=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Addon name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (!productId.trim()) {\n setError('Product ID is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: CreateAddonInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await createAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/addons'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.addons.createNewAddon', 'Create New Addon')}\n description={tr('billing.addons.createNewAddonDesc', 'Set up a new addon with quotas')}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.createPurpose',\n 'Create an add-on that customers can buy on top of a subscription — extra seats, storage, usage credits and so on. Set its price (with optional per-currency overrides) and the quotas it grants, then make it available to purchase.'\n )}\n </PagePurpose>\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.addons.createSuccess', 'Addon created successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.addons.addonNameLabel', 'Addon Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.addons.addonNamePlaceholder', 'e.g., Extra Storage')}\n required\n />\n </div>\n\n {/* Product ID */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.productId', 'Product ID')} *\n </label>\n <input\n id=\"productId\"\n type=\"text\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.addons.productIdPlaceholder', 'e.g., prod_abc123')}\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr('billing.addons.productIdDesc', 'The product this addon belongs to')}\n </p>\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"9.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.quotaAllocations', 'Quota Allocations')} *\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\n ) : quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n const totalValue = limitValue * allocation.quantity;\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n allocation.quotaId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.addons.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\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=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"text-text-muted\">\n <span className=\"capitalize\">Type: {limitType}</span>\n <span className=\"mx-2\">•</span>\n <span>\n Value per unit:{' '}\n <span className=\"font-medium text-text-primary\">\n {limitValue.toLocaleString()}\n </span>\n </span>\n </div>\n </div>\n\n {/* Quantity & Total */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.quantity', 'Quantity')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) =>\n handleQuotaChange(index, 'quantity', e.target.value)\n }\n className=\"w-24 px-3 py-1.5 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-center\"\n />\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-text-muted\">=</span>\n <span className=\"font-semibold text-status-success-text\">\n {totalValue.toLocaleString()} total\n </span>\n </div>\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isCreating || quotas.length === 0}\n className=\"px-6 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isCreating\n ? tr('billing.addons.creating', 'Creating...')\n : tr('billing.addons.createAddonBtn', 'Create Addon')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\n className=\"px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AddonDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Detail Page\n * Displays detailed information about a specific addon\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\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 limitType = (feature.quota.limits as { type?: string } | null)?.type ?? 'unknown';\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 type: limitType,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const AddonDetailPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const displayCurrency = useDefaultBillingCurrency();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { toggleAddon, isToggling } = useAddonMutations();\n\n // Memoize combined quotas before any conditional returns\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n\n // Permission check\n if (!permissions.canViewAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noViewPermission',\n \"You don't have permission to view addon details.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isLoading) {\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 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n // Error state\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableMessage',\n 'Unable to load addon details. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n // Not found state\n if (!addon) {\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-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=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n );\n }\n\n const handleToggle = async () => {\n try {\n await toggleAddon(addon.id, !addon.isActive);\n refetch();\n } catch (err) {\n console.error('Failed to toggle addon:', err);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.detailPurpose',\n 'A full breakdown of this add-on — its price and the extra quotas it grants on top of a subscription. Review it here to decide whether to add it, or (as an admin) to edit what it includes.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-4 border-b border-border-subtle\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-lg text-text-muted\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-muted mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Quotas Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-muted py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-4 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=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <p className=\"text-xs text-text-muted capitalize\">Type: {quota.type}</p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-text-muted\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Status Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.statusTitle', 'Status')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-muted\">\n {tr('billing.addons.status', 'Status')}\n </span>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${\n addon.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {addon.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-muted\">\n {tr('billing.addons.currency', 'Currency')}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {\n getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n ).currency\n }\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-muted\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-text-primary capitalize\">\n {addon.duration}\n </span>\n </div>\n </div>\n </div>\n\n {/* Actions Card */}\n {permissions.canManageAddons && (\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.actions', 'Actions')}\n </h2>\n </div>\n <div className=\"p-4 space-y-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}/edit`)}\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\"\n >\n Edit Addon\n </button>\n <button\n type=\"button\"\n onClick={handleToggle}\n disabled={isToggling}\n className={`w-full px-4 py-2 text-sm font-medium border border-border-subtle rounded-md transition-colors disabled:opacity-50 ${\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 {isToggling\n ? tr('billing.addons.processing', 'Processing...')\n : addon.isActive\n ? tr('billing.addons.deactivateAddon', 'Deactivate Addon')\n : tr('billing.addons.activateAddon', 'Activate Addon')}\n </button>\n </div>\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddonDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Detail Page\n * Displays detailed information about a specific addon\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\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 limitType = (feature.quota.limits as { type?: string } | null)?.type ?? 'unknown';\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 type: limitType,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const AddonDetailPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const displayCurrency = useDefaultBillingCurrency();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { toggleAddon, isToggling } = useAddonMutations();\n\n // Memoize combined quotas before any conditional returns\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n\n // Permission check\n if (!permissions.canViewAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noViewPermission',\n \"You don't have permission to view addon details.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isLoading) {\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 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n // Error state\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableMessage',\n 'Unable to load addon details. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n // Not found state\n if (!addon) {\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-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=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n );\n }\n\n const handleToggle = async () => {\n try {\n await toggleAddon(addon.id, !addon.isActive);\n refetch();\n } catch (err) {\n console.error('Failed to toggle addon:', err);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.detailPurpose',\n 'A full breakdown of this add-on — its price and the extra quotas it grants on top of a subscription. Review it here to decide whether to add it, or (as an admin) to edit what it includes.'\n )}\n </PagePurpose>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"overflow-hidden p-0\">\n <div className=\"p-4 border-b border-border-subtle\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-lg text-text-muted\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-text-muted mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Quotas Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-muted py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-sunken/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-4 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=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <p className=\"text-xs text-text-muted capitalize\">Type: {quota.type}</p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-text-muted\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Status Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.statusTitle', 'Status')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-muted\">\n {tr('billing.addons.status', 'Status')}\n </span>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${\n addon.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {addon.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-muted\">\n {tr('billing.addons.currency', 'Currency')}\n </span>\n <span className=\"text-sm font-medium text-text-primary\">\n {\n getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n ).currency\n }\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-muted\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-text-primary capitalize\">\n {addon.duration}\n </span>\n </div>\n </div>\n </div>\n\n {/* Actions Card */}\n {permissions.canManageAddons && (\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.addons.actions', 'Actions')}\n </h2>\n </div>\n <div className=\"p-4 space-y-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}/edit`)}\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\"\n >\n Edit Addon\n </button>\n <button\n type=\"button\"\n onClick={handleToggle}\n disabled={isToggling}\n className={`w-full px-4 py-2 text-sm font-medium border border-border-subtle rounded-md transition-colors disabled:opacity-50 ${\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 {isToggling\n ? tr('billing.addons.processing', 'Processing...')\n : addon.isActive\n ? tr('billing.addons.deactivateAddon', 'Deactivate Addon')\n : tr('billing.addons.activateAddon', 'Activate Addon')}\n </button>\n </div>\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Back to Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}