@burdenoff/microfe-billing 2026.821.1 → 2026.825.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  2. package/dist/billing/BillingRoot.js.map +1 -1
  3. package/dist/billing/BillingUserRoutes.js.map +1 -1
  4. package/dist/billing/hooks/useBillingAccountSelection.js.map +1 -1
  5. package/dist/billing/hooks/useBillingEventEmitter.js.map +1 -1
  6. package/dist/billing/hooks/useBillingNavigate.js.map +1 -1
  7. package/dist/billing/hooks/useBillingPermissions.js.map +1 -1
  8. package/dist/billing/hooks/useDefaultBillingCurrency.js.map +1 -1
  9. package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
  10. package/dist/billing/modules/addons/hooks/useAddons.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  12. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  14. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  15. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  16. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  17. package/dist/billing/modules/addons/store/addonCartStore.js.map +1 -1
  18. package/dist/billing/modules/affiliate/api.js.map +1 -1
  19. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +107 -107
  20. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  21. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +80 -81
  22. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/hooks/useInvoices.js.map +1 -1
  24. package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
  25. package/dist/billing/modules/billing/hooks/useTransactions.js.map +1 -1
  26. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  27. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  28. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  29. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  30. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  31. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  32. package/dist/billing/modules/billing/pages/UserRefundsPage.js +127 -127
  33. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  34. package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
  35. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  36. package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
  37. package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
  38. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  39. package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
  40. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  41. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
  42. package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
  43. package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
  44. package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
  45. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
  46. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  47. package/dist/billing/modules/checkout/hooks/useCheckout.js +0 -1
  48. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  49. package/dist/billing/modules/checkout/hooks/useNativeCheckout.js.map +1 -1
  50. package/dist/billing/modules/checkout/hooks/usePaymentProviders.js.map +1 -1
  51. package/dist/billing/modules/checkout/hooks/usePaymentStatusPolling.js.map +1 -1
  52. package/dist/billing/modules/checkout/hooks/useStoreShipping.js.map +1 -1
  53. package/dist/billing/modules/checkout/pages/CheckoutPage.js +412 -395
  54. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  55. package/dist/billing/modules/checkout/utils/storeOrder.js.map +1 -1
  56. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  57. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  58. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  59. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  60. package/dist/billing/modules/credits/hooks/useCredits.js.map +1 -1
  61. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  62. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  63. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  64. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  65. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
  66. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  67. package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
  68. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  69. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
  70. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  71. package/dist/billing/modules/dashboard/hooks/useBillingOverviewData.js.map +1 -1
  72. package/dist/billing/modules/dashboard/hooks/useDashboard.js.map +1 -1
  73. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  74. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  75. package/dist/billing/modules/earnings/api.js.map +1 -1
  76. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +145 -145
  77. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
  78. package/dist/billing/modules/earnings/pages/EarningsPage.js +133 -133
  79. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  80. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +7 -9
  81. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  82. package/dist/billing/modules/earnings/pages/PayoutsPage.js +95 -95
  83. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  84. package/dist/billing/modules/plans/constants/featuredQuotas.js.map +1 -1
  85. package/dist/billing/modules/plans/hooks/usePlans.js.map +1 -1
  86. package/dist/billing/modules/plans/hooks/useQuotas.js.map +1 -1
  87. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  88. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  89. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  90. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  91. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  92. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  93. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  94. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  95. package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
  96. package/dist/billing/modules/quota/hooks/useQuotaTemplates.js.map +1 -1
  97. package/dist/billing/modules/quota/pages/QuotaAssignFreePage.js.map +1 -1
  98. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  99. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  100. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  101. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  102. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  103. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  104. package/dist/billing/modules/store/pages/StoreCheckoutPage.js +5 -6
  105. package/dist/billing/modules/store/pages/StoreCheckoutPage.js.map +1 -1
  106. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  107. package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
  108. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  109. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  110. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  111. package/dist/billing/modules/usage/hooks/useUsage.js +14 -9
  112. package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
  113. package/dist/billing/modules/usage/pages/UsagePage.js +481 -453
  114. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  115. package/dist/billing/providers/BillingProvider.js.map +1 -1
  116. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  117. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  118. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  119. package/dist/billing/shared/components/EmptyState.js.map +1 -1
  120. package/dist/billing/shared/components/PageHeader.js.map +1 -1
  121. package/dist/billing/shared/components/ServerError.js.map +1 -1
  122. package/dist/billing/shared/components/StatCard.js.map +1 -1
  123. package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -1
  124. package/dist/billing/shared/types/graphql.js.map +1 -1
  125. package/dist/billing/shared/ui/Card.js.map +1 -1
  126. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  127. package/dist/billing/shared/ui/ProgressBar.js.map +1 -1
  128. package/dist/billing/shared/ui/StatusPill.js.map +1 -1
  129. package/dist/billing/shared/utils/directBillingGraphql.js.map +1 -1
  130. package/dist/billing/shared/utils/error.js.map +1 -1
  131. package/dist/billing/shared/utils/format.js.map +1 -1
  132. package/dist/billing/shared/utils/gatewayGraphql.js.map +1 -1
  133. package/dist/billing/shared/utils/navigation.js.map +1 -1
  134. package/dist/billing/shared/utils/paymentTheme.js.map +1 -1
  135. package/dist/billing/shared/utils/planFeatureQuantity.js.map +1 -1
  136. package/dist/billing/shared/utils/status.js.map +1 -1
  137. package/dist/billing/shared/utils/tokens.js.map +1 -1
  138. package/dist/generated/global-operations.js +5 -1
  139. package/dist/generated/global-operations.js.map +1 -1
  140. package/dist/generated/global-types.js.map +1 -1
  141. package/dist/generated/wspace-operations.js +1 -1
  142. package/dist/generated/wspace-operations.js.map +1 -1
  143. package/dist/node_modules/@capacitor/core/dist/index.js +1 -1
  144. package/dist/node_modules/@capacitor/core/dist/index.js.map +1 -1
  145. package/package.json +13 -10
@@ -9,10 +9,9 @@ import { PlanDuration as s, SubscriptionStatus as c } from "../../../../generate
9
9
  import { useMemo as l, useState as u } from "react";
10
10
  import { AlertCircle as d, BarChart3 as f, RefreshCw as p, TrendingUp as m, Users as h, Wallet as g } from "lucide-react";
11
11
  import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
12
- import { useI18n as b } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
- import { CTAOverflowMenu as x, EmphasisPanel as S, IllustratedEmptyState as C, PagePurpose as w } from "@burdenoff/fe-libs/ui";
12
+ import { CTAOverflowMenu as b, EmphasisPanel as x, IllustratedEmptyState as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
14
13
  //#region src/billing/modules/analytics/pages/AnalyticsPage.tsx
15
- var T = [
14
+ var w = [
16
15
  {
17
16
  value: "LAST_7_DAYS",
18
17
  label: "Last 7 days"
@@ -33,37 +32,37 @@ var T = [
33
32
  value: "LAST_MONTH",
34
33
  label: "Last month"
35
34
  }
36
- ], E = new Set([
35
+ ], T = new Set([
37
36
  c.Active,
38
37
  c.Trialing,
39
38
  c.PastDue,
40
39
  c.Paused
41
40
  ]);
42
- function D(e) {
41
+ function E(e) {
43
42
  let t = new Date(e);
44
43
  return t.setHours(0, 0, 0, 0), t;
45
44
  }
46
- function O(e) {
45
+ function D(e) {
47
46
  let t = /* @__PURE__ */ new Date(), n = t;
48
47
  switch (e) {
49
48
  case "LAST_7_DAYS": {
50
49
  let e = new Date(t);
51
50
  return e.setDate(e.getDate() - 6), {
52
- start: D(e),
51
+ start: E(e),
53
52
  end: n
54
53
  };
55
54
  }
56
55
  case "LAST_30_DAYS": {
57
56
  let e = new Date(t);
58
57
  return e.setDate(e.getDate() - 29), {
59
- start: D(e),
58
+ start: E(e),
60
59
  end: n
61
60
  };
62
61
  }
63
62
  case "LAST_90_DAYS": {
64
63
  let e = new Date(t);
65
64
  return e.setDate(e.getDate() - 89), {
66
- start: D(e),
65
+ start: E(e),
67
66
  end: n
68
67
  };
69
68
  }
@@ -77,45 +76,45 @@ function O(e) {
77
76
  };
78
77
  }
79
78
  }
80
- function k(e, t, n) {
79
+ function O(e, t, n) {
81
80
  if (!e) return !1;
82
81
  let r = new Date(e);
83
82
  return r >= t && r <= n;
84
83
  }
85
- function A(e, t) {
84
+ function k(e, t) {
86
85
  return t === s.Yearly ? e / 12 : e;
87
86
  }
88
- function j(e, t) {
87
+ function A(e, t) {
89
88
  return new Intl.DateTimeFormat("en-US", {
90
89
  month: "short",
91
90
  day: "numeric"
92
91
  }).format(e);
93
92
  }
94
- function M(e, t, n, r) {
93
+ function j(e, t, n, r) {
95
94
  let i = /* @__PURE__ */ new Map();
96
95
  return e.forEach((e) => {
97
96
  let a = new Date(e.paidAt || e.createdAt);
98
97
  if (a < n || a > r) return;
99
- let o = j(a, t);
98
+ let o = A(a, t);
100
99
  i.set(o, (i.get(o) ?? 0) + e.amount);
101
100
  }), Array.from(i.entries()).map(([e, t]) => ({
102
101
  label: e,
103
102
  value: t
104
103
  }));
105
104
  }
106
- function N(e, t, n, r) {
105
+ function M(e, t, n, r) {
107
106
  let i = /* @__PURE__ */ new Map();
108
107
  return e.forEach((e) => {
109
108
  let a = new Date(e.createdAt);
110
109
  if (a < n || a > r) return;
111
- let o = j(a, t);
110
+ let o = A(a, t);
112
111
  i.set(o, (i.get(o) ?? 0) + 1);
113
112
  }), Array.from(i.entries()).map(([e, t]) => ({
114
113
  label: e,
115
114
  value: t
116
115
  }));
117
116
  }
118
- function P(e, t) {
117
+ function N(e, t) {
119
118
  try {
120
119
  return r(e, t);
121
120
  } catch {
@@ -126,12 +125,12 @@ function P(e, t) {
126
125
  }).format(e);
127
126
  }
128
127
  }
129
- var F = () => {
130
- let { t: r } = b(), s = a(), [D, j] = u("LAST_30_DAYS"), { start: F, end: I } = l(() => O(D), [D]), { data: L, loading: R, error: z, refetch: B } = o({ fetchPolicy: "cache-and-network" }), V = l(() => L?.getBillingAccountsByOrg ?? [], [L?.getBillingAccountsByOrg]), H = l(() => L?.getPayments ?? [], [L?.getPayments]), U = l(() => V.flatMap((e) => (e.subscriptions ?? []).filter((e) => !!e)), [V]), W = l(() => U.filter((e) => E.has(e.status)), [U]), G = W.find((e) => e.plan?.currency)?.plan?.currency || H[0]?.currency || V[0]?.currency || "USD", K = l(() => {
131
- let e = W.reduce((e, t) => {
128
+ var P = () => {
129
+ let r = a(), [s, E] = u("LAST_30_DAYS"), { start: A, end: P } = l(() => D(s), [s]), { data: F, loading: I, error: L, refetch: R } = o({ fetchPolicy: "cache-and-network" }), z = l(() => F?.getBillingAccountsByOrg ?? [], [F?.getBillingAccountsByOrg]), B = l(() => F?.getPayments ?? [], [F?.getPayments]), V = l(() => z.flatMap((e) => (e.subscriptions ?? []).filter((e) => !!e)), [z]), H = l(() => V.filter((e) => T.has(e.status)), [V]), U = H.find((e) => e.plan?.currency)?.plan?.currency || B[0]?.currency || z[0]?.currency || "USD", W = l(() => {
130
+ let e = H.reduce((e, t) => {
132
131
  let n = t.plan?.price ?? 0, r = t.seatCount && t.seatCount > 0 ? t.seatCount : 1;
133
- return e + A(n, t.plan?.duration) * r;
134
- }, 0), t = e * 12, n = V.length, r = V.filter((e) => k(e.createdAt, F, I)).length, i = U.filter((e) => (e.status === c.Canceled || e.status === c.Expired) && k(e.canceledAt || e.endDate, F, I)).length, a = W.length + i, o = a > 0 ? i / a * 100 : 0, s = n > 0 ? e / n : 0;
132
+ return e + k(n, t.plan?.duration) * r;
133
+ }, 0), t = e * 12, n = z.length, r = z.filter((e) => O(e.createdAt, A, P)).length, i = V.filter((e) => (e.status === c.Canceled || e.status === c.Expired) && O(e.canceledAt || e.endDate, A, P)).length, a = H.length + i, o = a > 0 ? i / a * 100 : 0, s = n > 0 ? e / n : 0;
135
134
  return {
136
135
  mrr: e,
137
136
  arr: t,
@@ -140,38 +139,38 @@ var F = () => {
140
139
  ltv: o > 0 ? s / (o / 100) : t,
141
140
  totalCustomers: n,
142
141
  newCustomers: r,
143
- activeSubscriptions: W.length
142
+ activeSubscriptions: H.length
144
143
  };
145
144
  }, [
146
- W,
147
- V,
148
- F,
149
- I,
150
- U
151
- ]), q = l(() => M(H, D, F, I), [
152
145
  H,
153
- D,
154
- F,
155
- I
156
- ]), J = l(() => N(U, D, F, I), [
157
- D,
158
- F,
159
- I,
160
- U
161
- ]), Y = l(() => {
146
+ z,
147
+ A,
148
+ P,
149
+ V
150
+ ]), G = l(() => j(B, s, A, P), [
151
+ B,
152
+ s,
153
+ A,
154
+ P
155
+ ]), K = l(() => M(V, s, A, P), [
156
+ s,
157
+ A,
158
+ P,
159
+ V
160
+ ]), q = l(() => {
162
161
  let e = /* @__PURE__ */ new Map();
163
- return W.forEach((t) => {
164
- let n = t.plan?.name || "Unknown Plan", r = t.seatCount && t.seatCount > 0 ? t.seatCount : 1, i = A(t.plan?.price ?? 0, t.plan?.duration) * r, a = e.get(n);
162
+ return H.forEach((t) => {
163
+ let n = t.plan?.name || "Unknown Plan", r = t.seatCount && t.seatCount > 0 ? t.seatCount : 1, i = k(t.plan?.price ?? 0, t.plan?.duration) * r, a = e.get(n);
165
164
  e.set(n, {
166
165
  name: n,
167
166
  activeSubscriptions: (a?.activeSubscriptions ?? 0) + 1,
168
167
  estimatedMrr: (a?.estimatedMrr ?? 0) + i
169
168
  });
170
169
  }), Array.from(e.values()).sort((e, t) => t.estimatedMrr - e.estimatedMrr);
171
- }, [W]), X = l(() => V.map((e) => {
172
- let t = (e.subscriptions ?? []).filter((e) => e ? E.has(e.status) : !1), n = t.reduce((e, t) => {
170
+ }, [H]), J = l(() => z.map((e) => {
171
+ let t = (e.subscriptions ?? []).filter((e) => e ? T.has(e.status) : !1), n = t.reduce((e, t) => {
173
172
  let n = t.seatCount && t.seatCount > 0 ? t.seatCount : 1;
174
- return e + A(t.plan?.price ?? 0, t.plan?.duration) * n;
173
+ return e + k(t.plan?.price ?? 0, t.plan?.duration) * n;
175
174
  }, 0);
176
175
  return {
177
176
  id: e.id ?? e.name ?? e.email ?? "unknown-account",
@@ -180,8 +179,8 @@ var F = () => {
180
179
  activeSubscriptions: t.length,
181
180
  estimatedMrr: n
182
181
  };
183
- }).sort((e, t) => t.estimatedMrr - e.estimatedMrr).slice(0, 5), [V]), Z = Math.max(...q.map((e) => e.value), 0), Q = Math.max(...J.map((e) => e.value), 0);
184
- return s.canViewAnalytics ? /* @__PURE__ */ y("div", {
182
+ }).sort((e, t) => t.estimatedMrr - e.estimatedMrr).slice(0, 5), [z]), Y = Math.max(...G.map((e) => e.value), 0), X = Math.max(...K.map((e) => e.value), 0);
183
+ return r.canViewAnalytics ? /* @__PURE__ */ y("div", {
185
184
  className: "space-y-6 p-4 sm:p-6",
186
185
  children: [
187
186
  /* @__PURE__ */ v(t, {
@@ -190,35 +189,35 @@ var F = () => {
190
189
  actions: /* @__PURE__ */ y("div", {
191
190
  className: "flex flex-col gap-2 sm:flex-row sm:items-center",
192
191
  children: [/* @__PURE__ */ v("select", {
193
- value: D,
194
- onChange: (e) => j(e.target.value),
192
+ value: s,
193
+ onChange: (e) => E(e.target.value),
195
194
  "aria-label": "Analytics period",
196
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)]",
197
- children: T.map((e) => /* @__PURE__ */ v("option", {
196
+ children: w.map((e) => /* @__PURE__ */ v("option", {
198
197
  value: e.value,
199
198
  children: e.label
200
199
  }, e.value))
201
- }), /* @__PURE__ */ v(x, { actions: [{
200
+ }), /* @__PURE__ */ v(b, { actions: [{
202
201
  label: "Refresh",
203
202
  onSelect: () => {
204
- B();
203
+ R();
205
204
  },
206
205
  icon: /* @__PURE__ */ v(p, { className: "size-4" })
207
206
  }] })]
208
207
  })
209
208
  }),
210
- /* @__PURE__ */ v(w, { children: "Understand the financial health of your business at a glance — recurring revenue (MRR/ARR), customer growth, churn and your highest-value customers — all computed from live billing data. Use the period selector to compare timeframes and spot trends." }),
211
- z && /* @__PURE__ */ y("div", {
209
+ /* @__PURE__ */ v(C, { children: "Understand the financial health of your business at a glance — recurring revenue (MRR/ARR), customer growth, churn and your highest-value customers — all computed from live billing data. Use the period selector to compare timeframes and spot trends." }),
210
+ L && /* @__PURE__ */ y("div", {
212
211
  className: "flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text",
213
212
  children: [/* @__PURE__ */ v(d, { className: "mt-0.5 size-4 shrink-0" }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
214
213
  className: "font-medium",
215
214
  children: "Unable to load billing analytics"
216
215
  }), /* @__PURE__ */ v("p", {
217
216
  className: "mt-1 text-sm",
218
- children: z.message
217
+ children: L.message
219
218
  })] })]
220
219
  }),
221
- R && V.length === 0 ? /* @__PURE__ */ v("div", {
220
+ I && z.length === 0 ? /* @__PURE__ */ v("div", {
222
221
  className: "flex min-h-[320px] items-center justify-center rounded-lg border border-border-subtle bg-bg-surface",
223
222
  children: /* @__PURE__ */ y("div", {
224
223
  className: "flex flex-col items-center gap-3 text-text-muted",
@@ -227,34 +226,34 @@ var F = () => {
227
226
  children: "Loading live billing analytics…"
228
227
  })]
229
228
  })
230
- }) : V.length === 0 ? /* @__PURE__ */ v(C, {
229
+ }) : z.length === 0 ? /* @__PURE__ */ v(S, {
231
230
  illustration: "empty-data",
232
231
  title: "No billing data available yet",
233
232
  description: "Create a billing account and complete a subscription or payment to unlock live analytics."
234
233
  }) : /* @__PURE__ */ y(_, { children: [
235
- /* @__PURE__ */ v(S, {
234
+ /* @__PURE__ */ v(x, {
236
235
  className: "p-4 sm:p-5",
237
236
  children: /* @__PURE__ */ y("div", {
238
237
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4",
239
238
  children: [
240
239
  /* @__PURE__ */ v(n, {
241
240
  label: "MRR",
242
- value: P(K.mrr, G),
241
+ value: N(W.mrr, U),
243
242
  icon: /* @__PURE__ */ v(m, { className: "size-5" })
244
243
  }),
245
244
  /* @__PURE__ */ v(n, {
246
245
  label: "ARR",
247
- value: P(K.arr, G),
246
+ value: N(W.arr, U),
248
247
  icon: /* @__PURE__ */ v(f, { className: "size-5" })
249
248
  }),
250
249
  /* @__PURE__ */ v(n, {
251
250
  label: "ARPU",
252
- value: P(K.arpu, G),
251
+ value: N(W.arpu, U),
253
252
  icon: /* @__PURE__ */ v(g, { className: "size-5" })
254
253
  }),
255
254
  /* @__PURE__ */ v(n, {
256
255
  label: "Churn Rate",
257
- value: i(K.churnRate),
256
+ value: i(W.churnRate),
258
257
  icon: /* @__PURE__ */ v(h, { className: "size-5" })
259
258
  })
260
259
  ]
@@ -265,19 +264,19 @@ var F = () => {
265
264
  children: [
266
265
  /* @__PURE__ */ v(n, {
267
266
  label: "Total Customers",
268
- value: K.totalCustomers
267
+ value: W.totalCustomers
269
268
  }),
270
269
  /* @__PURE__ */ v(n, {
271
270
  label: "New Customers",
272
- value: K.newCustomers
271
+ value: W.newCustomers
273
272
  }),
274
273
  /* @__PURE__ */ v(n, {
275
274
  label: "Active Subscriptions",
276
- value: K.activeSubscriptions
275
+ value: W.activeSubscriptions
277
276
  }),
278
277
  /* @__PURE__ */ v(n, {
279
278
  label: "Estimated LTV",
280
- value: P(K.ltv, G)
279
+ value: N(W.ltv, U)
281
280
  })
282
281
  ]
283
282
  }),
@@ -296,17 +295,17 @@ var F = () => {
296
295
  })] }), /* @__PURE__ */ y("span", {
297
296
  className: "rounded-full bg-status-info-bg-subtle px-2.5 py-1 text-xs font-medium text-status-info-text",
298
297
  children: [
299
- q.length,
298
+ G.length,
300
299
  " data point",
301
- q.length === 1 ? "" : "s"
300
+ G.length === 1 ? "" : "s"
302
301
  ]
303
302
  })]
304
- }), q.length === 0 ? /* @__PURE__ */ v("p", {
303
+ }), G.length === 0 ? /* @__PURE__ */ v("p", {
305
304
  className: "rounded-lg bg-bg-sunken/40 p-4 text-sm text-text-muted",
306
305
  children: "No successful payments were recorded during this period."
307
306
  }) : /* @__PURE__ */ v("div", {
308
307
  className: "space-y-3",
309
- children: q.map((e) => /* @__PURE__ */ y("div", {
308
+ children: G.map((e) => /* @__PURE__ */ y("div", {
310
309
  className: "space-y-1",
311
310
  children: [/* @__PURE__ */ y("div", {
312
311
  className: "flex items-center justify-between gap-3 text-sm",
@@ -315,13 +314,13 @@ var F = () => {
315
314
  children: e.label
316
315
  }), /* @__PURE__ */ v("span", {
317
316
  className: "text-text-muted",
318
- children: P(e.value, G)
317
+ children: N(e.value, U)
319
318
  })]
320
319
  }), /* @__PURE__ */ v("div", {
321
320
  className: "h-2 rounded-full bg-bg-sunken",
322
321
  children: /* @__PURE__ */ v("div", {
323
322
  className: "h-2 rounded-full bg-action-primary-bg transition-[width]",
324
- style: { width: `${Z > 0 ? e.value / Z * 100 : 0}%` }
323
+ style: { width: `${Y > 0 ? e.value / Y * 100 : 0}%` }
325
324
  })
326
325
  })]
327
326
  }, e.label))
@@ -338,14 +337,14 @@ var F = () => {
338
337
  children: "New subscriptions created during the selected period."
339
338
  })] }), /* @__PURE__ */ y("span", {
340
339
  className: "rounded-full bg-status-success-bg-subtle px-2.5 py-1 text-xs font-medium text-status-success-text",
341
- children: [J.reduce((e, t) => e + t.value, 0), " created"]
340
+ children: [K.reduce((e, t) => e + t.value, 0), " created"]
342
341
  })]
343
- }), J.length === 0 ? /* @__PURE__ */ v("p", {
342
+ }), K.length === 0 ? /* @__PURE__ */ v("p", {
344
343
  className: "rounded-lg bg-bg-sunken/40 p-4 text-sm text-text-muted",
345
344
  children: "No new subscriptions were created during this period."
346
345
  }) : /* @__PURE__ */ v("div", {
347
346
  className: "space-y-3",
348
- children: J.map((e) => /* @__PURE__ */ y("div", {
347
+ children: K.map((e) => /* @__PURE__ */ y("div", {
349
348
  className: "space-y-1",
350
349
  children: [/* @__PURE__ */ y("div", {
351
350
  className: "flex items-center justify-between gap-3 text-sm",
@@ -360,7 +359,7 @@ var F = () => {
360
359
  className: "h-2 rounded-full bg-bg-sunken",
361
360
  children: /* @__PURE__ */ v("div", {
362
361
  className: "h-2 rounded-full bg-status-success-bg transition-[width]",
363
- style: { width: `${Q > 0 ? e.value / Q * 100 : 0}%` }
362
+ style: { width: `${X > 0 ? e.value / X * 100 : 0}%` }
364
363
  })
365
364
  })]
366
365
  }, e.label))
@@ -382,12 +381,12 @@ var F = () => {
382
381
  })]
383
382
  }), /* @__PURE__ */ v("div", {
384
383
  className: "p-4",
385
- children: Y.length === 0 ? /* @__PURE__ */ v("p", {
384
+ children: q.length === 0 ? /* @__PURE__ */ v("p", {
386
385
  className: "text-sm text-text-muted",
387
386
  children: "No active plans to analyse yet."
388
387
  }) : /* @__PURE__ */ v("div", {
389
388
  className: "space-y-3",
390
- children: Y.map((e) => /* @__PURE__ */ y("div", {
389
+ children: q.map((e) => /* @__PURE__ */ y("div", {
391
390
  className: "flex flex-col gap-2 rounded-lg border border-border-subtle p-3 sm:flex-row sm:items-center sm:justify-between",
392
391
  children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
393
392
  className: "font-medium text-text-primary",
@@ -401,7 +400,7 @@ var F = () => {
401
400
  ]
402
401
  })] }), /* @__PURE__ */ y("p", {
403
402
  className: "text-sm font-semibold text-text-primary",
404
- children: [P(e.estimatedMrr, G), " / month"]
403
+ children: [N(e.estimatedMrr, U), " / month"]
405
404
  })]
406
405
  }, e.name))
407
406
  })
@@ -419,12 +418,12 @@ var F = () => {
419
418
  })]
420
419
  }), /* @__PURE__ */ v("div", {
421
420
  className: "p-4",
422
- children: X.length === 0 ? /* @__PURE__ */ v("p", {
421
+ children: J.length === 0 ? /* @__PURE__ */ v("p", {
423
422
  className: "text-sm text-text-muted",
424
423
  children: "No customer revenue data available yet."
425
424
  }) : /* @__PURE__ */ v("div", {
426
425
  className: "space-y-3",
427
- children: X.map((e) => /* @__PURE__ */ y("div", {
426
+ children: J.map((e) => /* @__PURE__ */ y("div", {
428
427
  className: "flex flex-col gap-2 rounded-lg border border-border-subtle p-3 sm:flex-row sm:items-center sm:justify-between",
429
428
  children: [/* @__PURE__ */ y("div", {
430
429
  className: "min-w-0",
@@ -439,7 +438,7 @@ var F = () => {
439
438
  className: "text-left sm:text-right",
440
439
  children: [/* @__PURE__ */ y("p", {
441
440
  className: "text-sm font-semibold text-text-primary",
442
- children: [P(e.estimatedMrr, G), " / month"]
441
+ children: [N(e.estimatedMrr, U), " / month"]
443
442
  }), /* @__PURE__ */ y("p", {
444
443
  className: "text-xs text-text-muted",
445
444
  children: [
@@ -459,6 +458,6 @@ var F = () => {
459
458
  }) : /* @__PURE__ */ v(e, { message: "You don't have permission to view analytics." });
460
459
  };
461
460
  //#endregion
462
- export { F as AnalyticsPage };
461
+ export { P as AnalyticsPage };
463
462
 
464
463
  //# sourceMappingURL=AnalyticsPage.js.map
@@ -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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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 { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\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)]\"\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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"}