@burdenoff/microfe-billing 2026.625.4 → 2026.626.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +192 -187
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +322 -317
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +190 -185
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +297 -292
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +60 -75
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +86 -101
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +124 -121
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +103 -111
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +196 -160
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +58 -73
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +65 -75
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +399 -399
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +49 -48
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +144 -151
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +430 -425
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +121 -93
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +154 -152
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +467 -462
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +294 -286
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +123 -117
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +43 -42
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +192 -207
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +40 -56
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +128 -121
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +165 -180
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +103 -100
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +175 -175
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +96 -96
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +42 -40
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +45 -38
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +339 -334
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +80 -74
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +342 -337
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +38 -53
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +60 -75
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +242 -237
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +29 -39
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +1 -0
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +299 -294
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +191 -186
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +308 -303
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +57 -78
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +79 -78
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +15 -13
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +338 -299
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +82 -103
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +59 -54
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +172 -215
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/package.json +2 -2
|
@@ -12,26 +12,27 @@ import "../hooks/index.js";
|
|
|
12
12
|
import { useEffect as p, useRef as m, useState as h } from "react";
|
|
13
13
|
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
14
14
|
import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
15
|
+
import { EmphasisPanel as b, IllustratedEmptyState as x, PagePurpose as S } from "@burdenoff/fe-libs/ui";
|
|
15
16
|
//#region src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx
|
|
16
|
-
var
|
|
17
|
+
var C = ["expired", "upgraded"], w = 10, T = () => {
|
|
17
18
|
let { t } = y(), r = (e, n) => {
|
|
18
19
|
let r = t(e);
|
|
19
20
|
return r === e ? n : r;
|
|
20
|
-
}, i = l(), a = c(), { orgId: o } = e(), { billingAccounts: s, isLoading:
|
|
21
|
+
}, i = l(), a = c(), { orgId: o } = e(), { billingAccounts: s, isLoading: T } = d(), { selectedAccountId: D, setSelectedAccountId: O } = n({
|
|
21
22
|
orgId: o,
|
|
22
23
|
billingAccounts: s,
|
|
23
24
|
syncFromUrl: !0,
|
|
24
25
|
urlParamName: "billingAccountId"
|
|
25
|
-
}), [
|
|
26
|
-
p(() => (clearTimeout(
|
|
27
|
-
N
|
|
28
|
-
}, 400), () => clearTimeout(
|
|
29
|
-
let { subscriptions:
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
return a.canViewSubscriptions ?
|
|
26
|
+
}), [k, A] = h(!1), [j, M] = h("all"), [N, P] = h(""), [F, I] = h(""), [L, R] = h(1), z = m(void 0);
|
|
27
|
+
p(() => (clearTimeout(z.current), z.current = setTimeout(() => {
|
|
28
|
+
I(N), R(1);
|
|
29
|
+
}, 400), () => clearTimeout(z.current)), [N]);
|
|
30
|
+
let { subscriptions: B, activeSubscriptions: V, totalCount: H, totalPages: U, isLoading: W, error: G, refetch: K } = f(D ?? void 0, L, w, F || void 0), q = s.find((e) => e.id === D), J = (e) => {
|
|
31
|
+
O(e.id), A(!1), R(1);
|
|
32
|
+
}, Y = (e) => {
|
|
33
|
+
R(e);
|
|
34
|
+
}, X = B.filter((e) => j === "all" ? !0 : j === "inactive" ? C.includes(e.status) : e.status === j), Z = T || W;
|
|
35
|
+
return a.canViewSubscriptions ? Z && !q ? /* @__PURE__ */ v("div", {
|
|
35
36
|
className: "space-y-6 p-6",
|
|
36
37
|
children: [/* @__PURE__ */ _("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ _("div", {
|
|
37
38
|
className: "space-y-4",
|
|
@@ -50,7 +51,7 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
50
51
|
})
|
|
51
52
|
}, e))
|
|
52
53
|
})]
|
|
53
|
-
}) :
|
|
54
|
+
}) : G ? /* @__PURE__ */ _("div", {
|
|
54
55
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
55
56
|
children: /* @__PURE__ */ v("div", {
|
|
56
57
|
className: "text-center space-y-4",
|
|
@@ -76,11 +77,11 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
76
77
|
}),
|
|
77
78
|
/* @__PURE__ */ _("p", {
|
|
78
79
|
className: "text-sm text-text-secondary",
|
|
79
|
-
children:
|
|
80
|
+
children: G.message
|
|
80
81
|
}),
|
|
81
82
|
/* @__PURE__ */ _("button", {
|
|
82
83
|
type: "button",
|
|
83
|
-
onClick: () =>
|
|
84
|
+
onClick: () => K(),
|
|
84
85
|
className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors",
|
|
85
86
|
children: "Try Again"
|
|
86
87
|
})
|
|
@@ -91,13 +92,20 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
91
92
|
children: [
|
|
92
93
|
/* @__PURE__ */ v("div", {
|
|
93
94
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
94
|
-
children: [/* @__PURE__ */ v("div", { children: [
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
children: [/* @__PURE__ */ v("div", { children: [
|
|
96
|
+
/* @__PURE__ */ _("h1", {
|
|
97
|
+
className: "text-2xl md:text-3xl font-semibold tracking-tight text-text-primary",
|
|
98
|
+
children: r("billing.subscriptions.title", "Subscriptions")
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ _("p", {
|
|
101
|
+
className: "text-sm text-text-secondary mt-1",
|
|
102
|
+
children: r("billing.subscriptions.subtitle", "Manage your active and past subscriptions")
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ _(S, {
|
|
105
|
+
className: "mt-3",
|
|
106
|
+
children: r("billing.subscriptions.purpose", "See every plan a billing account is subscribed to — active, canceled and expired — in one place. Use it to review what you are paying for, when each plan renews or ends, upgrade or cancel a subscription, and start a new one by browsing plans.")
|
|
107
|
+
})
|
|
108
|
+
] }), a.canCreateSubscription && /* @__PURE__ */ v("button", {
|
|
101
109
|
type: "button",
|
|
102
110
|
onClick: () => i("/plans"),
|
|
103
111
|
className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200",
|
|
@@ -119,7 +127,7 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
119
127
|
className: "relative",
|
|
120
128
|
children: [/* @__PURE__ */ v("button", {
|
|
121
129
|
type: "button",
|
|
122
|
-
onClick: () =>
|
|
130
|
+
onClick: () => A(!k),
|
|
123
131
|
className: "w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200",
|
|
124
132
|
children: [/* @__PURE__ */ v("div", {
|
|
125
133
|
className: "flex items-center gap-3",
|
|
@@ -141,14 +149,14 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
141
149
|
className: "text-left",
|
|
142
150
|
children: [/* @__PURE__ */ _("p", {
|
|
143
151
|
className: "text-sm font-medium text-text-primary",
|
|
144
|
-
children:
|
|
152
|
+
children: q?.name || "Select Account"
|
|
145
153
|
}), /* @__PURE__ */ _("p", {
|
|
146
154
|
className: "text-xs text-text-secondary",
|
|
147
|
-
children:
|
|
155
|
+
children: q?.email
|
|
148
156
|
})]
|
|
149
157
|
})]
|
|
150
158
|
}), /* @__PURE__ */ _("svg", {
|
|
151
|
-
className: `size-4 text-text-secondary transition-transform ${
|
|
159
|
+
className: `size-4 text-text-secondary transition-transform ${k ? "rotate-180" : ""}`,
|
|
152
160
|
fill: "none",
|
|
153
161
|
viewBox: "0 0 24 24",
|
|
154
162
|
stroke: "currentColor",
|
|
@@ -159,14 +167,14 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
159
167
|
d: "M19 9l-7 7-7-7"
|
|
160
168
|
})
|
|
161
169
|
})]
|
|
162
|
-
}),
|
|
170
|
+
}), k && /* @__PURE__ */ v(g, { children: [/* @__PURE__ */ _("div", {
|
|
163
171
|
className: "fixed inset-0 z-10",
|
|
164
|
-
onClick: () =>
|
|
172
|
+
onClick: () => A(!1)
|
|
165
173
|
}), /* @__PURE__ */ _("div", {
|
|
166
174
|
className: "absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto",
|
|
167
175
|
children: s.map((e) => /* @__PURE__ */ v("button", {
|
|
168
176
|
type: "button",
|
|
169
|
-
onClick: () =>
|
|
177
|
+
onClick: () => J(e),
|
|
170
178
|
className: "w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors",
|
|
171
179
|
children: [/* @__PURE__ */ v("div", {
|
|
172
180
|
className: "flex items-center gap-3 min-w-0",
|
|
@@ -194,7 +202,7 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
194
202
|
children: e.email
|
|
195
203
|
})]
|
|
196
204
|
})]
|
|
197
|
-
}),
|
|
205
|
+
}), q?.id === e.id && /* @__PURE__ */ _("svg", {
|
|
198
206
|
className: "size-4 text-text-link flex-shrink-0",
|
|
199
207
|
fill: "none",
|
|
200
208
|
viewBox: "0 0 24 24",
|
|
@@ -209,40 +217,34 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
209
217
|
}, e.id))
|
|
210
218
|
})] })]
|
|
211
219
|
}),
|
|
212
|
-
|
|
213
|
-
className: "
|
|
214
|
-
children:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
children: [/* @__PURE__ */ _("p", {
|
|
220
|
+
q && /* @__PURE__ */ _(b, {
|
|
221
|
+
className: "p-5",
|
|
222
|
+
children: /* @__PURE__ */ v("div", {
|
|
223
|
+
className: "grid grid-cols-1 sm:grid-cols-3 gap-4",
|
|
224
|
+
children: [
|
|
225
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
218
226
|
className: "text-sm text-text-secondary",
|
|
219
227
|
children: "Account"
|
|
220
228
|
}), /* @__PURE__ */ _("p", {
|
|
221
229
|
className: "text-lg font-semibold text-text-primary mt-1",
|
|
222
|
-
children:
|
|
223
|
-
})]
|
|
224
|
-
|
|
225
|
-
/* @__PURE__ */ v("div", {
|
|
226
|
-
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5",
|
|
227
|
-
children: [/* @__PURE__ */ _("p", {
|
|
230
|
+
children: q.name
|
|
231
|
+
})] }),
|
|
232
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
228
233
|
className: "text-sm text-text-secondary",
|
|
229
234
|
children: "Active Subscriptions"
|
|
230
235
|
}), /* @__PURE__ */ _("p", {
|
|
231
236
|
className: "text-lg font-semibold tabular-nums text-status-success-text mt-1",
|
|
232
|
-
children:
|
|
233
|
-
})]
|
|
234
|
-
|
|
235
|
-
/* @__PURE__ */ v("div", {
|
|
236
|
-
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5",
|
|
237
|
-
children: [/* @__PURE__ */ _("p", {
|
|
237
|
+
children: V.length
|
|
238
|
+
})] }),
|
|
239
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
238
240
|
className: "text-sm text-text-secondary",
|
|
239
241
|
children: "Credit Balance"
|
|
240
242
|
}), /* @__PURE__ */ v("p", {
|
|
241
243
|
className: "text-lg font-semibold tabular-nums text-text-primary mt-1",
|
|
242
|
-
children: [(
|
|
243
|
-
})]
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
children: [(q.creditAmount || 0).toLocaleString(), " Credits"]
|
|
245
|
+
})] })
|
|
246
|
+
]
|
|
247
|
+
})
|
|
246
248
|
}),
|
|
247
249
|
/* @__PURE__ */ v("div", {
|
|
248
250
|
className: "flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3 sm:gap-4 pb-4 border-b border-border-subtle",
|
|
@@ -256,8 +258,8 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
256
258
|
"inactive"
|
|
257
259
|
].map((e) => /* @__PURE__ */ _("button", {
|
|
258
260
|
type: "button",
|
|
259
|
-
onClick: () =>
|
|
260
|
-
className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${
|
|
261
|
+
onClick: () => M(e),
|
|
262
|
+
className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${j === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
|
|
261
263
|
children: e === "inactive" ? "Inactive" : e
|
|
262
264
|
}, e))
|
|
263
265
|
}),
|
|
@@ -277,70 +279,47 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
277
279
|
}), /* @__PURE__ */ _("input", {
|
|
278
280
|
type: "text",
|
|
279
281
|
placeholder: r("billing.subscriptions.searchPlaceholder", "Search subscriptions..."),
|
|
280
|
-
value:
|
|
281
|
-
onChange: (e) =>
|
|
282
|
+
value: N,
|
|
283
|
+
onChange: (e) => P(e.target.value),
|
|
282
284
|
className: "w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent"
|
|
283
285
|
})]
|
|
284
286
|
}),
|
|
285
287
|
/* @__PURE__ */ v("div", {
|
|
286
288
|
className: "text-sm text-text-secondary",
|
|
287
289
|
children: [
|
|
288
|
-
|
|
290
|
+
H,
|
|
289
291
|
" subscription",
|
|
290
|
-
|
|
292
|
+
H === 1 ? "" : "s",
|
|
291
293
|
" total"
|
|
292
294
|
]
|
|
293
295
|
})
|
|
294
296
|
]
|
|
295
297
|
}),
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
children: /* @__PURE__ */ _("path", {
|
|
307
|
-
strokeLinecap: "round",
|
|
308
|
-
strokeLinejoin: "round",
|
|
309
|
-
strokeWidth: 2,
|
|
310
|
-
d: "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
|
|
311
|
-
})
|
|
312
|
-
})
|
|
313
|
-
}),
|
|
314
|
-
/* @__PURE__ */ _("h3", {
|
|
315
|
-
className: "text-lg font-medium text-text-primary",
|
|
316
|
-
children: r("billing.subscriptions.noSubscriptionsFound", "No subscriptions found")
|
|
317
|
-
}),
|
|
318
|
-
/* @__PURE__ */ _("p", {
|
|
319
|
-
className: "text-sm text-text-secondary mt-1",
|
|
320
|
-
children: O !== "all" || M ? "No subscriptions match your filters." : "You don't have any subscriptions yet."
|
|
321
|
-
}),
|
|
322
|
-
a.canCreateSubscription && O === "all" && !M && /* @__PURE__ */ _("button", {
|
|
323
|
-
type: "button",
|
|
324
|
-
onClick: () => i("/plans"),
|
|
325
|
-
className: "mt-4 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200",
|
|
326
|
-
children: "Browse Plans"
|
|
327
|
-
})
|
|
328
|
-
]
|
|
298
|
+
X.length === 0 ? /* @__PURE__ */ _(x, {
|
|
299
|
+
illustration: j !== "all" || F ? "empty-search" : "empty-data",
|
|
300
|
+
title: r("billing.subscriptions.noSubscriptionsFound", "No subscriptions found"),
|
|
301
|
+
description: j !== "all" || F ? "No subscriptions match your filters." : "You don't have any subscriptions yet.",
|
|
302
|
+
action: a.canCreateSubscription && j === "all" && !F ? /* @__PURE__ */ _("button", {
|
|
303
|
+
type: "button",
|
|
304
|
+
onClick: () => i("/plans"),
|
|
305
|
+
className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200",
|
|
306
|
+
children: "Browse Plans"
|
|
307
|
+
}) : void 0
|
|
329
308
|
}) : /* @__PURE__ */ _("div", {
|
|
330
309
|
className: "space-y-4",
|
|
331
|
-
children:
|
|
310
|
+
children: X.map((e) => /* @__PURE__ */ _(E, {
|
|
332
311
|
subscription: e,
|
|
333
312
|
onView: () => i(u(`/subscriptions/${e.id}`, e.billingAccountId)),
|
|
334
313
|
canCancel: a.canCancelSubscription
|
|
335
314
|
}, e.id))
|
|
336
315
|
}),
|
|
337
|
-
|
|
316
|
+
U > 1 && /* @__PURE__ */ v("div", {
|
|
338
317
|
className: "flex items-center justify-center gap-2 pt-4",
|
|
339
318
|
children: [
|
|
340
319
|
/* @__PURE__ */ _("button", {
|
|
341
320
|
type: "button",
|
|
342
|
-
onClick: () =>
|
|
343
|
-
disabled:
|
|
321
|
+
onClick: () => Y(L - 1),
|
|
322
|
+
disabled: L === 1,
|
|
344
323
|
className: "px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors",
|
|
345
324
|
children: /* @__PURE__ */ _("svg", {
|
|
346
325
|
className: "size-4",
|
|
@@ -357,23 +336,23 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
357
336
|
}),
|
|
358
337
|
/* @__PURE__ */ _("div", {
|
|
359
338
|
className: "flex items-center gap-1",
|
|
360
|
-
children: Array.from({ length:
|
|
361
|
-
let t = e === 1 || e ===
|
|
339
|
+
children: Array.from({ length: U }, (e, t) => t + 1).map((e) => {
|
|
340
|
+
let t = e === 1 || e === U || Math.abs(e - L) <= 1, n = e === L - 2 && L > 3, r = e === L + 2 && L < U - 2;
|
|
362
341
|
return n || r ? /* @__PURE__ */ _("span", {
|
|
363
342
|
className: "px-2 text-text-secondary",
|
|
364
343
|
children: "..."
|
|
365
344
|
}, e) : t ? /* @__PURE__ */ _("button", {
|
|
366
345
|
type: "button",
|
|
367
|
-
onClick: () =>
|
|
368
|
-
className: `px-3 py-1.5 text-sm rounded-button transition-colors ${e ===
|
|
346
|
+
onClick: () => Y(e),
|
|
347
|
+
className: `px-3 py-1.5 text-sm rounded-button transition-colors ${e === L ? "bg-action-primary-bg text-action-primary-text" : "border border-border-subtle hover:bg-bg-sunken"}`,
|
|
369
348
|
children: e
|
|
370
349
|
}, e) : null;
|
|
371
350
|
})
|
|
372
351
|
}),
|
|
373
352
|
/* @__PURE__ */ _("button", {
|
|
374
353
|
type: "button",
|
|
375
|
-
onClick: () =>
|
|
376
|
-
disabled:
|
|
354
|
+
onClick: () => Y(L + 1),
|
|
355
|
+
disabled: L === U,
|
|
377
356
|
className: "px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors",
|
|
378
357
|
children: /* @__PURE__ */ _("svg", {
|
|
379
358
|
className: "size-4",
|
|
@@ -422,7 +401,7 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
422
401
|
]
|
|
423
402
|
})
|
|
424
403
|
});
|
|
425
|
-
},
|
|
404
|
+
}, E = ({ subscription: e, onView: n, canCancel: c }) => {
|
|
426
405
|
let l = e.plan, u = s(e.status), d = e.status === "active", f = new Date(e.endDate), p = /* @__PURE__ */ new Date(), m = Math.max(0, Math.ceil((f.getTime() - p.getTime()) / (1e3 * 60 * 60 * 24)));
|
|
427
406
|
return /* @__PURE__ */ _("div", {
|
|
428
407
|
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 cursor-pointer",
|
|
@@ -531,6 +510,6 @@ var b = ["expired", "upgraded"], x = 10, S = () => {
|
|
|
531
510
|
});
|
|
532
511
|
};
|
|
533
512
|
//#endregion
|
|
534
|
-
export {
|
|
513
|
+
export { T as SubscriptionsListPage };
|
|
535
514
|
|
|
536
515
|
//# sourceMappingURL=SubscriptionsListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriptionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscriptions List Page\n * Displays paginated subscriptions for the selected billing account\n */\n\nimport { useState, useEffect, useRef, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingAccounts, usePaginatedSubscriptions } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { BillingSubscription, SubscriptionStatus, BillingAccount } from '../../../shared/types';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor } from '../../../shared/utils/status';\nimport { StatusPill } from '../../../shared/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype FilterStatus = 'all' | 'active' | 'canceled' | 'inactive';\n\n// Statuses that count as \"inactive\" (historical/past subscriptions)\nconst INACTIVE_STATUSES = ['expired', 'upgraded'] as const;\n\nconst PAGE_SIZE = 10;\n\nexport const SubscriptionsListPage: 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 { orgId } = useBilling();\n\n // Fetch all billing accounts for the selector\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccounts();\n\n // State\n const {\n selectedAccountId: selectedBillingAccountId,\n setSelectedAccountId: setSelectedBillingAccountId,\n } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setCurrentPage(1);\n }, 400);\n return () => clearTimeout(debounceTimer.current);\n }, [searchQuery]);\n\n // Fetch paginated subscriptions for selected billing account\n const {\n subscriptions,\n activeSubscriptions,\n totalCount,\n totalPages,\n isLoading: isLoadingSubscriptions,\n error,\n refetch,\n } = usePaginatedSubscriptions(\n selectedBillingAccountId ?? undefined,\n currentPage,\n PAGE_SIZE,\n debouncedSearch || undefined\n );\n\n // Get selected billing account info from the list\n const selectedBillingAccount = billingAccounts.find((a) => a.id === selectedBillingAccountId);\n\n // Handle account selection\n const handleSelectAccount = (account: BillingAccount) => {\n setSelectedBillingAccountId(account.id);\n setIsAccountSelectorOpen(false);\n setCurrentPage(1); // Reset to first page when switching accounts\n };\n\n // Handle page change\n const handlePageChange = (newPage: number) => {\n setCurrentPage(newPage);\n };\n\n // Client-side status filter only; search is handled by the backend\n const filteredSubscriptions = subscriptions.filter((subscription) => {\n if (filterStatus === 'all') return true;\n if (filterStatus === 'inactive') {\n return INACTIVE_STATUSES.includes(subscription.status as (typeof INACTIVE_STATUSES)[number]);\n }\n return subscription.status === filterStatus;\n });\n\n const isLoading = isLoadingAccounts || isLoadingSubscriptions;\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n You don't have permission to view subscriptions.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !selectedBillingAccount) {\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=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Failed to load subscriptions</h2>\n <p className=\"text-sm text-text-secondary\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\n </div>\n\n {permissions.canCreateSubscription && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n New Subscription\n </button>\n )}\n </div>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-4 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedBillingAccount?.name || 'Select Account'}\n </p>\n <p className=\"text-xs text-text-secondary\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`size-4 text-text-secondary transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\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 9l-7 7-7-7\"\n />\n </svg>\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"size-4 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"size-4 text-text-link flex-shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Account Summary */}\n {selectedBillingAccount && (\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5\">\n <p className=\"text-sm text-text-secondary\">Account</p>\n <p className=\"text-lg font-semibold text-text-primary mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5\">\n <p className=\"text-sm text-text-secondary\">Active Subscriptions</p>\n <p className=\"text-lg font-semibold tabular-nums text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5\">\n <p className=\"text-sm text-text-secondary\">Credit Balance</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3 sm:gap-4 pb-4 border-b border-border-subtle\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken flex-wrap\">\n {(['all', 'active', 'canceled', 'inactive'] as FilterStatus[]).map((status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${\n filterStatus === status\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {status === 'inactive' ? 'Inactive' : status}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder={tr('billing.subscriptions.searchPlaceholder', 'Search subscriptions...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent\"\n />\n </div>\n\n {/* Total Count */}\n <div className=\"text-sm text-text-secondary\">\n {totalCount} subscription{totalCount !== 1 ? 's' : ''} total\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border-seam rounded-card\">\n <div className=\"size-12 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary\">\n {tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {filterStatus !== 'all' || debouncedSearch\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"}\n </p>\n {permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Browse Plans\n </button>\n )}\n </div>\n ) : (\n <div className=\"space-y-4\">\n {filteredSubscriptions.map((subscription) => (\n <SubscriptionCard\n key={subscription.id}\n subscription={subscription}\n onView={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}`,\n subscription.billingAccountId\n )\n )\n }\n canCancel={permissions.canCancelSubscription}\n />\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={currentPage === 1}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {\n // Show first, last, current, and adjacent pages\n const showPage =\n page === 1 || page === totalPages || Math.abs(page - currentPage) <= 1;\n\n // Show ellipsis\n const showEllipsisBefore = page === currentPage - 2 && currentPage > 3;\n const showEllipsisAfter = page === currentPage + 2 && currentPage < totalPages - 2;\n\n if (showEllipsisBefore || showEllipsisAfter) {\n return (\n <span key={page} className=\"px-2 text-text-secondary\">\n ...\n </span>\n );\n }\n\n if (!showPage) return null;\n\n return (\n <button\n type=\"button\"\n key={page}\n onClick={() => handlePageChange(page)}\n className={`px-3 py-1.5 text-sm rounded-button transition-colors ${\n page === currentPage\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n {page}\n </button>\n );\n })}\n </div>\n\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={currentPage === totalPages}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Subscription Card Component\n// ============================================================================\n\ninterface SubscriptionCardProps {\n subscription: BillingSubscription;\n onView: () => void;\n canCancel: boolean;\n}\n\nconst SubscriptionCard: FC<SubscriptionCardProps> = ({\n subscription,\n onView,\n canCancel: _canCancel,\n}) => {\n const plan = subscription.plan;\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const now = new Date();\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-5\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-4\">\n {/* Plan Icon */}\n <div className=\"size-12 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-6 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n\n {/* Subscription Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary\">{plan?.name || 'Unknown Plan'}</h3>\n <StatusPill status={statusColor} dot pulse={isActive}>\n {formatSubscriptionStatus(subscription.status)}\n </StatusPill>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-text-secondary\">\n <span>\n {plan ? formatCurrency(plan.price, plan.currency) : '—'} /{' '}\n {plan ? formatPlanDuration(plan.duration) : '—'}\n </span>\n <span className=\"hidden sm:inline\">•</span>\n <span className=\"hidden sm:inline\">\n Started {formatDate(subscription.startDate, 'short')}\n </span>\n </div>\n </div>\n\n {/* Right Side Info */}\n <div className=\"flex items-center gap-4\">\n {isActive && (\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{daysRemaining} days</p>\n <p className=\"text-xs text-text-secondary\">remaining</p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onView();\n }}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n View\n </button>\n </div>\n </div>\n\n {/* Additional Info */}\n {subscription.addonSubscriptions && subscription.addonSubscriptions.length > 0 && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n {subscription.addonSubscriptions.length} addon\n {subscription.addonSubscriptions.length !== 1 ? 's' : ''} attached\n </p>\n </div>\n )}\n\n {/* Cancellation Info */}\n {subscription.canceledAt && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Canceled on {formatDate(subscription.canceledAt)}\n {subscription.cancellationReason && <span>: {subscription.cancellationReason}</span>}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,IAAM,IAAoB,CAAC,WAAW,WAAW,EAE3C,IAAY,IAEL,UAAkC;CAC7C,IAAM,EAAE,MAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,IAAa,GAAoB,EACjC,IAAc,GAAuB,EACrC,EAAE,aAAU,GAAY,EAGxB,EAAE,oBAAiB,WAAW,MAAsB,GAAoB,EAGxE,EACJ,mBAAmB,GACnB,sBAAsB,MACpB,EAA2B;EAC7B;EACA;EACA,aAAa;EACb,cAAc;EACf,CAAC,EAEI,CAAC,GAAuB,KAA4B,EAAS,GAAM,EACnE,CAAC,GAAc,KAAmB,EAAuB,MAAM,EAC/D,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAiB,KAAsB,EAAS,GAAG,EACpD,CAAC,GAAa,KAAkB,EAAS,EAAE,EAC3C,IAAgB,EAAkD,KAAA,EAAU;AAElF,UACE,aAAa,EAAc,QAAQ,EACnC,EAAc,UAAU,iBAAiB;AAEvC,EADA,EAAmB,EAAY,EAC/B,EAAe,EAAE;IAChB,IAAI,QACM,aAAa,EAAc,QAAQ,GAC/C,CAAC,EAAY,CAAC;CAGjB,IAAM,EACJ,kBACA,wBACA,eACA,eACA,WAAW,GACX,UACA,eACE,EACF,KAA4B,KAAA,GAC5B,GACA,GACA,KAAmB,KAAA,EACpB,EAGK,IAAyB,EAAgB,MAAM,MAAM,EAAE,OAAO,EAAyB,EAGvF,KAAuB,MAA4B;AAGvD,EAFA,EAA4B,EAAQ,GAAG,EACvC,EAAyB,GAAM,EAC/B,EAAe,EAAE;IAIb,KAAoB,MAAoB;AAC5C,IAAe,EAAQ;IAInB,IAAwB,EAAc,QAAQ,MAC9C,MAAiB,QAAc,KAC/B,MAAiB,aACZ,EAAkB,SAAS,EAAa,OAA6C,GAEvF,EAAa,WAAW,EAC/B,EAEI,IAAY,KAAqB;AAuFvC,QApFK,EAAY,uBA6Bb,KAAa,CAAC,IAEd,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA,EAC/D,kBAAC,OAAD;GAAK,WAAU;aACZ;IAAC;IAAG;IAAG;IAAE,CAAC,KAAK,MACd,kBAAC,OAAD;IAAa,WAAU;cACrB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAK,WAAU,8CAA+C,CAAA,EAC9D,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA,EAC/D,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA,CAC3D;QACF;;IACF,EARI,EAQJ,CACN;GACE,CAAA,CACF;MAKN,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAiC,CAAA;IACzF,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAM;KAAY,CAAA;IAC9D,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,GAAS;KACxB,WAAU;eACX;KAEQ,CAAA;IACL;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,+BAA+B,gBAAgB;KAChD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,kCAAkC,4CAA4C;KAChF,CAAA,CACA,EAAA,CAAA,EAEL,EAAY,yBACX,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,SAAS;KACnC,WAAU;eAHZ,CAKE,kBAAC,OAAD;MAAK,WAAU;MAAS,MAAK;MAAO,SAAQ;MAAY,QAAO;gBAC7D,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA,EAAA,mBAEC;OAEP;;GAGL,EAAgB,SAAS,KACxB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAyB,CAAC,EAAsB;KAC/D,WAAU;eAHZ,CAKE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QACE,WAAU;QACV,MAAK;QACL,SAAQ;QACR,QAAO;kBAEP,kBAAC,QAAD;SACE,eAAc;SACd,gBAAe;SACf,aAAa;SACb,GAAE;SACF,CAAA;QACE,CAAA;OACF,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAwB,QAAQ;QAC/B,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAA+B,GAAwB;QAAU,CAAA,CAC1E;SACF;SACN,kBAAC,OAAD;MACE,WAAW,mDAAmD,IAAwB,eAAe;MACrG,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA,CACC;QAER,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAqB,eAAe,EAAyB,GAAM;KAAI,CAAA,EACtF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAgB,KAAK,MACpB,kBAAC,UAAD;MACE,MAAK;MAEL,eAAe,EAAoB,EAAQ;MAC3C,WAAU;gBAJZ,CAME,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SACE,WAAU;SACV,MAAK;SACL,SAAQ;SACR,QAAO;mBAEP,kBAAC,QAAD;UACE,eAAc;UACd,gBAAe;UACf,aAAa;UACb,GAAE;UACF,CAAA;SACE,CAAA;QACF,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAQ;SACP,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBAAwC,EAAQ;SAAU,CAAA,CACnE;UACF;UACL,GAAwB,OAAO,EAAQ,MACtC,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;iBAEP,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA,CAED;QA1CF,EAAQ,GA0CN,CACT;KACE,CAAA,CACL,EAAA,CAAA,CAED;;GAIP,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAW,CAAA,EACtD,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAuB;OACtB,CAAA,CACA;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAwB,CAAA,EACnE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAoB;OACnB,CAAA,CACA;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAkB,CAAA,EAC7D,kBAAC,KAAD;OAAG,WAAU;iBAAb,EACI,EAAuB,gBAAgB,GAAG,gBAAgB,EAAC,WAC3D;SACA;;KACF;;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBACX;OAAC;OAAO;OAAU;OAAY;OAAW,CAAoB,KAAK,MAClE,kBAAC,UAAD;OACE,MAAK;OAEL,eAAe,EAAgB,EAAO;OACtC,WAAW,+EACT,MAAiB,IACb,8CACA;iBAGL,MAAW,aAAa,aAAa;OAC/B,EATF,EASE,CACT;MACE,CAAA;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;iBAEP,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA,EACN,kBAAC,SAAD;OACE,MAAK;OACL,aAAa,EAAG,2CAA2C,0BAA0B;OACrF,OAAO;OACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;OAC/C,WAAU;OACV,CAAA,CACE;;KAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG;OAAW;OAAc,MAAe,IAAU,KAAN;OAAS;OAClD;;KACF;;GAGL,EAAsB,WAAW,IAChC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;iBAEP,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA;MACF,CAAA;KACN,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,8CAA8C,yBAAyB;MACxE,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,MAAiB,SAAS,IACvB,yCACA;MACF,CAAA;KACH,EAAY,yBAAyB,MAAiB,SAAS,CAAC,KAC/D,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,SAAS;MACnC,WAAU;gBACX;MAEQ,CAAA;KAEP;QAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAsB,KAAK,MAC1B,kBAAC,GAAD;KAEgB;KACd,cACE,EACE,EACE,kBAAkB,EAAa,MAC/B,EAAa,iBACd,CACF;KAEH,WAAW,EAAY;KACvB,EAXK,EAAa,GAWlB,CACF;IACE,CAAA;GAIP,IAAa,KACZ,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,IAAc,EAAE;MAChD,UAAU,MAAgB;MAC1B,WAAU;gBAEV,kBAAC,OAAD;OAAK,WAAU;OAAS,MAAK;OAAO,SAAQ;OAAY,QAAO;iBAC7D,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA;MACC,CAAA;KAET,kBAAC,OAAD;MAAK,WAAU;gBACZ,MAAM,KAAK,EAAE,QAAQ,GAAY,GAAG,GAAG,MAAM,IAAI,EAAE,CAAC,KAAK,MAAS;OAEjE,IAAM,IACJ,MAAS,KAAK,MAAS,KAAc,KAAK,IAAI,IAAO,EAAY,IAAI,GAGjE,IAAqB,MAAS,IAAc,KAAK,IAAc,GAC/D,IAAoB,MAAS,IAAc,KAAK,IAAc,IAAa;AAYjF,cAVI,KAAsB,IAEtB,kBAAC,QAAD;QAAiB,WAAU;kBAA2B;QAE/C,EAFI,EAEJ,GAIN,IAGH,kBAAC,UAAD;QACE,MAAK;QAEL,eAAe,EAAiB,EAAK;QACrC,WAAW,wDACT,MAAS,IACL,kDACA;kBAGL;QACM,EATF,EASE,GAdW;QAgBtB;MACE,CAAA;KAEN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAiB,IAAc,EAAE;MAChD,UAAU,MAAgB;MAC1B,WAAU;gBAEV,kBAAC,OAAD;OAAK,WAAU;OAAS,MAAK;OAAO,SAAQ;OAAY,QAAO;iBAC7D,kBAAC,QAAD;QAAM,eAAc;QAAQ,gBAAe;QAAQ,aAAa;QAAG,GAAE;QAAiB,CAAA;OAClF,CAAA;MACC,CAAA;KACL;;GAEJ;MA/ZJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eAA0C;KAAkB,CAAA;IAC1E,kBAAC,KAAD;KAAG,WAAU;eAAuC;KAEhD,CAAA;IACA;;EACF,CAAA;GAuZN,KAA+C,EACnD,iBACA,WACA,WAAW,QACP;CACJ,IAAM,IAAO,EAAa,MACpB,IAAc,EAA2B,EAAa,OAAO,EAC7D,IAAW,EAAa,WAAY,UAGpC,IAAU,IAAI,KAAK,EAAa,QAAQ,EACxC,oBAAM,IAAI,MAAM,EAChB,IAAgB,KAAK,IACzB,GACA,KAAK,MAAM,EAAQ,SAAS,GAAG,EAAI,SAAS,KAAK,MAAO,KAAK,KAAK,IAAI,CACvE;AAED,QACE,kBAAC,OAAD;EACE,WAAU;EACV,SAAS;YAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QACE,WAAU;QACV,MAAK;QACL,SAAQ;QACR,QAAO;kBAEP,kBAAC,QAAD;SACE,eAAc;SACd,gBAAe;SACf,aAAa;SACb,GAAE;SACF,CAAA;QACE,CAAA;OACF,CAAA;MAGN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,MAAD;SAAI,WAAU;mBAAmC,GAAM,QAAQ;SAAoB,CAAA,EACnF,kBAAC,GAAD;SAAY,QAAQ;SAAa,KAAA;SAAI,OAAO;mBACzC,EAAyB,EAAa,OAAO;SACnC,CAAA,CACT;WACN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,QAAD,EAAA,UAAA;UACG,IAAO,EAAe,EAAK,OAAO,EAAK,SAAS,GAAG;UAAI;UAAG;UAC1D,IAAO,EAAmB,EAAK,SAAS,GAAG;UACvC,EAAA,CAAA;SACP,kBAAC,QAAD;UAAM,WAAU;oBAAmB;UAAQ,CAAA;SAC3C,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CAAmC,YACxB,EAAW,EAAa,WAAW,QAAQ,CAC/C;;SACH;UACF;;MAGN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACG,KACC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAAsD,GAAc,QAAS;YAC7E,kBAAC,KAAD;SAAG,WAAU;mBAA8B;SAAa,CAAA,CACpD;WAER,kBAAC,UAAD;QACE,MAAK;QACL,UAAU,MAAM;AAEd,SADA,EAAE,iBAAiB,EACnB,GAAQ;;QAEV,WAAU;kBACX;QAEQ,CAAA,CACL;;MACF;;IAGL,EAAa,sBAAsB,EAAa,mBAAmB,SAAS,KAC3E,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACG,EAAa,mBAAmB;OAAO;OACvC,EAAa,mBAAmB,WAAW,IAAU,KAAN;OAAS;OACvD;;KACA,CAAA;IAIP,EAAa,cACZ,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAA2C;OAC5B,EAAW,EAAa,WAAW;OAC/C,EAAa,sBAAsB,kBAAC,QAAD,EAAA,UAAA,CAAM,MAAG,EAAa,mBAA0B,EAAA,CAAA;OAClF;;KACA,CAAA;IAEJ;;EACF,CAAA"}
|
|
1
|
+
{"version":3,"file":"SubscriptionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/pages/SubscriptionsListPage.tsx"],"sourcesContent":["/**\n * Subscriptions Module - Subscriptions List Page\n * Displays paginated subscriptions for the selected billing account\n */\n\nimport { useState, useEffect, useRef, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingAccounts, usePaginatedSubscriptions } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { BillingSubscription, SubscriptionStatus, BillingAccount } from '../../../shared/types';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport {\n formatCurrency,\n formatDate,\n formatPlanDuration,\n formatSubscriptionStatus,\n} from '../../../shared/utils/format';\nimport { getSubscriptionStatusColor } from '../../../shared/utils/status';\nimport { StatusPill } from '../../../shared/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, EmphasisPanel, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterStatus = 'all' | 'active' | 'canceled' | 'inactive';\n\n// Statuses that count as \"inactive\" (historical/past subscriptions)\nconst INACTIVE_STATUSES = ['expired', 'upgraded'] as const;\n\nconst PAGE_SIZE = 10;\n\nexport const SubscriptionsListPage: 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 { orgId } = useBilling();\n\n // Fetch all billing accounts for the selector\n const { billingAccounts, isLoading: isLoadingAccounts } = useBillingAccounts();\n\n // State\n const {\n selectedAccountId: selectedBillingAccountId,\n setSelectedAccountId: setSelectedBillingAccountId,\n } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const debounceTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(() => {\n clearTimeout(debounceTimer.current);\n debounceTimer.current = setTimeout(() => {\n setDebouncedSearch(searchQuery);\n setCurrentPage(1);\n }, 400);\n return () => clearTimeout(debounceTimer.current);\n }, [searchQuery]);\n\n // Fetch paginated subscriptions for selected billing account\n const {\n subscriptions,\n activeSubscriptions,\n totalCount,\n totalPages,\n isLoading: isLoadingSubscriptions,\n error,\n refetch,\n } = usePaginatedSubscriptions(\n selectedBillingAccountId ?? undefined,\n currentPage,\n PAGE_SIZE,\n debouncedSearch || undefined\n );\n\n // Get selected billing account info from the list\n const selectedBillingAccount = billingAccounts.find((a) => a.id === selectedBillingAccountId);\n\n // Handle account selection\n const handleSelectAccount = (account: BillingAccount) => {\n setSelectedBillingAccountId(account.id);\n setIsAccountSelectorOpen(false);\n setCurrentPage(1); // Reset to first page when switching accounts\n };\n\n // Handle page change\n const handlePageChange = (newPage: number) => {\n setCurrentPage(newPage);\n };\n\n // Client-side status filter only; search is handled by the backend\n const filteredSubscriptions = subscriptions.filter((subscription) => {\n if (filterStatus === 'all') return true;\n if (filterStatus === 'inactive') {\n return INACTIVE_STATUSES.includes(subscription.status as (typeof INACTIVE_STATUSES)[number]);\n }\n return subscription.status === filterStatus;\n });\n\n const isLoading = isLoadingAccounts || isLoadingSubscriptions;\n\n // Permission check\n if (!permissions.canViewSubscriptions) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">\n You don't have permission to view subscriptions.\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !selectedBillingAccount) {\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=\"space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Failed to load subscriptions</h2>\n <p className=\"text-sm text-text-secondary\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.subscriptions.title', 'Subscriptions')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.subscriptions.subtitle', 'Manage your active and past subscriptions')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.subscriptions.purpose',\n 'See every plan a billing account is subscribed to — active, canceled and expired — in one place. Use it to review what you are paying for, when each plan renews or ends, upgrade or cancel a subscription, and start a new one by browsing plans.'\n )}\n </PagePurpose>\n </div>\n\n {permissions.canCreateSubscription && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n New Subscription\n </button>\n )}\n </div>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <svg\n className=\"size-4 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedBillingAccount?.name || 'Select Account'}\n </p>\n <p className=\"text-xs text-text-secondary\">{selectedBillingAccount?.email}</p>\n </div>\n </div>\n <svg\n className={`size-4 text-text-secondary transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\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 9l-7 7-7-7\"\n />\n </svg>\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <svg\n className=\"size-4 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {selectedBillingAccount?.id === account.id && (\n <svg\n className=\"size-4 text-text-link flex-shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Account Summary — primary emphasis zone */}\n {selectedBillingAccount && (\n <EmphasisPanel className=\"p-5\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <p className=\"text-sm text-text-secondary\">Account</p>\n <p className=\"text-lg font-semibold text-text-primary mt-1\">\n {selectedBillingAccount.name}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Active Subscriptions</p>\n <p className=\"text-lg font-semibold tabular-nums text-status-success-text mt-1\">\n {activeSubscriptions.length}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-secondary\">Credit Balance</p>\n <p className=\"text-lg font-semibold tabular-nums text-text-primary mt-1\">\n {(selectedBillingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3 sm:gap-4 pb-4 border-b border-border-subtle\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken flex-wrap\">\n {(['all', 'active', 'canceled', 'inactive'] as FilterStatus[]).map((status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${\n filterStatus === status\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {status === 'inactive' ? 'Inactive' : status}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder={tr('billing.subscriptions.searchPlaceholder', 'Search subscriptions...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-transparent\"\n />\n </div>\n\n {/* Total Count */}\n <div className=\"text-sm text-text-secondary\">\n {totalCount} subscription{totalCount !== 1 ? 's' : ''} total\n </div>\n </div>\n\n {/* Subscriptions List */}\n {filteredSubscriptions.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterStatus !== 'all' || debouncedSearch ? 'empty-search' : 'empty-data'}\n title={tr('billing.subscriptions.noSubscriptionsFound', 'No subscriptions found')}\n description={\n filterStatus !== 'all' || debouncedSearch\n ? 'No subscriptions match your filters.'\n : \"You don't have any subscriptions yet.\"\n }\n action={\n permissions.canCreateSubscription && filterStatus === 'all' && !debouncedSearch ? (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Browse Plans\n </button>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {filteredSubscriptions.map((subscription) => (\n <SubscriptionCard\n key={subscription.id}\n subscription={subscription}\n onView={() =>\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${subscription.id}`,\n subscription.billingAccountId\n )\n )\n }\n canCancel={permissions.canCancelSubscription}\n />\n ))}\n </div>\n )}\n\n {/* Pagination */}\n {totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage - 1)}\n disabled={currentPage === 1}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n </button>\n\n <div className=\"flex items-center gap-1\">\n {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => {\n // Show first, last, current, and adjacent pages\n const showPage =\n page === 1 || page === totalPages || Math.abs(page - currentPage) <= 1;\n\n // Show ellipsis\n const showEllipsisBefore = page === currentPage - 2 && currentPage > 3;\n const showEllipsisAfter = page === currentPage + 2 && currentPage < totalPages - 2;\n\n if (showEllipsisBefore || showEllipsisAfter) {\n return (\n <span key={page} className=\"px-2 text-text-secondary\">\n ...\n </span>\n );\n }\n\n if (!showPage) return null;\n\n return (\n <button\n type=\"button\"\n key={page}\n onClick={() => handlePageChange(page)}\n className={`px-3 py-1.5 text-sm rounded-button transition-colors ${\n page === currentPage\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle hover:bg-bg-sunken'\n }`}\n >\n {page}\n </button>\n );\n })}\n </div>\n\n <button\n type=\"button\"\n onClick={() => handlePageChange(currentPage + 1)}\n disabled={currentPage === totalPages}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-button disabled:opacity-50 disabled:cursor-not-allowed hover:bg-bg-sunken transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Subscription Card Component\n// ============================================================================\n\ninterface SubscriptionCardProps {\n subscription: BillingSubscription;\n onView: () => void;\n canCancel: boolean;\n}\n\nconst SubscriptionCard: FC<SubscriptionCardProps> = ({\n subscription,\n onView,\n canCancel: _canCancel,\n}) => {\n const plan = subscription.plan;\n const statusColor = getSubscriptionStatusColor(subscription.status);\n const isActive = subscription.status === ('active' as SubscriptionStatus);\n\n // Calculate days remaining\n const endDate = new Date(subscription.endDate);\n const now = new Date();\n const daysRemaining = Math.max(\n 0,\n Math.ceil((endDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200 cursor-pointer\"\n onClick={onView}\n >\n <div className=\"p-5\">\n <div className=\"flex flex-col sm:flex-row sm:items-center gap-4\">\n {/* Plan Icon */}\n <div className=\"size-12 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-6 text-text-link\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n\n {/* Subscription Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-text-primary\">{plan?.name || 'Unknown Plan'}</h3>\n <StatusPill status={statusColor} dot pulse={isActive}>\n {formatSubscriptionStatus(subscription.status)}\n </StatusPill>\n </div>\n <div className=\"flex items-center gap-4 mt-1 text-sm text-text-secondary\">\n <span>\n {plan ? formatCurrency(plan.price, plan.currency) : '—'} /{' '}\n {plan ? formatPlanDuration(plan.duration) : '—'}\n </span>\n <span className=\"hidden sm:inline\">•</span>\n <span className=\"hidden sm:inline\">\n Started {formatDate(subscription.startDate, 'short')}\n </span>\n </div>\n </div>\n\n {/* Right Side Info */}\n <div className=\"flex items-center gap-4\">\n {isActive && (\n <div className=\"text-right\">\n <p className=\"text-sm font-medium text-text-primary\">{daysRemaining} days</p>\n <p className=\"text-xs text-text-secondary\">remaining</p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onView();\n }}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n View\n </button>\n </div>\n </div>\n\n {/* Additional Info */}\n {subscription.addonSubscriptions && subscription.addonSubscriptions.length > 0 && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n {subscription.addonSubscriptions.length} addon\n {subscription.addonSubscriptions.length !== 1 ? 's' : ''} attached\n </p>\n </div>\n )}\n\n {/* Cancellation Info */}\n {subscription.canceledAt && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Canceled on {formatDate(subscription.canceledAt)}\n {subscription.cancellationReason && <span>: {subscription.cancellationReason}</span>}\n </p>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|