@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
|
@@ -14,8 +14,9 @@ import "../hooks/index.js";
|
|
|
14
14
|
import { useMemo as m, useState as h } from "react";
|
|
15
15
|
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
16
16
|
import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
17
|
+
import { IllustratedEmptyState as y, PagePurpose as b } from "@burdenoff/fe-libs/ui";
|
|
17
18
|
//#region src/billing/modules/plans/pages/PlansListPage.tsx
|
|
18
|
-
function
|
|
19
|
+
function x(e) {
|
|
19
20
|
let t = /* @__PURE__ */ new Map();
|
|
20
21
|
for (let n of e) {
|
|
21
22
|
if (!n.quota) continue;
|
|
@@ -28,21 +29,21 @@ function y(e) {
|
|
|
28
29
|
}
|
|
29
30
|
return Array.from(t.values());
|
|
30
31
|
}
|
|
31
|
-
var
|
|
32
|
+
var S = () => {
|
|
32
33
|
let { t: n } = v(), r = (e, t) => {
|
|
33
34
|
let r = n(e);
|
|
34
35
|
return r === e ? t : r;
|
|
35
|
-
}, i = l(), a = c(), { plans: s, isLoading: u, error: d, refetch: m } = p(), { togglePlan:
|
|
36
|
+
}, i = l(), a = c(), { plans: s, isLoading: u, error: d, refetch: m } = p(), { togglePlan: x, isToggling: S } = f(), [T, E] = h("grid"), [D, O] = h(!1), [k, A] = h("all");
|
|
36
37
|
if (!a.canViewPlans) return /* @__PURE__ */ g(e, { message: r("billing.plans.noViewPermission", "You don't have permission to view pricing plans.") });
|
|
37
|
-
let
|
|
38
|
+
let j = s.filter((e) => !(!D && !e.isActive || k !== "all" && e.duration !== k)), M = async (e) => {
|
|
38
39
|
if (a.canManagePlans) try {
|
|
39
|
-
await
|
|
40
|
+
await x(e.id, !e.isActive), await m();
|
|
40
41
|
} catch (e) {
|
|
41
42
|
console.error("Failed to toggle plan:", e);
|
|
42
43
|
}
|
|
43
|
-
},
|
|
44
|
+
}, N = (e) => {
|
|
44
45
|
i(`/checkout/${e.id}`);
|
|
45
|
-
},
|
|
46
|
+
}, P = (e) => {
|
|
46
47
|
i(`/plans/${e.id}`);
|
|
47
48
|
};
|
|
48
49
|
return u && s.length === 0 ? /* @__PURE__ */ _("div", {
|
|
@@ -119,13 +120,20 @@ var b = () => {
|
|
|
119
120
|
children: [
|
|
120
121
|
/* @__PURE__ */ _("div", {
|
|
121
122
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
122
|
-
children: [/* @__PURE__ */ _("div", { children: [
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
children: [/* @__PURE__ */ _("div", { children: [
|
|
124
|
+
/* @__PURE__ */ g("h1", {
|
|
125
|
+
className: "text-2xl md:text-3xl font-semibold tracking-tight text-text-primary",
|
|
126
|
+
children: r("billing.plans.title", "Pricing Plans")
|
|
127
|
+
}),
|
|
128
|
+
/* @__PURE__ */ g("p", {
|
|
129
|
+
className: "text-sm text-text-muted mt-1",
|
|
130
|
+
children: r("billing.plans.subtitle", "Choose the plan that best fits your needs")
|
|
131
|
+
}),
|
|
132
|
+
/* @__PURE__ */ g(b, {
|
|
133
|
+
className: "mt-3",
|
|
134
|
+
children: r("billing.plans.listPurpose", "Manage the full catalog of pricing plans for this product — including inactive ones. Create new tiers, edit the quotas and price each plan grants, and activate or deactivate plans to control what customers can subscribe to.")
|
|
135
|
+
})
|
|
136
|
+
] }), a.canManagePlans && /* @__PURE__ */ _("button", {
|
|
129
137
|
type: "button",
|
|
130
138
|
onClick: () => i("/plans/new"),
|
|
131
139
|
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",
|
|
@@ -151,20 +159,20 @@ var b = () => {
|
|
|
151
159
|
children: [
|
|
152
160
|
/* @__PURE__ */ g("button", {
|
|
153
161
|
type: "button",
|
|
154
|
-
onClick: () =>
|
|
155
|
-
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
162
|
+
onClick: () => A("all"),
|
|
163
|
+
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${k === "all" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-muted hover:text-text-primary"}`,
|
|
156
164
|
children: "All"
|
|
157
165
|
}),
|
|
158
166
|
/* @__PURE__ */ g("button", {
|
|
159
167
|
type: "button",
|
|
160
|
-
onClick: () =>
|
|
161
|
-
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
168
|
+
onClick: () => A("monthly"),
|
|
169
|
+
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${k === "monthly" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-muted hover:text-text-primary"}`,
|
|
162
170
|
children: "Monthly"
|
|
163
171
|
}),
|
|
164
172
|
/* @__PURE__ */ g("button", {
|
|
165
173
|
type: "button",
|
|
166
|
-
onClick: () =>
|
|
167
|
-
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
|
|
174
|
+
onClick: () => A("yearly"),
|
|
175
|
+
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${k === "yearly" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-muted hover:text-text-primary"}`,
|
|
168
176
|
children: "Yearly"
|
|
169
177
|
})
|
|
170
178
|
]
|
|
@@ -173,8 +181,8 @@ var b = () => {
|
|
|
173
181
|
className: "flex items-center gap-2 text-sm cursor-pointer",
|
|
174
182
|
children: [/* @__PURE__ */ g("input", {
|
|
175
183
|
type: "checkbox",
|
|
176
|
-
checked:
|
|
177
|
-
onChange: (e) =>
|
|
184
|
+
checked: D,
|
|
185
|
+
onChange: (e) => O(e.target.checked),
|
|
178
186
|
className: "size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]"
|
|
179
187
|
}), /* @__PURE__ */ g("span", {
|
|
180
188
|
className: "text-text-muted",
|
|
@@ -185,8 +193,8 @@ var b = () => {
|
|
|
185
193
|
className: "ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50",
|
|
186
194
|
children: [/* @__PURE__ */ g("button", {
|
|
187
195
|
type: "button",
|
|
188
|
-
onClick: () =>
|
|
189
|
-
className: `p-1.5 rounded-md transition-colors ${
|
|
196
|
+
onClick: () => E("grid"),
|
|
197
|
+
className: `p-1.5 rounded-md transition-colors ${T === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-muted hover:text-text-primary"}`,
|
|
190
198
|
title: "Grid view",
|
|
191
199
|
children: /* @__PURE__ */ g("svg", {
|
|
192
200
|
className: "size-4",
|
|
@@ -202,8 +210,8 @@ var b = () => {
|
|
|
202
210
|
})
|
|
203
211
|
}), /* @__PURE__ */ g("button", {
|
|
204
212
|
type: "button",
|
|
205
|
-
onClick: () =>
|
|
206
|
-
className: `p-1.5 rounded-md transition-colors ${
|
|
213
|
+
onClick: () => E("list"),
|
|
214
|
+
className: `p-1.5 rounded-md transition-colors ${T === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-muted hover:text-text-primary"}`,
|
|
207
215
|
title: "List view",
|
|
208
216
|
children: /* @__PURE__ */ g("svg", {
|
|
209
217
|
className: "size-4",
|
|
@@ -221,66 +229,43 @@ var b = () => {
|
|
|
221
229
|
})
|
|
222
230
|
]
|
|
223
231
|
}),
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
strokeLinecap: "round",
|
|
236
|
-
strokeLinejoin: "round",
|
|
237
|
-
strokeWidth: 2,
|
|
238
|
-
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"
|
|
239
|
-
})
|
|
240
|
-
})
|
|
241
|
-
}),
|
|
242
|
-
/* @__PURE__ */ g("h3", {
|
|
243
|
-
className: "text-lg font-medium text-text-primary",
|
|
244
|
-
children: r("billing.plans.noPlansFound", "No plans found")
|
|
245
|
-
}),
|
|
246
|
-
/* @__PURE__ */ g("p", {
|
|
247
|
-
className: "text-sm text-text-muted mt-1",
|
|
248
|
-
children: T ? r("billing.plans.noPlansFiltered", "No plans match your current filters.") : r("billing.plans.noActivePlans", "There are no active plans available.")
|
|
249
|
-
}),
|
|
250
|
-
a.canManagePlans && /* @__PURE__ */ g("button", {
|
|
251
|
-
type: "button",
|
|
252
|
-
onClick: () => i("/plans/new"),
|
|
253
|
-
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",
|
|
254
|
-
children: r("billing.plans.createFirstPlan", "Create your first plan")
|
|
255
|
-
})
|
|
256
|
-
]
|
|
257
|
-
}) : C === "grid" ? /* @__PURE__ */ g("div", {
|
|
232
|
+
j.length === 0 ? /* @__PURE__ */ g(y, {
|
|
233
|
+
illustration: "empty-data",
|
|
234
|
+
title: r("billing.plans.noPlansFound", "No plans found"),
|
|
235
|
+
description: D ? r("billing.plans.noPlansFiltered", "No plans match your current filters.") : r("billing.plans.noActivePlans", "There are no active plans available."),
|
|
236
|
+
action: a.canManagePlans ? /* @__PURE__ */ g("button", {
|
|
237
|
+
type: "button",
|
|
238
|
+
onClick: () => i("/plans/new"),
|
|
239
|
+
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",
|
|
240
|
+
children: r("billing.plans.createFirstPlan", "Create your first plan")
|
|
241
|
+
}) : void 0
|
|
242
|
+
}) : T === "grid" ? /* @__PURE__ */ g("div", {
|
|
258
243
|
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6",
|
|
259
|
-
children:
|
|
244
|
+
children: j.map((e) => /* @__PURE__ */ g(C, {
|
|
260
245
|
plan: e,
|
|
261
|
-
onSelect:
|
|
262
|
-
onView:
|
|
263
|
-
onToggle:
|
|
246
|
+
onSelect: N,
|
|
247
|
+
onView: P,
|
|
248
|
+
onToggle: M,
|
|
264
249
|
canSelect: a.canCreateSubscription,
|
|
265
250
|
canManage: a.canManagePlans,
|
|
266
|
-
isToggling:
|
|
251
|
+
isToggling: S
|
|
267
252
|
}, e.id))
|
|
268
253
|
}) : /* @__PURE__ */ g("div", {
|
|
269
254
|
className: "space-y-3",
|
|
270
|
-
children:
|
|
255
|
+
children: j.map((e) => /* @__PURE__ */ g(w, {
|
|
271
256
|
plan: e,
|
|
272
|
-
onSelect:
|
|
273
|
-
onView:
|
|
274
|
-
onToggle:
|
|
257
|
+
onSelect: N,
|
|
258
|
+
onView: P,
|
|
259
|
+
onToggle: M,
|
|
275
260
|
canSelect: a.canCreateSubscription,
|
|
276
261
|
canManage: a.canManagePlans,
|
|
277
|
-
isToggling:
|
|
262
|
+
isToggling: S
|
|
278
263
|
}, e.id))
|
|
279
264
|
})
|
|
280
265
|
]
|
|
281
266
|
});
|
|
282
|
-
},
|
|
283
|
-
let p = d(), h = m(() =>
|
|
267
|
+
}, C = ({ plan: e, onSelect: t, onView: o, onToggle: s, canSelect: c, canManage: l, isToggling: f }) => {
|
|
268
|
+
let p = d(), h = m(() => x(e.features || []), [e.features]);
|
|
284
269
|
return /* @__PURE__ */ _("div", {
|
|
285
270
|
className: `relative border rounded-card p-6 flex flex-col h-full transition-[box-shadow,transform] duration-200 ease-[var(--motion-easing-out)] ${e.isActive ? "border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:shadow-[var(--shadow-elevation-2)] [@media(hover:none)]:" : "border-dashed border-text-muted/30 bg-bg-sunken/30 shadow-elevation-1"}`,
|
|
286
271
|
children: [
|
|
@@ -381,8 +366,8 @@ var b = () => {
|
|
|
381
366
|
})
|
|
382
367
|
]
|
|
383
368
|
});
|
|
384
|
-
},
|
|
385
|
-
let l = d(), f = m(() =>
|
|
369
|
+
}, w = ({ plan: e, onSelect: t, onView: n, onToggle: a, canSelect: o, canManage: s, isToggling: c }) => {
|
|
370
|
+
let l = d(), f = m(() => x(e.features || []), [e.features]);
|
|
386
371
|
return /* @__PURE__ */ _("div", {
|
|
387
372
|
className: `flex items-center gap-4 p-4 border rounded-card shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] ${e.isActive ? "border-border-subtle bg-bg-surface" : "border-dashed border-text-muted/30 bg-bg-sunken/30"}`,
|
|
388
373
|
children: [
|
|
@@ -448,6 +433,6 @@ var b = () => {
|
|
|
448
433
|
});
|
|
449
434
|
};
|
|
450
435
|
//#endregion
|
|
451
|
-
export {
|
|
436
|
+
export { S as PlansListPage };
|
|
452
437
|
|
|
453
438
|
//# sourceMappingURL=PlansListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlansListPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansListPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans List Page\n * Displays all available pricing plans\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlans, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport type { Plan, PlanDuration, PlanFeature } from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const PlansListPage: 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 { plans, isLoading, error, refetch } = usePlans();\n const { togglePlan, isToggling } = usePlanMutations();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showInactive, setShowInactive] = useState(false);\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n\n // Permission check\n if (!permissions.canViewPlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noViewPermission',\n \"You don't have permission to view pricing plans.\"\n )}\n />\n );\n }\n\n // Filter plans\n const filteredPlans = plans.filter((plan) => {\n if (!showInactive && !plan.isActive) return false;\n if (billingInterval !== 'all' && plan.duration !== billingInterval) return false;\n return true;\n });\n\n const handleTogglePlan = async (plan: Plan) => {\n if (!permissions.canManagePlans) return;\n try {\n await togglePlan(plan.id, !plan.isActive);\n await refetch();\n } catch (err) {\n console.error('Failed to toggle plan:', err);\n }\n };\n\n const handleSelectPlan = (plan: Plan) => {\n navigateTo(`/checkout/${plan.id}`);\n };\n\n const handleViewPlan = (plan: Plan) => {\n navigateTo(`/plans/${plan.id}`);\n };\n\n // Loading state\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3, 4].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state - show server error component for 500 errors\n if (error) {\n if (isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableMessage',\n 'Unable to load pricing plans. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n // Generic error for non-server errors\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.failedToLoad', 'Failed to load plans')}\n </h2>\n <p className=\"text-sm text-text-muted\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.plans.title', 'Pricing Plans')}\n </h1>\n <p className=\"text-sm text-text-muted mt-1\">\n {tr('billing.plans.subtitle', 'Choose the plan that best fits your needs')}\n </p>\n </div>\n\n {permissions.canManagePlans && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans/new')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Create Plan\n </button>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Billing Interval Toggle */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setBillingInterval('all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'all'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('monthly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'monthly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Monthly\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('yearly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'yearly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Yearly\n </button>\n </div>\n\n {/* Show Inactive Toggle */}\n {permissions.canManagePlans && (\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-text-muted\">\n {tr('billing.plans.showInactive', 'Show inactive plans')}\n </span>\n </label>\n )}\n\n {/* View Mode Toggle */}\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans Grid/List */}\n {filteredPlans.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-16 border border-dashed border-border-seam rounded-card bg-bg-surface\">\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-muted\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 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 <h3 className=\"text-lg font-medium text-text-primary\">\n {tr('billing.plans.noPlansFound', 'No plans found')}\n </h3>\n <p className=\"text-sm text-text-muted mt-1\">\n {showInactive\n ? tr('billing.plans.noPlansFiltered', 'No plans match your current filters.')\n : tr('billing.plans.noActivePlans', 'There are no active plans available.')}\n </p>\n {permissions.canManagePlans && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans/new')}\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\"\n >\n {tr('billing.plans.createFirstPlan', 'Create your first plan')}\n </button>\n )}\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredPlans.map((plan) => (\n <PlanCard\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <PlanRow\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Plan Card Component\n// ============================================================================\n\ninterface PlanCardProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanCard: FC<PlanCardProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`relative border rounded-card p-6 flex flex-col h-full transition-[box-shadow,transform] duration-200 ease-[var(--motion-easing-out)] ${\n plan.isActive\n ? 'border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:shadow-[var(--shadow-elevation-2)] [@media(hover:none)]:'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30 shadow-elevation-1'\n }`}\n >\n {/* Status Badge */}\n {!plan.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n\n {/* Plan Name */}\n <div className=\"mb-4\">\n <h3 className=\"text-xl font-bold text-text-primary\">{plan.name}</h3>\n <span className=\"text-sm text-text-muted\">{formatPlanDurationLabel(plan.duration)}</span>\n </div>\n\n {/* Price */}\n <div className=\"flex items-baseline gap-1 mb-1\">\n <span className=\"text-3xl font-bold text-text-primary\">\n {(() => {\n const base =\n plan.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? { price: plan.pricePerSeat, currency: plan.currency }\n : { price: plan.price, currency: plan.currency };\n const { price, currency } = getDisplayPrice(\n base.price,\n base.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-text-muted\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n {plan.pricingModel === PricingModel.PER_SEAT && (\n <p className=\"text-xs text-text-muted mb-4\">\n per seat\n {plan.minSeats ? ` · min ${plan.minSeats} seat${plan.minSeats !== 1 ? 's' : ''}` : ''}\n </p>\n )}\n {plan.pricingModel !== PricingModel.PER_SEAT && <div className=\"mb-6\" />}\n\n {/* Features - Combined Quotas */}\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-6\">\n {combinedQuotas.slice(0, 5).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-5 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-text-primary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 5 && (\n <li className=\"text-sm text-text-muted pl-7\">\n +{combinedQuotas.length - 5} more quotas\n </li>\n )}\n </ul>\n )}\n\n {/* Actions - pushed to bottom with mt-auto */}\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n {canSelect && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => onSelect(plan)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Select Plan\n </button>\n )}\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => onView(plan)}\n className=\"flex-1 px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View Details\n </button>\n {canManage && (\n <button\n type=\"button\"\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-4 py-2 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n plan.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Plan Row Component (List View)\n// ============================================================================\n\ninterface PlanRowProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanRow: FC<PlanRowProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-card shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] ${\n plan.isActive\n ? 'border-border-subtle bg-bg-surface'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30'\n }`}\n >\n {/* Plan Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{plan.name}</h3>\n {!plan.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-muted\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n {/* Price */}\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-muted\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n {canSelect && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => onSelect(plan)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Select\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onView(plan)}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View\n </button>\n {canManage && (\n <button\n type=\"button\"\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n plan.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlansListPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansListPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans List Page\n * Displays all available pricing plans\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlans, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport type { Plan, PlanDuration, PlanFeature } from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const PlansListPage: 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 { plans, isLoading, error, refetch } = usePlans();\n const { togglePlan, isToggling } = usePlanMutations();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showInactive, setShowInactive] = useState(false);\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n\n // Permission check\n if (!permissions.canViewPlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noViewPermission',\n \"You don't have permission to view pricing plans.\"\n )}\n />\n );\n }\n\n // Filter plans\n const filteredPlans = plans.filter((plan) => {\n if (!showInactive && !plan.isActive) return false;\n if (billingInterval !== 'all' && plan.duration !== billingInterval) return false;\n return true;\n });\n\n const handleTogglePlan = async (plan: Plan) => {\n if (!permissions.canManagePlans) return;\n try {\n await togglePlan(plan.id, !plan.isActive);\n await refetch();\n } catch (err) {\n console.error('Failed to toggle plan:', err);\n }\n };\n\n const handleSelectPlan = (plan: Plan) => {\n navigateTo(`/checkout/${plan.id}`);\n };\n\n const handleViewPlan = (plan: Plan) => {\n navigateTo(`/plans/${plan.id}`);\n };\n\n // Loading state\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3, 4].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state - show server error component for 500 errors\n if (error) {\n if (isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableMessage',\n 'Unable to load pricing plans. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n // Generic error for non-server errors\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center\">\n <svg\n className=\"size-6 text-status-error-text\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.failedToLoad', 'Failed to load plans')}\n </h2>\n <p className=\"text-sm text-text-muted\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.plans.title', 'Pricing Plans')}\n </h1>\n <p className=\"text-sm text-text-muted mt-1\">\n {tr('billing.plans.subtitle', 'Choose the plan that best fits your needs')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.listPurpose',\n 'Manage the full catalog of pricing plans for this product — including inactive ones. Create new tiers, edit the quotas and price each plan grants, and activate or deactivate plans to control what customers can subscribe to.'\n )}\n </PagePurpose>\n </div>\n\n {permissions.canManagePlans && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans/new')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Create Plan\n </button>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Billing Interval Toggle */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setBillingInterval('all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'all'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('monthly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'monthly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Monthly\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('yearly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'yearly'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n >\n Yearly\n </button>\n </div>\n\n {/* Show Inactive Toggle */}\n {permissions.canManagePlans && (\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-text-muted\">\n {tr('billing.plans.showInactive', 'Show inactive plans')}\n </span>\n </label>\n )}\n\n {/* View Mode Toggle */}\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-muted hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans Grid/List */}\n {filteredPlans.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.plans.noPlansFound', 'No plans found')}\n description={\n showInactive\n ? tr('billing.plans.noPlansFiltered', 'No plans match your current filters.')\n : tr('billing.plans.noActivePlans', 'There are no active plans available.')\n }\n action={\n permissions.canManagePlans ? (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans/new')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.plans.createFirstPlan', 'Create your first plan')}\n </button>\n ) : undefined\n }\n />\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredPlans.map((plan) => (\n <PlanCard\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <PlanRow\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Plan Card Component\n// ============================================================================\n\ninterface PlanCardProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanCard: FC<PlanCardProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`relative border rounded-card p-6 flex flex-col h-full transition-[box-shadow,transform] duration-200 ease-[var(--motion-easing-out)] ${\n plan.isActive\n ? 'border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:shadow-[var(--shadow-elevation-2)] [@media(hover:none)]:'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30 shadow-elevation-1'\n }`}\n >\n {/* Status Badge */}\n {!plan.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n\n {/* Plan Name */}\n <div className=\"mb-4\">\n <h3 className=\"text-xl font-bold text-text-primary\">{plan.name}</h3>\n <span className=\"text-sm text-text-muted\">{formatPlanDurationLabel(plan.duration)}</span>\n </div>\n\n {/* Price */}\n <div className=\"flex items-baseline gap-1 mb-1\">\n <span className=\"text-3xl font-bold text-text-primary\">\n {(() => {\n const base =\n plan.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? { price: plan.pricePerSeat, currency: plan.currency }\n : { price: plan.price, currency: plan.currency };\n const { price, currency } = getDisplayPrice(\n base.price,\n base.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-text-muted\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n {plan.pricingModel === PricingModel.PER_SEAT && (\n <p className=\"text-xs text-text-muted mb-4\">\n per seat\n {plan.minSeats ? ` · min ${plan.minSeats} seat${plan.minSeats !== 1 ? 's' : ''}` : ''}\n </p>\n )}\n {plan.pricingModel !== PricingModel.PER_SEAT && <div className=\"mb-6\" />}\n\n {/* Features - Combined Quotas */}\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-6\">\n {combinedQuotas.slice(0, 5).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-5 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-text-primary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 5 && (\n <li className=\"text-sm text-text-muted pl-7\">\n +{combinedQuotas.length - 5} more quotas\n </li>\n )}\n </ul>\n )}\n\n {/* Actions - pushed to bottom with mt-auto */}\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n {canSelect && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => onSelect(plan)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Select Plan\n </button>\n )}\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => onView(plan)}\n className=\"flex-1 px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View Details\n </button>\n {canManage && (\n <button\n type=\"button\"\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-4 py-2 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n plan.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Plan Row Component (List View)\n// ============================================================================\n\ninterface PlanRowProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanRow: FC<PlanRowProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-card shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)] ${\n plan.isActive\n ? 'border-border-subtle bg-bg-surface'\n : 'border-dashed border-text-muted/30 bg-bg-sunken/30'\n }`}\n >\n {/* Plan Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{plan.name}</h3>\n {!plan.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-muted\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n {/* Price */}\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-muted\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-2\">\n {canSelect && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => onSelect(plan)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors\"\n >\n Select\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onView(plan)}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n View\n </button>\n {canManage && (\n <button\n type=\"button\"\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-3 py-1.5 text-sm font-medium border border-border-subtle rounded-md transition-colors ${\n plan.isActive\n ? 'text-status-error-text hover:bg-status-error-bg-subtle'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,SAAS,EAAc,GAA0C;CAC/D,IAAM,oBAAW,IAAI,KAA4B;AAEjD,MAAK,IAAM,KAAW,GAAU;AAC9B,MAAI,CAAC,EAAQ,MAAO;EAEpB,IAAM,IAAU,EAAQ,MAAM,IACxB,IAAa,EAAuB,EAAQ,EAE5C,IAAW,EAAS,IAAI,EAAQ;AACtC,EAAI,IACF,EAAS,cAAc,IAEvB,EAAS,IAAI,GAAS;GACpB;GACA,MAAM,EAAQ,MAAM,QAAQ;GAC5B,YAAY;GACb,CAAC;;AAIN,QAAO,MAAM,KAAK,EAAS,QAAQ,CAAC;;AAGtC,IAAa,UAA0B;CACrC,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,IAAa,GAAoB,EACjC,IAAc,GAAuB,EACrC,EAAE,UAAO,cAAW,UAAO,eAAY,GAAU,EACjD,EAAE,eAAY,kBAAe,GAAkB,EAE/C,CAAC,GAAU,KAAe,EAAmB,OAAO,EACpD,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAiB,KAAsB,EAA+B,MAAM;AAGnF,KAAI,CAAC,EAAY,aACf,QACE,kBAAC,GAAD,EACE,SAAS,EACP,kCACA,mDACD,EACD,CAAA;CAKN,IAAM,IAAgB,EAAM,QAAQ,MAElC,EADI,CAAC,KAAgB,CAAC,EAAK,YACvB,MAAoB,SAAS,EAAK,aAAa,GAEnD,EAEI,IAAmB,OAAO,MAAe;AACxC,QAAY,eACjB,KAAI;AAEF,GADA,MAAM,EAAW,EAAK,IAAI,CAAC,EAAK,SAAS,EACzC,MAAM,GAAS;WACR,GAAK;AACZ,WAAQ,MAAM,0BAA0B,EAAI;;IAI1C,KAAoB,MAAe;AACvC,IAAW,aAAa,EAAK,KAAK;IAG9B,KAAkB,MAAe;AACrC,IAAW,UAAU,EAAK,KAAK;;AA6EjC,QAzEI,KAAa,EAAM,WAAW,IAE9B,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;cAAvB;KACE,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;KAC/D,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA;KAC/D,kBAAC,OAAD;MAAK,WAAU;gBACZ;OAAC;OAAG;OAAG;OAAG;OAAE,CAAC,KAAK,MACjB,kBAAC,OAAD,EAAa,WAAU,iDAAkD,EAA/D,EAA+D,CACzE;MACE,CAAA;KACF;MARI,EAQJ,CACN;GACE,CAAA,CACF;MAKN,IACE,EAAc,EAAM,GAEpB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,OAAO,EAAG,mCAAmC,qBAAqB;GAClE,SAAS,EACP,0CACA,iFACD;GACD,eAAe,GAAS;GACxB,WAAA;GACA,CAAA;EACE,CAAA,GAKR,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;eACX,EAAG,8BAA8B,uBAAuB;KACtD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAA2B,EAAM;KAAY,CAAA;IAC1D,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;KACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAG,uBAAuB,gBAAgB;MACxC,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,0BAA0B,4CAA4C;MACxE,CAAA;KACJ,kBAAC,GAAD;MAAa,WAAU;gBACpB,EACC,6BACA,kOACD;MACW,CAAA;KACV,EAAA,CAAA,EAEL,EAAY,kBACX,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,aAAa;KACvC,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,cAEC;OAEP;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAmB,MAAM;QACxC,WAAW,gEACT,MAAoB,QAChB,8CACA;kBAEP;QAEQ,CAAA;OACT,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAmB,UAA0B;QAC5D,WAAW,gEACT,MAAoB,YAChB,8CACA;kBAEP;QAEQ,CAAA;OACT,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAmB,SAAyB;QAC3D,WAAW,gEACT,MAAoB,WAChB,8CACA;kBAEP;QAEQ,CAAA;OACL;;KAGL,EAAY,kBACX,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS;OACT,WAAW,MAAM,EAAgB,EAAE,OAAO,QAAQ;OAClD,WAAU;OACV,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,8BAA8B,sBAAsB;OACnD,CAAA,CACD;;KAIV,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAY,OAAO;OAClC,WAAW,sCACT,MAAa,SACT,8CACA;OAEN,OAAM;iBAEN,kBAAC,OAAD;QAAK,WAAU;QAAS,MAAK;QAAO,SAAQ;QAAY,QAAO;kBAC7D,kBAAC,QAAD;SACE,eAAc;SACd,gBAAe;SACf,aAAa;SACb,GAAE;SACF,CAAA;QACE,CAAA;OACC,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAY,OAAO;OAClC,WAAW,sCACT,MAAa,SACT,8CACA;OAEN,OAAM;iBAEN,kBAAC,OAAD;QAAK,WAAU;QAAS,MAAK;QAAO,SAAQ;QAAY,QAAO;kBAC7D,kBAAC,QAAD;SACE,eAAc;SACd,gBAAe;SACf,aAAa;SACb,GAAE;SACF,CAAA;QACE,CAAA;OACC,CAAA,CACL;;KACF;;GAGL,EAAc,WAAW,IACxB,kBAAC,GAAD;IACE,cAAa;IACb,OAAO,EAAG,8BAA8B,iBAAiB;IACzD,aACE,IACI,EAAG,iCAAiC,uCAAuC,GAC3E,EAAG,+BAA+B,uCAAuC;IAE/E,QACE,EAAY,iBACV,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,aAAa;KACvC,WAAU;eAET,EAAG,iCAAiC,yBAAyB;KACvD,CAAA,GACP,KAAA;IAEN,CAAA,GACA,MAAa,SACf,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAc,KAAK,MAClB,kBAAC,GAAD;KAEQ;KACN,UAAU;KACV,QAAQ;KACR,UAAU;KACV,WAAW,EAAY;KACvB,WAAW,EAAY;KACX;KACZ,EARK,EAAK,GAQV,CACF;IACE,CAAA,GAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAc,KAAK,MAClB,kBAAC,GAAD;KAEQ;KACN,UAAU;KACV,QAAQ;KACR,UAAU;KACV,WAAW,EAAY;KACvB,WAAW,EAAY;KACX;KACZ,EARK,EAAK,GAQV,CACF;IACE,CAAA;GAEJ;;GAkBJ,KAA+B,EACnC,SACA,aACA,WACA,aACA,cACA,cACA,oBACI;CACJ,IAAM,IAAkB,GAA2B,EAC7C,IAAiB,QAEd,EADU,EAAK,YAAY,EAAE,CACN,EAC7B,CAAC,EAAK,SAAS,CAAC;AAEnB,QACE,kBAAC,OAAD;EACE,WAAW,wIACT,EAAK,WACD,+HACA;YAJR;GAQG,CAAC,EAAK,YACL,kBAAC,QAAD;IAAM,WAAU;cAA4F;IAErG,CAAA;GAIT,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAuC,EAAK;KAAU,CAAA,EACpE,kBAAC,QAAD;KAAM,WAAU;eAA2B,EAAwB,EAAK,SAAS;KAAQ,CAAA,CACrF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;sBACN;MACN,IAAM,IACJ,EAAK,iBAAiB,EAAa,YAAY,EAAK,eAChD;OAAE,OAAO,EAAK;OAAc,UAAU,EAAK;OAAU,GACrD;OAAE,OAAO,EAAK;OAAO,UAAU,EAAK;OAAU,EAC9C,EAAE,UAAO,gBAAa,EAC1B,EAAK,OACL,EAAK,UACL,EAAK,gBACL,EACD;AACD,aAAO,EAAe,GAAO,EAAS;SACpC;KACC,CAAA,EACP,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAkC,MAAG,EAAmB,EAAK,SAAS,CAAQ;OAC1E;;GACL,EAAK,iBAAiB,EAAa,YAClC,kBAAC,KAAD;IAAG,WAAU;cAAb,CAA4C,YAEzC,EAAK,WAAW,UAAU,EAAK,SAAS,OAAO,EAAK,aAAa,IAAU,KAAN,QAAa,GACjF;;GAEL,EAAK,iBAAiB,EAAa,YAAY,kBAAC,OAAD,EAAK,WAAU,QAAS,CAAA;GAGvE,EAAe,SAAS,KACvB,kBAAC,MAAD;IAAI,WAAU;cAAd,CACG,EAAe,MAAM,GAAG,EAAE,CAAC,KAAK,MAC/B,kBAAC,MAAD;KAAwB,WAAU;eAAlC,CACE,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,QAAD;OAAM,WAAU;iBAAqB,EAAM;OAAY,CAAA,CACnD;SACN,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAM,WAAW,gBAAgB;MAC7B,CAAA,CACJ;OApBI,EAAM,QAoBV,CACL,EACD,EAAe,SAAS,KACvB,kBAAC,MAAD;KAAI,WAAU;eAAd;MAA6C;MACzC,EAAe,SAAS;MAAE;MACzB;OAEJ;;GAIP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KAAa,EAAK,YACjB,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,EAAK;KAC7B,WAAU;eACX;KAEQ,CAAA,EAEX,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAO,EAAK;MAC3B,WAAU;gBACX;MAEQ,CAAA,EACR,KACC,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,EAAK;MAC7B,UAAU;MACV,WAAW,0FACT,EAAK,WACD,2DACA;gBAGL,EAAK,WAAW,eAAe;MACzB,CAAA,CAEP;OACF;;GACF;;GAkBJ,KAA6B,EACjC,SACA,aACA,WACA,aACA,cACA,cACA,oBACI;CACJ,IAAM,IAAkB,GAA2B,EAC7C,IAAiB,QAEd,EADU,EAAK,YAAY,EAAE,CACN,EAC7B,CAAC,EAAK,SAAS,CAAC;AAEnB,QACE,kBAAC,OAAD;EACE,WAAW,sLACT,EAAK,WACD,uCACA;YAJR;GAQE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA4C,EAAK;MAAU,CAAA,EACxE,CAAC,EAAK,YACL,kBAAC,QAAD;MAAM,WAAU;gBAAuE;MAEhF,CAAA,CAEL;QACN,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAe;MAAO;MAAO,EAAe,WAAW,IAAU,KAAN;MAAS;MACnE;OACA;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;sBACL;MACN,IAAM,EAAE,UAAO,gBAAa,EAC1B,EAAK,OACL,EAAK,UACL,EAAK,gBACL,EACD;AACD,aAAO,EAAe,GAAO,EAAS;SACpC;KACA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAAyC,QAAK,EAAmB,EAAK,SAAS,CAAO;OAClF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,KAAa,EAAK,YACjB,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,EAAK;MAC7B,WAAU;gBACX;MAEQ,CAAA;KAEX,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAO,EAAK;MAC3B,WAAU;gBACX;MAEQ,CAAA;KACR,KACC,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,EAAK;MAC7B,UAAU;MACV,WAAW,4FACT,EAAK,WACD,2DACA;gBAGL,EAAK,WAAW,eAAe;MACzB,CAAA;KAEP;;GACF"}
|