@burdenoff/microfe-billing 2026.1013.2 → 2026.1013.4

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 (57) hide show
  1. package/dist/billing/modules/addons/pages/AddonCreatePage.js +4 -4
  2. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -3
  4. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  5. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +1 -1
  6. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  7. package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js +3 -3
  8. package/dist/billing/modules/billing/pages/BillingWebhookLogsPage.js.map +1 -1
  9. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  10. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  11. package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js +2 -2
  12. package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js.map +1 -1
  13. package/dist/billing/modules/billing/pages/RefundsPage.js +2 -2
  14. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  15. package/dist/billing/modules/billing/pages/UserRefundsPage.js +1 -1
  16. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  17. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +1 -1
  18. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  19. package/dist/billing/modules/checkout/components/StoreShippingSection.js +1 -1
  20. package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
  21. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +1 -1
  22. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  23. package/dist/billing/modules/coupons/pages/CouponsListPage.js +2 -2
  24. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  25. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +2 -2
  26. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  27. package/dist/billing/modules/coupons/pages/EditCouponPage.js +1 -1
  28. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  29. package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
  30. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  31. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +2 -2
  32. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  33. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +2 -2
  34. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  35. package/dist/billing/modules/discounts/pages/DiscountsListPage.js +2 -2
  36. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  37. package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js +1 -1
  38. package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -1
  39. package/dist/billing/modules/earnings/pages/EarningsPage.js +1 -1
  40. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  41. package/dist/billing/modules/earnings/pages/PayoutsPage.js +3 -3
  42. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  43. package/dist/billing/modules/plans/pages/PlanCreatePage.js +3 -3
  44. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  45. package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -3
  46. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  47. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
  48. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  49. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +2 -2
  50. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  51. package/dist/billing/modules/settings/pages/SettingsPage.js +1 -1
  52. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  53. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +1 -1
  54. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  55. package/dist/billing/modules/usage/pages/UsagePage.js +1 -1
  56. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  57. package/package.json +1 -1
@@ -146,7 +146,7 @@ var h = () => {
146
146
  value: N,
147
147
  onChange: (e) => P(e.target.value),
148
148
  disabled: x.length === 0,
149
- className: `w-full 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)] disabled:opacity-50 ${N === "" ? "text-text-muted" : "text-text-primary"}`,
149
+ className: `w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50 ${N === "" ? "text-text-muted" : "text-text-primary"}`,
150
150
  required: !0,
151
151
  children: [/* @__PURE__ */ d("option", {
152
152
  value: "",
@@ -186,7 +186,7 @@ var h = () => {
186
186
  id: "currency",
187
187
  value: k,
188
188
  onChange: (e) => A(e.target.value),
189
- 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)]",
189
+ 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
190
190
  children: [
191
191
  /* @__PURE__ */ d("option", {
192
192
  value: "USD",
@@ -231,7 +231,7 @@ var h = () => {
231
231
  id: "duration",
232
232
  value: j,
233
233
  onChange: (e) => M(e.target.value),
234
- 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)]",
234
+ 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
235
235
  children: [/* @__PURE__ */ d("option", {
236
236
  value: r.MONTHLY,
237
237
  children: "Monthly"
@@ -273,7 +273,7 @@ var h = () => {
273
273
  children: [/* @__PURE__ */ f("select", {
274
274
  value: e.quotaId,
275
275
  onChange: (e) => G(t, "quotaId", e.target.value),
276
- 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)] ${e.quotaId === "" ? "text-text-muted" : "text-text-primary"}`,
276
+ className: `flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${e.quotaId === "" ? "text-text-muted" : "text-text-primary"}`,
277
277
  children: [/* @__PURE__ */ d("option", {
278
278
  value: "",
279
279
  disabled: !0,
@@ -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 { useProducts } from '../../../hooks/useProducts';\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 { products, isLoading: isProductsLoading } = useProducts();\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 || isProductsLoading) {\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 */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.product', 'Product')} *\n </label>\n <select\n id=\"productId\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n disabled={products.length === 0}\n className={`w-full 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)] disabled:opacity-50 ${\n productId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n required\n >\n <option value=\"\" disabled>\n {tr('billing.addons.selectProduct', 'Select a product...')}\n </option>\n {products.map((product) => (\n <option key={product.id} value={product.slug}>\n {product.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-muted mt-1\">\n {products.length === 0\n ? tr('billing.addons.noProductsAvailable', 'No active products found.')\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 limitType = limits?.type ?? 'unknown';\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 <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 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 </>\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 { useProducts } from '../../../hooks/useProducts';\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 { products, isLoading: isProductsLoading } = useProducts();\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 || isProductsLoading) {\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 */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.addons.product', 'Product')} *\n </label>\n <select\n id=\"productId\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n disabled={products.length === 0}\n className={`w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50 ${\n productId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n required\n >\n <option value=\"\" disabled>\n {tr('billing.addons.selectProduct', 'Select a product...')}\n </option>\n {products.map((product) => (\n <option key={product.id} value={product.slug}>\n {product.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-muted mt-1\">\n {products.length === 0\n ? tr('billing.addons.noProductsAvailable', 'No active products found.')\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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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 limitType = limits?.type ?? 'unknown';\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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary 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 <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 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 </>\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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"}
@@ -206,7 +206,7 @@ var b = () => {
206
206
  id: "currency",
207
207
  value: L,
208
208
  onChange: (e) => R(e.target.value),
209
- 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)]",
209
+ 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
210
210
  children: [
211
211
  /* @__PURE__ */ g("option", {
212
212
  value: "USD",
@@ -251,7 +251,7 @@ var b = () => {
251
251
  id: "duration",
252
252
  value: z,
253
253
  onChange: (e) => B(e.target.value),
254
- 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)]",
254
+ 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
255
255
  children: [/* @__PURE__ */ g("option", {
256
256
  value: i.MONTHLY,
257
257
  children: "Monthly"
@@ -303,7 +303,7 @@ var b = () => {
303
303
  children: [/* @__PURE__ */ _("select", {
304
304
  value: e.quotaId,
305
305
  onChange: (e) => $(t, "quotaId", e.target.value),
306
- 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)] ${e.quotaId === "" ? "text-text-muted" : "text-text-primary"}`,
306
+ className: `flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${e.quotaId === "" ? "text-text-muted" : "text-text-primary"}`,
307
307
  children: [/* @__PURE__ */ g("option", {
308
308
  value: "",
309
309
  disabled: !0,
@@ -1 +1 @@
1
- {"version":3,"file":"AddonEditPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonEditPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Edit Page\n * Form to edit an existing addon\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { isServerError } from '../../../shared/utils';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdateAddonInput,\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 AddonEditPage: 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 { addon, isLoading: isAddonLoading, error: addonError, refetch } = useAddon(addonId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updateAddon, isUpdating } = 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 [isActive, setIsActive] = useState(true);\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 // Populate form when addon data loads\n useEffect(() => {\n if (addon) {\n setName(addon.name ?? '');\n setPrice(String(addon.price ?? ''));\n setCurrency(addon.currency ?? 'USD');\n setDuration(addon.duration ?? PlanDuration.MONTHLY);\n setIsActive(addon.isActive ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const existing = addon.currencyPrices as Record<string, number>;\n setCurrencyPriceOverrides(\n Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }))\n );\n }\n // Extract quota allocations from features\n if (addon.features && addon.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of addon.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n const value = getPlanFeatureQuantity(feature);\n quotaMap.set(quotaId, (quotaMap.get(quotaId) ?? 0) + value);\n }\n }\n\n for (const [quotaId, quantity] of quotaMap) {\n allocations.push({ quotaId, quantity });\n }\n\n setQuotaAllocations(allocations);\n }\n }\n }, [addon]);\n\n // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr('billing.addons.noEditPermission', \"You don't have permission to edit addons.\")}\n />\n );\n }\n\n // Loading state\n if (isAddonLoading || 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 // Error state\n if (addonError && isServerError(addonError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableLoadAddon',\n 'Unable to load addon. The server might be down or experiencing issues.'\n )}\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 <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\"\n >\n Back to Addons\n </button>\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 (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: UpdateAddonInput = {\n id: addonId!,\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n isActive,\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 updateAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/addons/${addonId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Addon: ${addon.name}`}\n description={tr(\n 'billing.addons.editAddonDesc',\n 'Update the addon details and quota allocations'\n )}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.editPurpose',\n \"Update this add-on's price, currency overrides and the quotas it grants. Changes affect new purchases; existing add-on subscriptions keep what they already bought.\"\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.updateSuccess', 'Addon updated 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 {/* 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\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-text-primary\">\n {tr('billing.addons.addonIsActive', 'Addon is active')}\n </label>\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 className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\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 limitType =\n (selectedQuota?.limits as { type?: string } | null)?.type ?? 'unknown';\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 <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 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 </>\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={isUpdating}\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 {isUpdating\n ? tr('billing.addons.saving', 'Saving...')\n : tr('billing.addons.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addonId}`)}\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":"AddonEditPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonEditPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Edit Page\n * Form to edit an existing addon\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useQuotas } from '../../plans/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { isServerError } from '../../../shared/utils';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdateAddonInput,\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 AddonEditPage: 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 { addon, isLoading: isAddonLoading, error: addonError, refetch } = useAddon(addonId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updateAddon, isUpdating } = 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 [isActive, setIsActive] = useState(true);\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 // Populate form when addon data loads\n useEffect(() => {\n if (addon) {\n setName(addon.name ?? '');\n setPrice(String(addon.price ?? ''));\n setCurrency(addon.currency ?? 'USD');\n setDuration(addon.duration ?? PlanDuration.MONTHLY);\n setIsActive(addon.isActive ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (addon.currencyPrices && typeof addon.currencyPrices === 'object') {\n const existing = addon.currencyPrices as Record<string, number>;\n setCurrencyPriceOverrides(\n Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }))\n );\n }\n // Extract quota allocations from features\n if (addon.features && addon.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of addon.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n const value = getPlanFeatureQuantity(feature);\n quotaMap.set(quotaId, (quotaMap.get(quotaId) ?? 0) + value);\n }\n }\n\n for (const [quotaId, quantity] of quotaMap) {\n allocations.push({ quotaId, quantity });\n }\n\n setQuotaAllocations(allocations);\n }\n }\n }, [addon]);\n\n // Permission check\n if (!permissions.canManageAddons) {\n return (\n <AccessDenied\n message={tr('billing.addons.noEditPermission', \"You don't have permission to edit addons.\")}\n />\n );\n }\n\n // Loading state\n if (isAddonLoading || 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 // Error state\n if (addonError && isServerError(addonError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableLoadAddon',\n 'Unable to load addon. The server might be down or experiencing issues.'\n )}\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 <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\"\n >\n Back to Addons\n </button>\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 (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: UpdateAddonInput = {\n id: addonId!,\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n isActive,\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 updateAddon(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/addons/${addonId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update addon');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Addon: ${addon.name}`}\n description={tr(\n 'billing.addons.editAddonDesc',\n 'Update the addon details and quota allocations'\n )}\n />\n\n <PagePurpose>\n {tr(\n 'billing.addons.editPurpose',\n \"Update this add-on's price, currency overrides and the quotas it grants. Changes affect new purchases; existing add-on subscriptions keep what they already bought.\"\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.updateSuccess', 'Addon updated 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 {/* 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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\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)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-text-primary\">\n {tr('billing.addons.addonIsActive', 'Addon is active')}\n </label>\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 className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\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 limitType =\n (selectedQuota?.limits as { type?: string } | null)?.type ?? 'unknown';\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 scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary 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 <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.addons.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 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 </>\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={isUpdating}\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 {isUpdating\n ? tr('billing.addons.saving', 'Saving...')\n : tr('billing.addons.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addonId}`)}\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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"}
@@ -192,7 +192,7 @@ var P = () => {
192
192
  value: s,
193
193
  onChange: (e) => E(e.target.value),
194
194
  "aria-label": "Analytics period",
195
- className: "rounded-input border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
195
+ className: "rounded-input border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
196
196
  children: w.map((e) => /* @__PURE__ */ v("option", {
197
197
  value: e.value,
198
198
  children: e.label
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../../src/billing/modules/analytics/pages/AnalyticsPage.tsx"],"sourcesContent":["/**\n * Analytics Module - Revenue Analytics Page\n * Uses real billing data aggregated from billing accounts, subscriptions, and payments.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { AlertCircle, BarChart3, RefreshCw, TrendingUp, Users, Wallet } from 'lucide-react';\nimport {\n CTAOverflowMenu,\n PagePurpose,\n EmphasisPanel,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader, StatCard } from '../../../shared/components';\nimport { formatCurrency, formatPercentage } from '../../../shared/utils/format';\nimport type { AnalyticsPeriod } from '../../../shared/types';\nimport {\n type GetBillingAnalyticsPageDataQuery,\n useGetBillingAnalyticsPageDataQuery,\n} from '../../../../generated/global-operations';\nimport { PlanDuration, SubscriptionStatus } from '../../../../generated/global-types';\n\nconst PERIOD_OPTIONS: { value: AnalyticsPeriod; label: string }[] = [\n { value: 'LAST_7_DAYS' as AnalyticsPeriod, label: 'Last 7 days' },\n { value: 'LAST_30_DAYS' as AnalyticsPeriod, label: 'Last 30 days' },\n { value: 'LAST_90_DAYS' as AnalyticsPeriod, label: 'Last 90 days' },\n { value: 'CURRENT_MONTH' as AnalyticsPeriod, label: 'This month' },\n { value: 'LAST_MONTH' as AnalyticsPeriod, label: 'Last month' },\n];\n\nconst ACTIVE_SUBSCRIPTION_STATUSES = new Set<SubscriptionStatus>([\n SubscriptionStatus.Active,\n SubscriptionStatus.Trialing,\n SubscriptionStatus.PastDue,\n SubscriptionStatus.Paused,\n]);\n\ntype RevenuePoint = {\n label: string;\n value: number;\n};\n\ntype PlanBreakdown = {\n name: string;\n activeSubscriptions: number;\n estimatedMrr: number;\n};\n\ntype CustomerSummary = {\n id: string;\n name: string;\n email: string;\n activeSubscriptions: number;\n estimatedMrr: number;\n};\n\ntype BillingAnalyticsAccount = GetBillingAnalyticsPageDataQuery['getBillingAccountsByOrg'][number];\ntype BillingAnalyticsSubscription = NonNullable<\n NonNullable<BillingAnalyticsAccount['subscriptions']>[number]\n>;\ntype BillingAnalyticsPayment = GetBillingAnalyticsPageDataQuery['getPayments'][number];\n\nfunction startOfDay(date: Date): Date {\n const value = new Date(date);\n value.setHours(0, 0, 0, 0);\n return value;\n}\n\nfunction getPeriodRange(period: AnalyticsPeriod): { start: Date; end: Date } {\n const now = new Date();\n const end = now;\n\n switch (period) {\n case 'LAST_7_DAYS': {\n const start = new Date(now);\n start.setDate(start.getDate() - 6);\n return { start: startOfDay(start), end };\n }\n case 'LAST_30_DAYS': {\n const start = new Date(now);\n start.setDate(start.getDate() - 29);\n return { start: startOfDay(start), end };\n }\n case 'LAST_90_DAYS': {\n const start = new Date(now);\n start.setDate(start.getDate() - 89);\n return { start: startOfDay(start), end };\n }\n case 'LAST_MONTH': {\n const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n const monthEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);\n return { start, end: monthEnd };\n }\n case 'CURRENT_MONTH':\n default:\n return {\n start: new Date(now.getFullYear(), now.getMonth(), 1),\n end,\n };\n }\n}\n\nfunction isWithinRange(dateValue: string | null | undefined, start: Date, end: Date): boolean {\n if (!dateValue) {\n return false;\n }\n\n const date = new Date(dateValue);\n return date >= start && date <= end;\n}\n\nfunction normalizeRecurringAmount(\n amount: number,\n duration: PlanDuration | null | undefined\n): number {\n if (duration === PlanDuration.Yearly) {\n return amount / 12;\n }\n\n return amount;\n}\n\nfunction getBucketLabel(dateValue: Date, period: AnalyticsPeriod): string {\n if (period === 'LAST_90_DAYS') {\n return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(dateValue);\n }\n\n return new Intl.DateTimeFormat('en-US', {\n month: 'short',\n day: 'numeric',\n }).format(dateValue);\n}\n\nfunction buildRevenueSeries(\n payments: Array<{ amount: number; paidAt?: string | null; createdAt: string }>,\n period: AnalyticsPeriod,\n start: Date,\n end: Date\n): RevenuePoint[] {\n const buckets = new Map<string, number>();\n\n payments.forEach((payment) => {\n const paymentDate = new Date(payment.paidAt || payment.createdAt);\n if (paymentDate < start || paymentDate > end) {\n return;\n }\n\n const label = getBucketLabel(paymentDate, period);\n buckets.set(label, (buckets.get(label) ?? 0) + payment.amount);\n });\n\n return Array.from(buckets.entries()).map(([label, value]) => ({ label, value }));\n}\n\nfunction buildSubscriptionGrowthSeries(\n subscriptions: Array<{ createdAt: string }>,\n period: AnalyticsPeriod,\n start: Date,\n end: Date\n): RevenuePoint[] {\n const buckets = new Map<string, number>();\n\n subscriptions.forEach((subscription) => {\n const createdAt = new Date(subscription.createdAt);\n if (createdAt < start || createdAt > end) {\n return;\n }\n\n const label = getBucketLabel(createdAt, period);\n buckets.set(label, (buckets.get(label) ?? 0) + 1);\n });\n\n return Array.from(buckets.entries()).map(([label, value]) => ({ label, value }));\n}\n\nfunction formatMetricCurrency(value: number, currency: string): string {\n try {\n return formatCurrency(value, currency);\n } catch {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: 2,\n }).format(value);\n }\n}\n\nexport const AnalyticsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [period, setPeriod] = useState<AnalyticsPeriod>('LAST_30_DAYS' as AnalyticsPeriod);\n const { start, end } = useMemo(() => getPeriodRange(period), [period]);\n\n const { data, loading, error, refetch } = useGetBillingAnalyticsPageDataQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n const billingAccounts = useMemo<BillingAnalyticsAccount[]>(\n () => data?.getBillingAccountsByOrg ?? [],\n [data?.getBillingAccountsByOrg]\n );\n const successfulPayments = useMemo<BillingAnalyticsPayment[]>(\n () => data?.getPayments ?? [],\n [data?.getPayments]\n );\n\n const subscriptions = useMemo<BillingAnalyticsSubscription[]>(\n () =>\n billingAccounts.flatMap((account: BillingAnalyticsAccount) =>\n (account.subscriptions ?? []).filter(\n (subscription): subscription is BillingAnalyticsSubscription => Boolean(subscription)\n )\n ),\n [billingAccounts]\n );\n\n const activeSubscriptions = useMemo<BillingAnalyticsSubscription[]>(\n () =>\n subscriptions.filter((subscription: BillingAnalyticsSubscription) =>\n ACTIVE_SUBSCRIPTION_STATUSES.has(subscription.status)\n ),\n [subscriptions]\n );\n\n const displayCurrency =\n activeSubscriptions.find((subscription) => subscription.plan?.currency)?.plan?.currency ||\n successfulPayments[0]?.currency ||\n billingAccounts[0]?.currency ||\n 'USD';\n\n const metrics = useMemo(() => {\n const mrr = activeSubscriptions.reduce(\n (sum: number, subscription: BillingAnalyticsSubscription) => {\n const planPrice = subscription.plan?.price ?? 0;\n const seatCount =\n subscription.seatCount && subscription.seatCount > 0 ? subscription.seatCount : 1;\n return sum + normalizeRecurringAmount(planPrice, subscription.plan?.duration) * seatCount;\n },\n 0\n );\n\n const arr = mrr * 12;\n const totalCustomers = billingAccounts.length;\n const newCustomers = billingAccounts.filter((account: BillingAnalyticsAccount) =>\n isWithinRange(account.createdAt, start, end)\n ).length;\n const churnedSubscriptions = subscriptions.filter(\n (subscription: BillingAnalyticsSubscription) =>\n (subscription.status === SubscriptionStatus.Canceled ||\n subscription.status === SubscriptionStatus.Expired) &&\n isWithinRange(subscription.canceledAt || subscription.endDate, start, end)\n ).length;\n\n const churnBase = activeSubscriptions.length + churnedSubscriptions;\n const churnRate = churnBase > 0 ? (churnedSubscriptions / churnBase) * 100 : 0;\n const arpu = totalCustomers > 0 ? mrr / totalCustomers : 0;\n const ltv = churnRate > 0 ? arpu / (churnRate / 100) : arr;\n\n return {\n mrr,\n arr,\n arpu,\n churnRate,\n ltv,\n totalCustomers,\n newCustomers,\n activeSubscriptions: activeSubscriptions.length,\n };\n }, [activeSubscriptions, billingAccounts, start, end, subscriptions]);\n\n const revenueSeries = useMemo(\n () => buildRevenueSeries(successfulPayments, period, start, end),\n [successfulPayments, period, start, end]\n );\n\n const subscriptionGrowthSeries = useMemo(\n () => buildSubscriptionGrowthSeries(subscriptions, period, start, end),\n [period, start, end, subscriptions]\n );\n\n const revenueByPlan = useMemo<PlanBreakdown[]>(() => {\n const planMap = new Map<string, PlanBreakdown>();\n\n activeSubscriptions.forEach((subscription: BillingAnalyticsSubscription) => {\n const planName = subscription.plan?.name || 'Unknown Plan';\n const seatCount =\n subscription.seatCount && subscription.seatCount > 0 ? subscription.seatCount : 1;\n const estimatedMrr =\n normalizeRecurringAmount(subscription.plan?.price ?? 0, subscription.plan?.duration) *\n seatCount;\n\n const current = planMap.get(planName);\n planMap.set(planName, {\n name: planName,\n activeSubscriptions: (current?.activeSubscriptions ?? 0) + 1,\n estimatedMrr: (current?.estimatedMrr ?? 0) + estimatedMrr,\n });\n });\n\n return Array.from(planMap.values()).sort(\n (left: PlanBreakdown, right: PlanBreakdown) => right.estimatedMrr - left.estimatedMrr\n );\n }, [activeSubscriptions]);\n\n const topCustomers = useMemo<CustomerSummary[]>(() => {\n return billingAccounts\n .map((account: BillingAnalyticsAccount) => {\n const accountSubscriptions = (account.subscriptions ?? []).filter(\n (subscription): subscription is BillingAnalyticsSubscription => {\n if (!subscription) {\n return false;\n }\n\n return ACTIVE_SUBSCRIPTION_STATUSES.has(subscription.status);\n }\n );\n\n const estimatedMrr = accountSubscriptions.reduce(\n (sum: number, subscription: BillingAnalyticsSubscription) => {\n const seatCount =\n subscription.seatCount && subscription.seatCount > 0 ? subscription.seatCount : 1;\n return (\n sum +\n normalizeRecurringAmount(subscription.plan?.price ?? 0, subscription.plan?.duration) *\n seatCount\n );\n },\n 0\n );\n\n return {\n id: account.id ?? account.name ?? account.email ?? 'unknown-account',\n name: account.name ?? 'Unnamed account',\n email: account.email ?? 'No contact email',\n activeSubscriptions: accountSubscriptions.length,\n estimatedMrr,\n };\n })\n .sort(\n (left: CustomerSummary, right: CustomerSummary) => right.estimatedMrr - left.estimatedMrr\n )\n .slice(0, 5);\n }, [billingAccounts]);\n\n const maxRevenuePoint = Math.max(...revenueSeries.map((item) => item.value), 0);\n const maxGrowthPoint = Math.max(...subscriptionGrowthSeries.map((item) => item.value), 0);\n\n if (!permissions.canViewAnalytics) {\n return <AccessDenied message=\"You don't have permission to view analytics.\" />;\n }\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <PageHeader\n title=\"Revenue Analytics\"\n description=\"Track recurring revenue, customer growth, and billing performance using live billing data.\"\n actions={\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <select\n value={period}\n onChange={(event) => setPeriod(event.target.value as AnalyticsPeriod)}\n aria-label=\"Analytics period\"\n className=\"rounded-input border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n {PERIOD_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: 'Refresh',\n onSelect: () => {\n void refetch();\n },\n icon: <RefreshCw className=\"size-4\" />,\n },\n ]}\n />\n </div>\n }\n />\n\n <PagePurpose>\n Understand the financial health of your business at a glance — recurring revenue (MRR/ARR),\n customer growth, churn and your highest-value customers — all computed from live billing\n data. Use the period selector to compare timeframes and spot trends.\n </PagePurpose>\n\n {error && (\n <div className=\"flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n <div>\n <p className=\"font-medium\">Unable to load billing analytics</p>\n <p className=\"mt-1 text-sm\">{error.message}</p>\n </div>\n </div>\n )}\n\n {loading && billingAccounts.length === 0 ? (\n <div className=\"flex min-h-[320px] items-center justify-center rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"flex flex-col items-center gap-3 text-text-muted\">\n <RefreshCw className=\"size-6 animate-spin\" />\n <p className=\"text-sm\">Loading live billing analytics…</p>\n </div>\n </div>\n ) : billingAccounts.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No billing data available yet\"\n description=\"Create a billing account and complete a subscription or payment to unlock live analytics.\"\n />\n ) : (\n <>\n {/* Headline revenue metrics — the page's primary emphasis zone */}\n <EmphasisPanel className=\"p-4 sm:p-5\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n label=\"MRR\"\n value={formatMetricCurrency(metrics.mrr, displayCurrency)}\n icon={<TrendingUp className=\"size-5\" />}\n />\n <StatCard\n label=\"ARR\"\n value={formatMetricCurrency(metrics.arr, displayCurrency)}\n icon={<BarChart3 className=\"size-5\" />}\n />\n <StatCard\n label=\"ARPU\"\n value={formatMetricCurrency(metrics.arpu, displayCurrency)}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n label=\"Churn Rate\"\n value={formatPercentage(metrics.churnRate)}\n icon={<Users className=\"size-5\" />}\n />\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Total Customers\" value={metrics.totalCustomers} />\n <StatCard label=\"New Customers\" value={metrics.newCustomers} />\n <StatCard label=\"Active Subscriptions\" value={metrics.activeSubscriptions} />\n <StatCard\n label=\"Estimated LTV\"\n value={formatMetricCurrency(metrics.ltv, displayCurrency)}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <section className=\"rounded-card border border-border-seam bg-bg-surface p-4 sm:p-6 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">Revenue over time</h3>\n <p className=\"text-sm text-text-muted\">\n Successful payments received during the selected period.\n </p>\n </div>\n <span className=\"rounded-full bg-status-info-bg-subtle px-2.5 py-1 text-xs font-medium text-status-info-text\">\n {revenueSeries.length} data point{revenueSeries.length === 1 ? '' : 's'}\n </span>\n </div>\n\n {revenueSeries.length === 0 ? (\n <p className=\"rounded-lg bg-bg-sunken/40 p-4 text-sm text-text-muted\">\n No successful payments were recorded during this period.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {revenueSeries.map((point) => (\n <div key={point.label} className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"font-medium text-text-primary\">{point.label}</span>\n <span className=\"text-text-muted\">\n {formatMetricCurrency(point.value, displayCurrency)}\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg transition-[width]\"\n style={{\n width: `${maxRevenuePoint > 0 ? (point.value / maxRevenuePoint) * 100 : 0}%`,\n }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </section>\n\n <section className=\"rounded-card border border-border-seam bg-bg-surface p-4 sm:p-6 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">Subscription growth</h3>\n <p className=\"text-sm text-text-muted\">\n New subscriptions created during the selected period.\n </p>\n </div>\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {subscriptionGrowthSeries.reduce((sum, point) => sum + point.value, 0)} created\n </span>\n </div>\n\n {subscriptionGrowthSeries.length === 0 ? (\n <p className=\"rounded-lg bg-bg-sunken/40 p-4 text-sm text-text-muted\">\n No new subscriptions were created during this period.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {subscriptionGrowthSeries.map((point) => (\n <div key={point.label} className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"font-medium text-text-primary\">{point.label}</span>\n <span className=\"text-text-muted\">{point.value} new</span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-status-success-bg transition-[width]\"\n style={{\n width: `${maxGrowthPoint > 0 ? (point.value / maxGrowthPoint) * 100 : 0}%`,\n }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </section>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <section className=\"rounded-card border border-border-seam bg-bg-surface shadow-[var(--shadow-elevation-1)]\">\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"font-semibold text-text-primary\">Revenue by plan</h3>\n <p className=\"mt-1 text-sm text-text-muted\">\n Estimated monthly recurring revenue based on currently active subscriptions.\n </p>\n </div>\n <div className=\"p-4\">\n {revenueByPlan.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No active plans to analyse yet.</p>\n ) : (\n <div className=\"space-y-3\">\n {revenueByPlan.map((plan) => (\n <div\n key={plan.name}\n className=\"flex flex-col gap-2 rounded-lg border border-border-subtle p-3 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div>\n <p className=\"font-medium text-text-primary\">{plan.name}</p>\n <p className=\"text-sm text-text-muted\">\n {plan.activeSubscriptions} active subscription\n {plan.activeSubscriptions === 1 ? '' : 's'}\n </p>\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {formatMetricCurrency(plan.estimatedMrr, displayCurrency)} / month\n </p>\n </div>\n ))}\n </div>\n )}\n </div>\n </section>\n\n <section className=\"rounded-card border border-border-seam bg-bg-surface shadow-[var(--shadow-elevation-1)]\">\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"font-semibold text-text-primary\">Top customers</h3>\n <p className=\"mt-1 text-sm text-text-muted\">\n Ranked by estimated monthly recurring revenue from active subscriptions.\n </p>\n </div>\n <div className=\"p-4\">\n {topCustomers.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No customer revenue data available yet.</p>\n ) : (\n <div className=\"space-y-3\">\n {topCustomers.map((customer) => (\n <div\n key={customer.id}\n className=\"flex flex-col gap-2 rounded-lg border border-border-subtle p-3 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{customer.name}</p>\n <p className=\"truncate text-sm text-text-muted\">{customer.email}</p>\n </div>\n <div className=\"text-left sm:text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {formatMetricCurrency(customer.estimatedMrr, displayCurrency)} / month\n </p>\n <p className=\"text-xs text-text-muted\">\n {customer.activeSubscriptions} active subscription\n {customer.activeSubscriptions === 1 ? '' : 's'}\n </p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </section>\n </div>\n </>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsPage.js","names":[],"sources":["../../../../../src/billing/modules/analytics/pages/AnalyticsPage.tsx"],"sourcesContent":["/**\n * Analytics Module - Revenue Analytics Page\n * Uses real billing data aggregated from billing accounts, subscriptions, and payments.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { AlertCircle, BarChart3, RefreshCw, TrendingUp, Users, Wallet } from 'lucide-react';\nimport {\n CTAOverflowMenu,\n PagePurpose,\n EmphasisPanel,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader, StatCard } from '../../../shared/components';\nimport { formatCurrency, formatPercentage } from '../../../shared/utils/format';\nimport type { AnalyticsPeriod } from '../../../shared/types';\nimport {\n type GetBillingAnalyticsPageDataQuery,\n useGetBillingAnalyticsPageDataQuery,\n} from '../../../../generated/global-operations';\nimport { PlanDuration, SubscriptionStatus } from '../../../../generated/global-types';\n\nconst PERIOD_OPTIONS: { value: AnalyticsPeriod; label: string }[] = [\n { value: 'LAST_7_DAYS' as AnalyticsPeriod, label: 'Last 7 days' },\n { value: 'LAST_30_DAYS' as AnalyticsPeriod, label: 'Last 30 days' },\n { value: 'LAST_90_DAYS' as AnalyticsPeriod, label: 'Last 90 days' },\n { value: 'CURRENT_MONTH' as AnalyticsPeriod, label: 'This month' },\n { value: 'LAST_MONTH' as AnalyticsPeriod, label: 'Last month' },\n];\n\nconst ACTIVE_SUBSCRIPTION_STATUSES = new Set<SubscriptionStatus>([\n SubscriptionStatus.Active,\n SubscriptionStatus.Trialing,\n SubscriptionStatus.PastDue,\n SubscriptionStatus.Paused,\n]);\n\ntype RevenuePoint = {\n label: string;\n value: number;\n};\n\ntype PlanBreakdown = {\n name: string;\n activeSubscriptions: number;\n estimatedMrr: number;\n};\n\ntype CustomerSummary = {\n id: string;\n name: string;\n email: string;\n activeSubscriptions: number;\n estimatedMrr: number;\n};\n\ntype BillingAnalyticsAccount = GetBillingAnalyticsPageDataQuery['getBillingAccountsByOrg'][number];\ntype BillingAnalyticsSubscription = NonNullable<\n NonNullable<BillingAnalyticsAccount['subscriptions']>[number]\n>;\ntype BillingAnalyticsPayment = GetBillingAnalyticsPageDataQuery['getPayments'][number];\n\nfunction startOfDay(date: Date): Date {\n const value = new Date(date);\n value.setHours(0, 0, 0, 0);\n return value;\n}\n\nfunction getPeriodRange(period: AnalyticsPeriod): { start: Date; end: Date } {\n const now = new Date();\n const end = now;\n\n switch (period) {\n case 'LAST_7_DAYS': {\n const start = new Date(now);\n start.setDate(start.getDate() - 6);\n return { start: startOfDay(start), end };\n }\n case 'LAST_30_DAYS': {\n const start = new Date(now);\n start.setDate(start.getDate() - 29);\n return { start: startOfDay(start), end };\n }\n case 'LAST_90_DAYS': {\n const start = new Date(now);\n start.setDate(start.getDate() - 89);\n return { start: startOfDay(start), end };\n }\n case 'LAST_MONTH': {\n const start = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n const monthEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);\n return { start, end: monthEnd };\n }\n case 'CURRENT_MONTH':\n default:\n return {\n start: new Date(now.getFullYear(), now.getMonth(), 1),\n end,\n };\n }\n}\n\nfunction isWithinRange(dateValue: string | null | undefined, start: Date, end: Date): boolean {\n if (!dateValue) {\n return false;\n }\n\n const date = new Date(dateValue);\n return date >= start && date <= end;\n}\n\nfunction normalizeRecurringAmount(\n amount: number,\n duration: PlanDuration | null | undefined\n): number {\n if (duration === PlanDuration.Yearly) {\n return amount / 12;\n }\n\n return amount;\n}\n\nfunction getBucketLabel(dateValue: Date, period: AnalyticsPeriod): string {\n if (period === 'LAST_90_DAYS') {\n return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric' }).format(dateValue);\n }\n\n return new Intl.DateTimeFormat('en-US', {\n month: 'short',\n day: 'numeric',\n }).format(dateValue);\n}\n\nfunction buildRevenueSeries(\n payments: Array<{ amount: number; paidAt?: string | null; createdAt: string }>,\n period: AnalyticsPeriod,\n start: Date,\n end: Date\n): RevenuePoint[] {\n const buckets = new Map<string, number>();\n\n payments.forEach((payment) => {\n const paymentDate = new Date(payment.paidAt || payment.createdAt);\n if (paymentDate < start || paymentDate > end) {\n return;\n }\n\n const label = getBucketLabel(paymentDate, period);\n buckets.set(label, (buckets.get(label) ?? 0) + payment.amount);\n });\n\n return Array.from(buckets.entries()).map(([label, value]) => ({ label, value }));\n}\n\nfunction buildSubscriptionGrowthSeries(\n subscriptions: Array<{ createdAt: string }>,\n period: AnalyticsPeriod,\n start: Date,\n end: Date\n): RevenuePoint[] {\n const buckets = new Map<string, number>();\n\n subscriptions.forEach((subscription) => {\n const createdAt = new Date(subscription.createdAt);\n if (createdAt < start || createdAt > end) {\n return;\n }\n\n const label = getBucketLabel(createdAt, period);\n buckets.set(label, (buckets.get(label) ?? 0) + 1);\n });\n\n return Array.from(buckets.entries()).map(([label, value]) => ({ label, value }));\n}\n\nfunction formatMetricCurrency(value: number, currency: string): string {\n try {\n return formatCurrency(value, currency);\n } catch {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: 2,\n }).format(value);\n }\n}\n\nexport const AnalyticsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [period, setPeriod] = useState<AnalyticsPeriod>('LAST_30_DAYS' as AnalyticsPeriod);\n const { start, end } = useMemo(() => getPeriodRange(period), [period]);\n\n const { data, loading, error, refetch } = useGetBillingAnalyticsPageDataQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n const billingAccounts = useMemo<BillingAnalyticsAccount[]>(\n () => data?.getBillingAccountsByOrg ?? [],\n [data?.getBillingAccountsByOrg]\n );\n const successfulPayments = useMemo<BillingAnalyticsPayment[]>(\n () => data?.getPayments ?? [],\n [data?.getPayments]\n );\n\n const subscriptions = useMemo<BillingAnalyticsSubscription[]>(\n () =>\n billingAccounts.flatMap((account: BillingAnalyticsAccount) =>\n (account.subscriptions ?? []).filter(\n (subscription): subscription is BillingAnalyticsSubscription => Boolean(subscription)\n )\n ),\n [billingAccounts]\n );\n\n const activeSubscriptions = useMemo<BillingAnalyticsSubscription[]>(\n () =>\n subscriptions.filter((subscription: BillingAnalyticsSubscription) =>\n ACTIVE_SUBSCRIPTION_STATUSES.has(subscription.status)\n ),\n [subscriptions]\n );\n\n const displayCurrency =\n activeSubscriptions.find((subscription) => subscription.plan?.currency)?.plan?.currency ||\n successfulPayments[0]?.currency ||\n billingAccounts[0]?.currency ||\n 'USD';\n\n const metrics = useMemo(() => {\n const mrr = activeSubscriptions.reduce(\n (sum: number, subscription: BillingAnalyticsSubscription) => {\n const planPrice = subscription.plan?.price ?? 0;\n const seatCount =\n subscription.seatCount && subscription.seatCount > 0 ? subscription.seatCount : 1;\n return sum + normalizeRecurringAmount(planPrice, subscription.plan?.duration) * seatCount;\n },\n 0\n );\n\n const arr = mrr * 12;\n const totalCustomers = billingAccounts.length;\n const newCustomers = billingAccounts.filter((account: BillingAnalyticsAccount) =>\n isWithinRange(account.createdAt, start, end)\n ).length;\n const churnedSubscriptions = subscriptions.filter(\n (subscription: BillingAnalyticsSubscription) =>\n (subscription.status === SubscriptionStatus.Canceled ||\n subscription.status === SubscriptionStatus.Expired) &&\n isWithinRange(subscription.canceledAt || subscription.endDate, start, end)\n ).length;\n\n const churnBase = activeSubscriptions.length + churnedSubscriptions;\n const churnRate = churnBase > 0 ? (churnedSubscriptions / churnBase) * 100 : 0;\n const arpu = totalCustomers > 0 ? mrr / totalCustomers : 0;\n const ltv = churnRate > 0 ? arpu / (churnRate / 100) : arr;\n\n return {\n mrr,\n arr,\n arpu,\n churnRate,\n ltv,\n totalCustomers,\n newCustomers,\n activeSubscriptions: activeSubscriptions.length,\n };\n }, [activeSubscriptions, billingAccounts, start, end, subscriptions]);\n\n const revenueSeries = useMemo(\n () => buildRevenueSeries(successfulPayments, period, start, end),\n [successfulPayments, period, start, end]\n );\n\n const subscriptionGrowthSeries = useMemo(\n () => buildSubscriptionGrowthSeries(subscriptions, period, start, end),\n [period, start, end, subscriptions]\n );\n\n const revenueByPlan = useMemo<PlanBreakdown[]>(() => {\n const planMap = new Map<string, PlanBreakdown>();\n\n activeSubscriptions.forEach((subscription: BillingAnalyticsSubscription) => {\n const planName = subscription.plan?.name || 'Unknown Plan';\n const seatCount =\n subscription.seatCount && subscription.seatCount > 0 ? subscription.seatCount : 1;\n const estimatedMrr =\n normalizeRecurringAmount(subscription.plan?.price ?? 0, subscription.plan?.duration) *\n seatCount;\n\n const current = planMap.get(planName);\n planMap.set(planName, {\n name: planName,\n activeSubscriptions: (current?.activeSubscriptions ?? 0) + 1,\n estimatedMrr: (current?.estimatedMrr ?? 0) + estimatedMrr,\n });\n });\n\n return Array.from(planMap.values()).sort(\n (left: PlanBreakdown, right: PlanBreakdown) => right.estimatedMrr - left.estimatedMrr\n );\n }, [activeSubscriptions]);\n\n const topCustomers = useMemo<CustomerSummary[]>(() => {\n return billingAccounts\n .map((account: BillingAnalyticsAccount) => {\n const accountSubscriptions = (account.subscriptions ?? []).filter(\n (subscription): subscription is BillingAnalyticsSubscription => {\n if (!subscription) {\n return false;\n }\n\n return ACTIVE_SUBSCRIPTION_STATUSES.has(subscription.status);\n }\n );\n\n const estimatedMrr = accountSubscriptions.reduce(\n (sum: number, subscription: BillingAnalyticsSubscription) => {\n const seatCount =\n subscription.seatCount && subscription.seatCount > 0 ? subscription.seatCount : 1;\n return (\n sum +\n normalizeRecurringAmount(subscription.plan?.price ?? 0, subscription.plan?.duration) *\n seatCount\n );\n },\n 0\n );\n\n return {\n id: account.id ?? account.name ?? account.email ?? 'unknown-account',\n name: account.name ?? 'Unnamed account',\n email: account.email ?? 'No contact email',\n activeSubscriptions: accountSubscriptions.length,\n estimatedMrr,\n };\n })\n .sort(\n (left: CustomerSummary, right: CustomerSummary) => right.estimatedMrr - left.estimatedMrr\n )\n .slice(0, 5);\n }, [billingAccounts]);\n\n const maxRevenuePoint = Math.max(...revenueSeries.map((item) => item.value), 0);\n const maxGrowthPoint = Math.max(...subscriptionGrowthSeries.map((item) => item.value), 0);\n\n if (!permissions.canViewAnalytics) {\n return <AccessDenied message=\"You don't have permission to view analytics.\" />;\n }\n\n return (\n <div className=\"space-y-6 p-4 sm:p-6\">\n <PageHeader\n title=\"Revenue Analytics\"\n description=\"Track recurring revenue, customer growth, and billing performance using live billing data.\"\n actions={\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <select\n value={period}\n onChange={(event) => setPeriod(event.target.value as AnalyticsPeriod)}\n aria-label=\"Analytics period\"\n className=\"rounded-input border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {PERIOD_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: 'Refresh',\n onSelect: () => {\n void refetch();\n },\n icon: <RefreshCw className=\"size-4\" />,\n },\n ]}\n />\n </div>\n }\n />\n\n <PagePurpose>\n Understand the financial health of your business at a glance — recurring revenue (MRR/ARR),\n customer growth, churn and your highest-value customers — all computed from live billing\n data. Use the period selector to compare timeframes and spot trends.\n </PagePurpose>\n\n {error && (\n <div className=\"flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n <div>\n <p className=\"font-medium\">Unable to load billing analytics</p>\n <p className=\"mt-1 text-sm\">{error.message}</p>\n </div>\n </div>\n )}\n\n {loading && billingAccounts.length === 0 ? (\n <div className=\"flex min-h-[320px] items-center justify-center rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"flex flex-col items-center gap-3 text-text-muted\">\n <RefreshCw className=\"size-6 animate-spin\" />\n <p className=\"text-sm\">Loading live billing analytics…</p>\n </div>\n </div>\n ) : billingAccounts.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No billing data available yet\"\n description=\"Create a billing account and complete a subscription or payment to unlock live analytics.\"\n />\n ) : (\n <>\n {/* Headline revenue metrics — the page's primary emphasis zone */}\n <EmphasisPanel className=\"p-4 sm:p-5\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard\n label=\"MRR\"\n value={formatMetricCurrency(metrics.mrr, displayCurrency)}\n icon={<TrendingUp className=\"size-5\" />}\n />\n <StatCard\n label=\"ARR\"\n value={formatMetricCurrency(metrics.arr, displayCurrency)}\n icon={<BarChart3 className=\"size-5\" />}\n />\n <StatCard\n label=\"ARPU\"\n value={formatMetricCurrency(metrics.arpu, displayCurrency)}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n label=\"Churn Rate\"\n value={formatPercentage(metrics.churnRate)}\n icon={<Users className=\"size-5\" />}\n />\n </div>\n </EmphasisPanel>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Total Customers\" value={metrics.totalCustomers} />\n <StatCard label=\"New Customers\" value={metrics.newCustomers} />\n <StatCard label=\"Active Subscriptions\" value={metrics.activeSubscriptions} />\n <StatCard\n label=\"Estimated LTV\"\n value={formatMetricCurrency(metrics.ltv, displayCurrency)}\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <section className=\"rounded-card border border-border-seam bg-bg-surface p-4 sm:p-6 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">Revenue over time</h3>\n <p className=\"text-sm text-text-muted\">\n Successful payments received during the selected period.\n </p>\n </div>\n <span className=\"rounded-full bg-status-info-bg-subtle px-2.5 py-1 text-xs font-medium text-status-info-text\">\n {revenueSeries.length} data point{revenueSeries.length === 1 ? '' : 's'}\n </span>\n </div>\n\n {revenueSeries.length === 0 ? (\n <p className=\"rounded-lg bg-bg-sunken/40 p-4 text-sm text-text-muted\">\n No successful payments were recorded during this period.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {revenueSeries.map((point) => (\n <div key={point.label} className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"font-medium text-text-primary\">{point.label}</span>\n <span className=\"text-text-muted\">\n {formatMetricCurrency(point.value, displayCurrency)}\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg transition-[width]\"\n style={{\n width: `${maxRevenuePoint > 0 ? (point.value / maxRevenuePoint) * 100 : 0}%`,\n }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </section>\n\n <section className=\"rounded-card border border-border-seam bg-bg-surface p-4 sm:p-6 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div>\n <h3 className=\"font-semibold text-text-primary\">Subscription growth</h3>\n <p className=\"text-sm text-text-muted\">\n New subscriptions created during the selected period.\n </p>\n </div>\n <span className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text\">\n {subscriptionGrowthSeries.reduce((sum, point) => sum + point.value, 0)} created\n </span>\n </div>\n\n {subscriptionGrowthSeries.length === 0 ? (\n <p className=\"rounded-lg bg-bg-sunken/40 p-4 text-sm text-text-muted\">\n No new subscriptions were created during this period.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {subscriptionGrowthSeries.map((point) => (\n <div key={point.label} className=\"space-y-1\">\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"font-medium text-text-primary\">{point.label}</span>\n <span className=\"text-text-muted\">{point.value} new</span>\n </div>\n <div className=\"h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-status-success-bg transition-[width]\"\n style={{\n width: `${maxGrowthPoint > 0 ? (point.value / maxGrowthPoint) * 100 : 0}%`,\n }}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </section>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <section className=\"rounded-card border border-border-seam bg-bg-surface shadow-[var(--shadow-elevation-1)]\">\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"font-semibold text-text-primary\">Revenue by plan</h3>\n <p className=\"mt-1 text-sm text-text-muted\">\n Estimated monthly recurring revenue based on currently active subscriptions.\n </p>\n </div>\n <div className=\"p-4\">\n {revenueByPlan.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No active plans to analyse yet.</p>\n ) : (\n <div className=\"space-y-3\">\n {revenueByPlan.map((plan) => (\n <div\n key={plan.name}\n className=\"flex flex-col gap-2 rounded-lg border border-border-subtle p-3 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div>\n <p className=\"font-medium text-text-primary\">{plan.name}</p>\n <p className=\"text-sm text-text-muted\">\n {plan.activeSubscriptions} active subscription\n {plan.activeSubscriptions === 1 ? '' : 's'}\n </p>\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {formatMetricCurrency(plan.estimatedMrr, displayCurrency)} / month\n </p>\n </div>\n ))}\n </div>\n )}\n </div>\n </section>\n\n <section className=\"rounded-card border border-border-seam bg-bg-surface shadow-[var(--shadow-elevation-1)]\">\n <div className=\"border-b border-border-subtle p-4\">\n <h3 className=\"font-semibold text-text-primary\">Top customers</h3>\n <p className=\"mt-1 text-sm text-text-muted\">\n Ranked by estimated monthly recurring revenue from active subscriptions.\n </p>\n </div>\n <div className=\"p-4\">\n {topCustomers.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No customer revenue data available yet.</p>\n ) : (\n <div className=\"space-y-3\">\n {topCustomers.map((customer) => (\n <div\n key={customer.id}\n className=\"flex flex-col gap-2 rounded-lg border border-border-subtle p-3 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{customer.name}</p>\n <p className=\"truncate text-sm text-text-muted\">{customer.email}</p>\n </div>\n <div className=\"text-left sm:text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {formatMetricCurrency(customer.estimatedMrr, displayCurrency)} / month\n </p>\n <p className=\"text-xs text-text-muted\">\n {customer.activeSubscriptions} active subscription\n {customer.activeSubscriptions === 1 ? '' : 's'}\n </p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </section>\n </div>\n </>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -202,7 +202,7 @@ var C = 25, w = ["stripe", "razorpay"], T = new Set([
202
202
  }), /* @__PURE__ */ b("select", {
203
203
  value: m.processed === void 0 ? "" : String(m.processed),
204
204
  onChange: (e) => I("processed", e.target.value === "" ? void 0 : e.target.value === "true"),
205
- className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
205
+ className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
206
206
  children: [
207
207
  /* @__PURE__ */ y("option", {
208
208
  value: "",
@@ -224,7 +224,7 @@ var C = 25, w = ["stripe", "razorpay"], T = new Set([
224
224
  }), /* @__PURE__ */ b("select", {
225
225
  value: m.provider || "",
226
226
  onChange: (e) => I("provider", e.target.value || void 0),
227
- className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
227
+ className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
228
228
  children: [/* @__PURE__ */ y("option", {
229
229
  value: "",
230
230
  children: "All Providers"
@@ -239,7 +239,7 @@ var C = 25, w = ["stripe", "razorpay"], T = new Set([
239
239
  }), /* @__PURE__ */ b("select", {
240
240
  value: m.hasError === void 0 ? "" : String(m.hasError),
241
241
  onChange: (e) => I("hasError", e.target.value === "" ? void 0 : e.target.value === "true"),
242
- className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface",
242
+ className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
243
243
  children: [
244
244
  /* @__PURE__ */ y("option", {
245
245
  value: "",