@burdenoff/microfe-billing 2026.803.1 → 2026.805.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/plans/pages/PlanBrowseDetailPage.js +97 -97
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +108 -108
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/shared/utils/planFeatureQuantity.js +21 -1
- package/dist/billing/shared/utils/planFeatureQuantity.js.map +1 -1
- package/dist/generated/global-operations.js +1 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,72 +1,72 @@
|
|
|
1
1
|
import { formatCurrency as e, formatPlanDuration as t, formatPlanDurationLabel as n, formatQuotaValue as r } from "../../../shared/utils/format.js";
|
|
2
|
-
import { getPlanFeatureQuantity as i, getPlanFeatureUnit as a } from "../../../shared/utils/planFeatureQuantity.js";
|
|
3
|
-
import { useBillingPermissions as
|
|
4
|
-
import { useBillingNavigate as
|
|
5
|
-
import { usePlan as
|
|
2
|
+
import { getPlanFeatureQuantity as i, getPlanFeatureUnit as a, getQuotaDisplayName as o } from "../../../shared/utils/planFeatureQuantity.js";
|
|
3
|
+
import { useBillingPermissions as s } from "../../../hooks/useBillingPermissions.js";
|
|
4
|
+
import { useBillingNavigate as c } from "../../../hooks/useBillingNavigate.js";
|
|
5
|
+
import { usePlan as l, usePlanMutations as u } from "../hooks/usePlans.js";
|
|
6
6
|
import "../hooks/index.js";
|
|
7
|
-
import { useMemo as
|
|
8
|
-
import { useParams as
|
|
9
|
-
import { Fragment as
|
|
10
|
-
import { useI18n as
|
|
11
|
-
import { EmphasisPanel as
|
|
7
|
+
import { useMemo as d } from "react";
|
|
8
|
+
import { useParams as f } from "react-router";
|
|
9
|
+
import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
10
|
+
import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { EmphasisPanel as _, PagePurpose as v } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/billing/modules/plans/pages/PlanDetailPage.tsx
|
|
13
|
-
function
|
|
13
|
+
function y(e) {
|
|
14
14
|
let t = /* @__PURE__ */ new Map();
|
|
15
15
|
for (let n of e) {
|
|
16
16
|
if (!n.quota) continue;
|
|
17
|
-
let e = n.quota.id, r = n.quota.limits?.type ?? "unknown",
|
|
18
|
-
|
|
17
|
+
let e = n.quota.id, r = n.quota.limits?.type ?? "unknown", s = i(n), c = t.get(e);
|
|
18
|
+
c ? (c.totalValue += s, c.count += 1) : t.set(e, {
|
|
19
19
|
quotaId: e,
|
|
20
|
-
name: n
|
|
20
|
+
name: o(n),
|
|
21
21
|
quotaType: n.quota.quotaType ?? "",
|
|
22
22
|
limitType: r,
|
|
23
|
-
totalValue:
|
|
23
|
+
totalValue: s,
|
|
24
24
|
count: 1,
|
|
25
25
|
unit: a(n)
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
return Array.from(t.values());
|
|
29
29
|
}
|
|
30
|
-
var
|
|
31
|
-
let { t: i } =
|
|
30
|
+
var b = () => {
|
|
31
|
+
let { t: i } = g(), a = (e, t) => {
|
|
32
32
|
let n = i(e);
|
|
33
33
|
return n === e ? t : n;
|
|
34
|
-
}, { planId:
|
|
35
|
-
return x.canViewPlans ? C ? /* @__PURE__ */
|
|
34
|
+
}, { planId: o } = f(), b = c(), x = s(), { plan: S, isLoading: C, error: w, refetch: T } = l(o), { togglePlan: E, isToggling: D } = u(), O = d(() => y(S?.features || []), [S?.features]);
|
|
35
|
+
return x.canViewPlans ? C ? /* @__PURE__ */ h("div", {
|
|
36
36
|
className: "space-y-6 p-6",
|
|
37
|
-
children: [/* @__PURE__ */
|
|
37
|
+
children: [/* @__PURE__ */ h("div", {
|
|
38
38
|
className: "flex items-center gap-4",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
-
}), /* @__PURE__ */
|
|
39
|
+
children: [/* @__PURE__ */ m("div", { className: "size-10 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ m("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" })]
|
|
40
|
+
}), /* @__PURE__ */ h("div", {
|
|
41
41
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
42
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ m("div", {
|
|
43
43
|
className: "lg:col-span-2 space-y-6",
|
|
44
|
-
children: /* @__PURE__ */
|
|
44
|
+
children: /* @__PURE__ */ h("div", {
|
|
45
45
|
className: "border border-border-subtle rounded-lg p-6 space-y-4",
|
|
46
46
|
children: [
|
|
47
|
-
/* @__PURE__ */
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
/* @__PURE__ */
|
|
47
|
+
/* @__PURE__ */ m("div", { className: "h-6 w-32 bg-bg-sunken animate-pulse rounded" }),
|
|
48
|
+
/* @__PURE__ */ m("div", { className: "h-4 w-full bg-bg-sunken animate-pulse rounded" }),
|
|
49
|
+
/* @__PURE__ */ m("div", { className: "h-4 w-3/4 bg-bg-sunken animate-pulse rounded" })
|
|
50
50
|
]
|
|
51
51
|
})
|
|
52
|
-
}), /* @__PURE__ */
|
|
52
|
+
}), /* @__PURE__ */ h("div", {
|
|
53
53
|
className: "border border-border-subtle rounded-lg p-6 space-y-4",
|
|
54
|
-
children: [/* @__PURE__ */
|
|
54
|
+
children: [/* @__PURE__ */ m("div", { className: "h-6 w-24 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ m("div", { className: "h-10 w-full bg-bg-sunken animate-pulse rounded" })]
|
|
55
55
|
})]
|
|
56
56
|
})]
|
|
57
|
-
}) : w || !S ? /* @__PURE__ */
|
|
57
|
+
}) : w || !S ? /* @__PURE__ */ m("div", {
|
|
58
58
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
59
|
-
children: /* @__PURE__ */
|
|
59
|
+
children: /* @__PURE__ */ h("div", {
|
|
60
60
|
className: "text-center space-y-4",
|
|
61
61
|
children: [
|
|
62
|
-
/* @__PURE__ */
|
|
62
|
+
/* @__PURE__ */ m("div", {
|
|
63
63
|
className: "size-12 mx-auto rounded-full bg-status-error-bg-subtle flex items-center justify-center",
|
|
64
|
-
children: /* @__PURE__ */
|
|
64
|
+
children: /* @__PURE__ */ m("svg", {
|
|
65
65
|
className: "size-6 text-status-error-text",
|
|
66
66
|
fill: "none",
|
|
67
67
|
viewBox: "0 0 24 24",
|
|
68
68
|
stroke: "currentColor",
|
|
69
|
-
children: /* @__PURE__ */
|
|
69
|
+
children: /* @__PURE__ */ m("path", {
|
|
70
70
|
strokeLinecap: "round",
|
|
71
71
|
strokeLinejoin: "round",
|
|
72
72
|
strokeWidth: 2,
|
|
@@ -74,15 +74,15 @@ var y = () => {
|
|
|
74
74
|
})
|
|
75
75
|
})
|
|
76
76
|
}),
|
|
77
|
-
/* @__PURE__ */
|
|
77
|
+
/* @__PURE__ */ m("h2", {
|
|
78
78
|
className: "text-lg font-semibold text-text-primary",
|
|
79
79
|
children: a("billing.plans.planNotFound", "Plan not found")
|
|
80
80
|
}),
|
|
81
|
-
/* @__PURE__ */
|
|
81
|
+
/* @__PURE__ */ m("p", {
|
|
82
82
|
className: "text-sm text-text-muted",
|
|
83
83
|
children: w?.message || a("billing.plans.planNotFoundMessage", "The plan you are looking for does not exist.")
|
|
84
84
|
}),
|
|
85
|
-
/* @__PURE__ */
|
|
85
|
+
/* @__PURE__ */ m("button", {
|
|
86
86
|
type: "button",
|
|
87
87
|
onClick: () => b("/plans"),
|
|
88
88
|
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",
|
|
@@ -90,61 +90,61 @@ var y = () => {
|
|
|
90
90
|
})
|
|
91
91
|
]
|
|
92
92
|
})
|
|
93
|
-
}) : /* @__PURE__ */
|
|
93
|
+
}) : /* @__PURE__ */ h("div", {
|
|
94
94
|
className: "space-y-6 p-6",
|
|
95
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ h("div", {
|
|
96
96
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
97
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ h("div", {
|
|
98
98
|
className: "flex items-start gap-4",
|
|
99
|
-
children: [/* @__PURE__ */
|
|
99
|
+
children: [/* @__PURE__ */ m("button", {
|
|
100
100
|
type: "button",
|
|
101
101
|
onClick: () => b("/plans"),
|
|
102
102
|
className: "p-2 -ml-2 text-text-muted hover:text-text-primary hover:bg-bg-sunken rounded-md transition-colors",
|
|
103
|
-
children: /* @__PURE__ */
|
|
103
|
+
children: /* @__PURE__ */ m("svg", {
|
|
104
104
|
className: "size-5",
|
|
105
105
|
fill: "none",
|
|
106
106
|
viewBox: "0 0 24 24",
|
|
107
107
|
stroke: "currentColor",
|
|
108
|
-
children: /* @__PURE__ */
|
|
108
|
+
children: /* @__PURE__ */ m("path", {
|
|
109
109
|
strokeLinecap: "round",
|
|
110
110
|
strokeLinejoin: "round",
|
|
111
111
|
strokeWidth: 2,
|
|
112
112
|
d: "M15 19l-7-7 7-7"
|
|
113
113
|
})
|
|
114
114
|
})
|
|
115
|
-
}), /* @__PURE__ */
|
|
116
|
-
/* @__PURE__ */
|
|
115
|
+
}), /* @__PURE__ */ h("div", { children: [
|
|
116
|
+
/* @__PURE__ */ h("div", {
|
|
117
117
|
className: "flex items-center gap-3",
|
|
118
|
-
children: [/* @__PURE__ */
|
|
118
|
+
children: [/* @__PURE__ */ m("h1", {
|
|
119
119
|
className: "text-2xl md:text-3xl font-semibold tracking-tight text-text-primary",
|
|
120
120
|
children: S.name
|
|
121
|
-
}), /* @__PURE__ */
|
|
121
|
+
}), /* @__PURE__ */ m("span", {
|
|
122
122
|
className: `px-2 py-1 text-xs font-medium rounded ${S.isActive ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-muted"}`,
|
|
123
123
|
children: S.isActive ? "Active" : "Inactive"
|
|
124
124
|
})]
|
|
125
125
|
}),
|
|
126
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ h("p", {
|
|
127
127
|
className: "text-sm text-text-muted mt-1",
|
|
128
128
|
children: [n(S.duration), " billing plan"]
|
|
129
129
|
}),
|
|
130
|
-
/* @__PURE__ */
|
|
130
|
+
/* @__PURE__ */ m(v, {
|
|
131
131
|
className: "mt-3",
|
|
132
132
|
children: a("billing.plans.detailPurpose", "A full breakdown of this plan — its price, billing period and every quota and feature it grants. Use it to decide whether the plan fits before subscribing, or (as an admin) to edit or activate it.")
|
|
133
133
|
})
|
|
134
134
|
] })]
|
|
135
|
-
}), /* @__PURE__ */
|
|
135
|
+
}), /* @__PURE__ */ h("div", {
|
|
136
136
|
className: "flex items-center gap-2",
|
|
137
|
-
children: [x.canCreateSubscription && S.isActive && /* @__PURE__ */
|
|
137
|
+
children: [x.canCreateSubscription && S.isActive && /* @__PURE__ */ m("button", {
|
|
138
138
|
type: "button",
|
|
139
139
|
onClick: () => b(`/checkout/${S.id}`),
|
|
140
140
|
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",
|
|
141
141
|
children: "Subscribe to Plan"
|
|
142
|
-
}), x.canManagePlans && /* @__PURE__ */
|
|
142
|
+
}), x.canManagePlans && /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m("button", {
|
|
143
143
|
type: "button",
|
|
144
144
|
onClick: () => b(`/plans/${S.id}/edit`),
|
|
145
145
|
className: "px-4 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors",
|
|
146
146
|
children: "Edit Plan"
|
|
147
|
-
}), /* @__PURE__ */
|
|
147
|
+
}), /* @__PURE__ */ m("button", {
|
|
148
148
|
type: "button",
|
|
149
149
|
onClick: async () => {
|
|
150
150
|
if (x.canManagePlans) try {
|
|
@@ -158,28 +158,28 @@ var y = () => {
|
|
|
158
158
|
children: D ? a("billing.plans.processing", "Processing...") : S.isActive ? a("billing.plans.deactivate", "Deactivate") : a("billing.plans.activate", "Activate")
|
|
159
159
|
})] })]
|
|
160
160
|
})]
|
|
161
|
-
}), /* @__PURE__ */
|
|
161
|
+
}), /* @__PURE__ */ h("div", {
|
|
162
162
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
163
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ h("div", {
|
|
164
164
|
className: "lg:col-span-2 space-y-6",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
165
|
+
children: [/* @__PURE__ */ h(_, {
|
|
166
166
|
className: "p-6",
|
|
167
167
|
children: [
|
|
168
|
-
/* @__PURE__ */
|
|
168
|
+
/* @__PURE__ */ m("h2", {
|
|
169
169
|
className: "text-lg font-semibold text-text-primary mb-4",
|
|
170
170
|
children: a("billing.plans.pricing", "Pricing")
|
|
171
171
|
}),
|
|
172
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ h("div", {
|
|
173
173
|
className: "flex items-baseline gap-1",
|
|
174
|
-
children: [/* @__PURE__ */
|
|
174
|
+
children: [/* @__PURE__ */ m("span", {
|
|
175
175
|
className: "text-4xl font-bold text-text-primary",
|
|
176
176
|
children: e(S.price, S.currency)
|
|
177
|
-
}), /* @__PURE__ */
|
|
177
|
+
}), /* @__PURE__ */ h("span", {
|
|
178
178
|
className: "text-lg text-text-muted",
|
|
179
179
|
children: ["/ ", t(S.duration)]
|
|
180
180
|
})]
|
|
181
181
|
}),
|
|
182
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ h("p", {
|
|
183
183
|
className: "text-sm text-text-muted mt-2",
|
|
184
184
|
children: [
|
|
185
185
|
a("billing.plans.billed", "Billed"),
|
|
@@ -188,11 +188,11 @@ var y = () => {
|
|
|
188
188
|
]
|
|
189
189
|
})
|
|
190
190
|
]
|
|
191
|
-
}), /* @__PURE__ */
|
|
191
|
+
}), /* @__PURE__ */ h("div", {
|
|
192
192
|
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
193
|
+
children: [/* @__PURE__ */ m("div", {
|
|
194
194
|
className: "p-6 border-b border-border-subtle bg-bg-sunken/30",
|
|
195
|
-
children: /* @__PURE__ */
|
|
195
|
+
children: /* @__PURE__ */ h("h2", {
|
|
196
196
|
className: "text-lg font-semibold text-text-primary",
|
|
197
197
|
children: [
|
|
198
198
|
a("billing.plans.featuresAndQuotas", "Features & Quotas"),
|
|
@@ -201,46 +201,46 @@ var y = () => {
|
|
|
201
201
|
")"
|
|
202
202
|
]
|
|
203
203
|
})
|
|
204
|
-
}), /* @__PURE__ */
|
|
204
|
+
}), /* @__PURE__ */ m("div", {
|
|
205
205
|
className: "p-6",
|
|
206
|
-
children: O.length === 0 ? /* @__PURE__ */
|
|
206
|
+
children: O.length === 0 ? /* @__PURE__ */ m("p", {
|
|
207
207
|
className: "text-sm text-text-muted",
|
|
208
208
|
children: a("billing.plans.noFeatures", "No features configured for this plan.")
|
|
209
|
-
}) : /* @__PURE__ */
|
|
209
|
+
}) : /* @__PURE__ */ m("ul", {
|
|
210
210
|
className: "space-y-3",
|
|
211
|
-
children: O.map((e) => /* @__PURE__ */
|
|
211
|
+
children: O.map((e) => /* @__PURE__ */ h("li", {
|
|
212
212
|
className: "flex items-start gap-3 p-3 rounded-lg bg-bg-sunken/30",
|
|
213
|
-
children: [/* @__PURE__ */
|
|
213
|
+
children: [/* @__PURE__ */ m("div", {
|
|
214
214
|
className: "size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0",
|
|
215
|
-
children: /* @__PURE__ */
|
|
215
|
+
children: /* @__PURE__ */ m("svg", {
|
|
216
216
|
className: "size-4 text-status-success-text",
|
|
217
217
|
fill: "none",
|
|
218
218
|
viewBox: "0 0 24 24",
|
|
219
219
|
stroke: "currentColor",
|
|
220
|
-
children: /* @__PURE__ */
|
|
220
|
+
children: /* @__PURE__ */ m("path", {
|
|
221
221
|
strokeLinecap: "round",
|
|
222
222
|
strokeLinejoin: "round",
|
|
223
223
|
strokeWidth: 2,
|
|
224
224
|
d: "M5 13l4 4L19 7"
|
|
225
225
|
})
|
|
226
226
|
})
|
|
227
|
-
}), /* @__PURE__ */
|
|
227
|
+
}), /* @__PURE__ */ h("div", {
|
|
228
228
|
className: "flex-1",
|
|
229
|
-
children: [/* @__PURE__ */
|
|
229
|
+
children: [/* @__PURE__ */ h("div", {
|
|
230
230
|
className: "flex items-center justify-between",
|
|
231
|
-
children: [/* @__PURE__ */
|
|
231
|
+
children: [/* @__PURE__ */ m("p", {
|
|
232
232
|
className: "font-medium text-text-primary",
|
|
233
233
|
children: e.name
|
|
234
|
-
}), /* @__PURE__ */
|
|
234
|
+
}), /* @__PURE__ */ m("span", {
|
|
235
235
|
className: "font-semibold text-text-primary",
|
|
236
236
|
children: r(e.totalValue, e.unit)
|
|
237
237
|
})]
|
|
238
|
-
}), /* @__PURE__ */
|
|
238
|
+
}), /* @__PURE__ */ h("div", {
|
|
239
239
|
className: "flex items-center gap-2 mt-1",
|
|
240
|
-
children: [/* @__PURE__ */
|
|
240
|
+
children: [/* @__PURE__ */ h("span", {
|
|
241
241
|
className: "text-sm text-text-muted capitalize",
|
|
242
242
|
children: ["Type: ", e.limitType]
|
|
243
|
-
}), e.quotaType && /* @__PURE__ */
|
|
243
|
+
}), e.quotaType && /* @__PURE__ */ m("span", {
|
|
244
244
|
className: "px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded",
|
|
245
245
|
children: e.quotaType
|
|
246
246
|
})]
|
|
@@ -250,40 +250,40 @@ var y = () => {
|
|
|
250
250
|
})
|
|
251
251
|
})]
|
|
252
252
|
})]
|
|
253
|
-
}), /* @__PURE__ */
|
|
253
|
+
}), /* @__PURE__ */ h("div", {
|
|
254
254
|
className: "space-y-6",
|
|
255
|
-
children: [/* @__PURE__ */
|
|
255
|
+
children: [/* @__PURE__ */ h("div", {
|
|
256
256
|
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]",
|
|
257
|
-
children: [/* @__PURE__ */
|
|
257
|
+
children: [/* @__PURE__ */ m("div", {
|
|
258
258
|
className: "p-6 border-b border-border-subtle bg-bg-sunken/30",
|
|
259
|
-
children: /* @__PURE__ */
|
|
259
|
+
children: /* @__PURE__ */ m("h2", {
|
|
260
260
|
className: "text-lg font-semibold text-text-primary",
|
|
261
261
|
children: a("billing.plans.planDetails", "Plan Details")
|
|
262
262
|
})
|
|
263
|
-
}), /* @__PURE__ */
|
|
263
|
+
}), /* @__PURE__ */ h("div", {
|
|
264
264
|
className: "p-6 space-y-4",
|
|
265
265
|
children: [
|
|
266
|
-
/* @__PURE__ */
|
|
266
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
267
267
|
className: "text-sm text-text-muted",
|
|
268
268
|
children: a("billing.plans.planName", "Plan Name")
|
|
269
|
-
}), /* @__PURE__ */
|
|
269
|
+
}), /* @__PURE__ */ m("p", {
|
|
270
270
|
className: "text-sm font-medium text-text-primary mt-0.5",
|
|
271
271
|
children: S.name
|
|
272
272
|
})] }),
|
|
273
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
274
274
|
className: "text-sm text-text-muted",
|
|
275
275
|
children: a("billing.plans.status", "Status")
|
|
276
|
-
}), /* @__PURE__ */
|
|
276
|
+
}), /* @__PURE__ */ m("p", {
|
|
277
277
|
className: "text-sm text-text-primary mt-0.5",
|
|
278
|
-
children: /* @__PURE__ */
|
|
278
|
+
children: /* @__PURE__ */ m("span", {
|
|
279
279
|
className: `inline-flex items-center px-2 py-0.5 text-xs font-medium rounded ${S.isActive ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-muted"}`,
|
|
280
280
|
children: S.isActive ? "Active" : "Inactive"
|
|
281
281
|
})
|
|
282
282
|
})] }),
|
|
283
|
-
/* @__PURE__ */
|
|
283
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
284
284
|
className: "text-sm text-text-muted",
|
|
285
285
|
children: a("billing.plans.price", "Price")
|
|
286
|
-
}), /* @__PURE__ */
|
|
286
|
+
}), /* @__PURE__ */ h("p", {
|
|
287
287
|
className: "text-sm font-medium text-text-primary mt-0.5",
|
|
288
288
|
children: [
|
|
289
289
|
e(S.price, S.currency),
|
|
@@ -291,38 +291,38 @@ var y = () => {
|
|
|
291
291
|
t(S.duration)
|
|
292
292
|
]
|
|
293
293
|
})] }),
|
|
294
|
-
/* @__PURE__ */
|
|
294
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
295
295
|
className: "text-sm text-text-muted",
|
|
296
296
|
children: a("billing.plans.currency", "Currency")
|
|
297
|
-
}), /* @__PURE__ */
|
|
297
|
+
}), /* @__PURE__ */ m("p", {
|
|
298
298
|
className: "text-sm text-text-primary mt-0.5",
|
|
299
299
|
children: S.currency
|
|
300
300
|
})] }),
|
|
301
|
-
/* @__PURE__ */
|
|
301
|
+
/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
|
|
302
302
|
className: "text-sm text-text-muted",
|
|
303
303
|
children: a("billing.plans.billingPeriod", "Billing Period")
|
|
304
|
-
}), /* @__PURE__ */
|
|
304
|
+
}), /* @__PURE__ */ m("p", {
|
|
305
305
|
className: "text-sm text-text-primary mt-0.5",
|
|
306
306
|
children: n(S.duration)
|
|
307
307
|
})] })
|
|
308
308
|
]
|
|
309
309
|
})]
|
|
310
|
-
}), /* @__PURE__ */
|
|
310
|
+
}), /* @__PURE__ */ h("div", {
|
|
311
311
|
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]",
|
|
312
|
-
children: [/* @__PURE__ */
|
|
312
|
+
children: [/* @__PURE__ */ m("div", {
|
|
313
313
|
className: "p-6 border-b border-border-subtle bg-bg-sunken/30",
|
|
314
|
-
children: /* @__PURE__ */
|
|
314
|
+
children: /* @__PURE__ */ m("h2", {
|
|
315
315
|
className: "text-lg font-semibold text-text-primary",
|
|
316
316
|
children: a("billing.plans.statistics", "Statistics")
|
|
317
317
|
})
|
|
318
|
-
}), /* @__PURE__ */
|
|
318
|
+
}), /* @__PURE__ */ m("div", {
|
|
319
319
|
className: "p-6 space-y-4",
|
|
320
|
-
children: /* @__PURE__ */
|
|
320
|
+
children: /* @__PURE__ */ h("div", {
|
|
321
321
|
className: "flex items-center justify-between",
|
|
322
|
-
children: [/* @__PURE__ */
|
|
322
|
+
children: [/* @__PURE__ */ m("p", {
|
|
323
323
|
className: "text-sm text-text-muted",
|
|
324
324
|
children: a("billing.plans.uniqueQuotas", "Unique Quotas")
|
|
325
|
-
}), /* @__PURE__ */
|
|
325
|
+
}), /* @__PURE__ */ m("p", {
|
|
326
326
|
className: "font-semibold text-text-primary",
|
|
327
327
|
children: O.length
|
|
328
328
|
})]
|
|
@@ -331,19 +331,19 @@ var y = () => {
|
|
|
331
331
|
})]
|
|
332
332
|
})]
|
|
333
333
|
})]
|
|
334
|
-
}) : /* @__PURE__ */
|
|
334
|
+
}) : /* @__PURE__ */ m("div", {
|
|
335
335
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
336
|
-
children: /* @__PURE__ */
|
|
336
|
+
children: /* @__PURE__ */ h("div", {
|
|
337
337
|
className: "text-center space-y-2",
|
|
338
338
|
children: [
|
|
339
|
-
/* @__PURE__ */
|
|
339
|
+
/* @__PURE__ */ m("div", {
|
|
340
340
|
className: "size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center",
|
|
341
|
-
children: /* @__PURE__ */
|
|
341
|
+
children: /* @__PURE__ */ m("svg", {
|
|
342
342
|
className: "size-6 text-text-muted",
|
|
343
343
|
fill: "none",
|
|
344
344
|
viewBox: "0 0 24 24",
|
|
345
345
|
stroke: "currentColor",
|
|
346
|
-
children: /* @__PURE__ */
|
|
346
|
+
children: /* @__PURE__ */ m("path", {
|
|
347
347
|
strokeLinecap: "round",
|
|
348
348
|
strokeLinejoin: "round",
|
|
349
349
|
strokeWidth: 2,
|
|
@@ -351,11 +351,11 @@ var y = () => {
|
|
|
351
351
|
})
|
|
352
352
|
})
|
|
353
353
|
}),
|
|
354
|
-
/* @__PURE__ */
|
|
354
|
+
/* @__PURE__ */ m("h2", {
|
|
355
355
|
className: "text-lg font-semibold text-text-primary",
|
|
356
356
|
children: a("billing.accessDenied.title", "Access Denied")
|
|
357
357
|
}),
|
|
358
|
-
/* @__PURE__ */
|
|
358
|
+
/* @__PURE__ */ m("p", {
|
|
359
359
|
className: "text-sm text-text-muted max-w-sm",
|
|
360
360
|
children: a("billing.plans.noViewPlanPermission", "You don't have permission to view this plan.")
|
|
361
361
|
})
|
|
@@ -364,6 +364,6 @@ var y = () => {
|
|
|
364
364
|
});
|
|
365
365
|
};
|
|
366
366
|
//#endregion
|
|
367
|
-
export {
|
|
367
|
+
export { b as PlanDetailPage };
|
|
368
368
|
|
|
369
369
|
//# sourceMappingURL=PlanDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanDetailPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Detail Page\n * Displays detailed information about a single plan\n */\n\nimport { type FC, useMemo } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n formatQuotaValue,\n} from '../../../shared/utils/format';\nimport {\n getPlanFeatureQuantity,\n getPlanFeatureUnit,\n} from '../../../shared/utils/planFeatureQuantity';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\n count: number;\n unit?: string;\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 limits = feature.quota.limits as { type?: string; value?: number } | null;\n const limitType = limits?.type ?? 'unknown';\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n existing.count += 1;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\n count: 1,\n unit: getPlanFeatureUnit(feature),\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const PlanDetailPage: 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 { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { plan, isLoading, error, refetch } = usePlan(planId);\n const { togglePlan, isToggling } = usePlanMutations();\n\n // Combine duplicate quotas for cleaner display (must be before any returns)\n const combinedQuotas = useMemo(() => combineQuotas(plan?.features || []), [plan?.features]);\n\n // Permission check\n if (!permissions.canViewPlans) {\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-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=\"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\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-muted max-w-sm\">\n {tr(\n 'billing.plans.noViewPlanPermission',\n \"You don't have permission to view this plan.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-3/4 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n <div 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-10 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !plan) {\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.planNotFound', 'Plan not found')}\n </h2>\n <p className=\"text-sm text-text-muted\">\n {error?.message ||\n tr(\n 'billing.plans.planNotFoundMessage',\n 'The plan you are looking for does not exist.'\n )}\n </p>\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\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n const handleToggle = async () => {\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 return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"p-2 -ml-2 text-text-muted hover:text-text-primary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n <svg className=\"size-5\" 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 <div>\n <div className=\"flex items-center gap-3\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {plan.name}\n </h1>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${\n plan.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <p className=\"text-sm text-text-muted mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.detailPurpose',\n 'A full breakdown of this plan — its price, billing period and every quota and feature it grants. Use it to decide whether the plan fits before subscribing, or (as an admin) to edit or activate it.'\n )}\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {permissions.canCreateSubscription && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\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 Subscribe to Plan\n </button>\n )}\n {permissions.canManagePlans && (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/plans/${plan.id}/edit`)}\n className=\"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 Edit Plan\n </button>\n <button\n type=\"button\"\n onClick={handleToggle}\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 {isToggling\n ? tr('billing.plans.processing', 'Processing...')\n : plan.isActive\n ? tr('billing.plans.deactivate', 'Deactivate')\n : tr('billing.plans.activate', 'Activate')}\n </button>\n </>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"p-6\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('billing.plans.pricing', 'Pricing')}\n </h2>\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(plan.price, plan.currency)}\n </span>\n <span className=\"text-lg text-text-muted\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n <p className=\"text-sm text-text-muted mt-2\">\n {tr('billing.plans.billed', 'Billed')}{' '}\n {plan.duration === 'monthly'\n ? tr('billing.plans.everyMonth', 'every month')\n : tr('billing.plans.annually', 'annually')}\n </p>\n </EmphasisPanel>\n\n {/* Features Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.featuresAndQuotas', 'Features & Quotas')} (\n {combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.noFeatures', 'No features configured for this plan.')}\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <li\n key={quota.quotaId}\n className=\"flex items-start gap-3 p-3 rounded-lg bg-bg-sunken/30\"\n >\n <div className=\"size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0\">\n <svg\n className=\"size-4 text-status-success-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=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <span className=\"font-semibold text-text-primary\">\n {formatQuotaValue(quota.totalValue, quota.unit)}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className=\"text-sm text-text-muted capitalize\">\n Type: {quota.limitType}\n </span>\n {quota.quotaType && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n {quota.quotaType}\n </span>\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Plan Details Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.planDetails', 'Plan Details')}\n </h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.planName', 'Plan Name')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{plan.name}</p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">{tr('billing.plans.status', 'Status')}</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n <span\n className={`inline-flex items-center px-2 py-0.5 text-xs font-medium rounded ${\n plan.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">{tr('billing.plans.price', 'Price')}</p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {formatCurrency(plan.price, plan.currency)} / {formatPlanDuration(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.currency', 'Currency')}\n </p>\n <p className=\"text-sm text-text-primary mt-0.5\">{plan.currency}</p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.billingPeriod', 'Billing Period')}\n </p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatPlanDurationLabel(plan.duration)}\n </p>\n </div>\n </div>\n </div>\n\n {/* Quick Stats Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.statistics', 'Statistics')}\n </h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.uniqueQuotas', 'Unique Quotas')}\n </p>\n <p className=\"font-semibold text-text-primary\">{combinedQuotas.length}</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanDetailPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Detail Page\n * Displays detailed information about a single plan\n */\n\nimport { type FC, useMemo } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n formatQuotaValue,\n} from '../../../shared/utils/format';\nimport {\n getPlanFeatureQuantity,\n getPlanFeatureUnit,\n getQuotaDisplayName,\n} from '../../../shared/utils/planFeatureQuantity';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\n count: number;\n unit?: string;\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 limits = feature.quota.limits as { type?: string; value?: number } | null;\n const limitType = limits?.type ?? 'unknown';\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n existing.count += 1;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: getQuotaDisplayName(feature),\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\n count: 1,\n unit: getPlanFeatureUnit(feature),\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const PlanDetailPage: 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 { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { plan, isLoading, error, refetch } = usePlan(planId);\n const { togglePlan, isToggling } = usePlanMutations();\n\n // Combine duplicate quotas for cleaner display (must be before any returns)\n const combinedQuotas = useMemo(() => combineQuotas(plan?.features || []), [plan?.features]);\n\n // Permission check\n if (!permissions.canViewPlans) {\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-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=\"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\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-muted max-w-sm\">\n {tr(\n 'billing.plans.noViewPlanPermission',\n \"You don't have permission to view this plan.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-10 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n </div>\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-6\">\n <div className=\"border border-border-subtle rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-4 w-3/4 bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n <div 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-10 w-full bg-bg-sunken animate-pulse rounded\" />\n </div>\n </div>\n </div>\n );\n }\n\n // Error state\n if (error || !plan) {\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.planNotFound', 'Plan not found')}\n </h2>\n <p className=\"text-sm text-text-muted\">\n {error?.message ||\n tr(\n 'billing.plans.planNotFoundMessage',\n 'The plan you are looking for does not exist.'\n )}\n </p>\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\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n const handleToggle = async () => {\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 return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"p-2 -ml-2 text-text-muted hover:text-text-primary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n <svg className=\"size-5\" 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 <div>\n <div className=\"flex items-center gap-3\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {plan.name}\n </h1>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${\n plan.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <p className=\"text-sm text-text-muted mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.detailPurpose',\n 'A full breakdown of this plan — its price, billing period and every quota and feature it grants. Use it to decide whether the plan fits before subscribing, or (as an admin) to edit or activate it.'\n )}\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {permissions.canCreateSubscription && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\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 Subscribe to Plan\n </button>\n )}\n {permissions.canManagePlans && (\n <>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/plans/${plan.id}/edit`)}\n className=\"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 Edit Plan\n </button>\n <button\n type=\"button\"\n onClick={handleToggle}\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 {isToggling\n ? tr('billing.plans.processing', 'Processing...')\n : plan.isActive\n ? tr('billing.plans.deactivate', 'Deactivate')\n : tr('billing.plans.activate', 'Activate')}\n </button>\n </>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Main Content */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Pricing Card — primary emphasis zone */}\n <EmphasisPanel className=\"p-6\">\n <h2 className=\"text-lg font-semibold text-text-primary mb-4\">\n {tr('billing.plans.pricing', 'Pricing')}\n </h2>\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-4xl font-bold text-text-primary\">\n {formatCurrency(plan.price, plan.currency)}\n </span>\n <span className=\"text-lg text-text-muted\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n <p className=\"text-sm text-text-muted mt-2\">\n {tr('billing.plans.billed', 'Billed')}{' '}\n {plan.duration === 'monthly'\n ? tr('billing.plans.everyMonth', 'every month')\n : tr('billing.plans.annually', 'annually')}\n </p>\n </EmphasisPanel>\n\n {/* Features Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.featuresAndQuotas', 'Features & Quotas')} (\n {combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-6\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.noFeatures', 'No features configured for this plan.')}\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <li\n key={quota.quotaId}\n className=\"flex items-start gap-3 p-3 rounded-lg bg-bg-sunken/30\"\n >\n <div className=\"size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0\">\n <svg\n className=\"size-4 text-status-success-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=\"M5 13l4 4L19 7\"\n />\n </svg>\n </div>\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between\">\n <p className=\"font-medium text-text-primary\">{quota.name}</p>\n <span className=\"font-semibold text-text-primary\">\n {formatQuotaValue(quota.totalValue, quota.unit)}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className=\"text-sm text-text-muted capitalize\">\n Type: {quota.limitType}\n </span>\n {quota.quotaType && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-bg-sunken text-text-muted rounded\">\n {quota.quotaType}\n </span>\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Plan Details Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.planDetails', 'Plan Details')}\n </h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div>\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.planName', 'Plan Name')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{plan.name}</p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">{tr('billing.plans.status', 'Status')}</p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n <span\n className={`inline-flex items-center px-2 py-0.5 text-xs font-medium rounded ${\n plan.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">{tr('billing.plans.price', 'Price')}</p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {formatCurrency(plan.price, plan.currency)} / {formatPlanDuration(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.currency', 'Currency')}\n </p>\n <p className=\"text-sm text-text-primary mt-0.5\">{plan.currency}</p>\n </div>\n <div>\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.billingPeriod', 'Billing Period')}\n </p>\n <p className=\"text-sm text-text-primary mt-0.5\">\n {formatPlanDurationLabel(plan.duration)}\n </p>\n </div>\n </div>\n </div>\n\n {/* Quick Stats Card */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"p-6 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.statistics', 'Statistics')}\n </h2>\n </div>\n <div className=\"p-6 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-muted\">\n {tr('billing.plans.uniqueQuotas', 'Unique Quotas')}\n </p>\n <p className=\"font-semibold text-text-primary\">{combinedQuotas.length}</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -9,7 +9,27 @@ function t(e) {
|
|
|
9
9
|
let t = e.context;
|
|
10
10
|
return typeof t?.unit == "string" ? t.unit : void 0;
|
|
11
11
|
}
|
|
12
|
+
var n = new Set([
|
|
13
|
+
"count",
|
|
14
|
+
"bytes",
|
|
15
|
+
"days",
|
|
16
|
+
"seconds",
|
|
17
|
+
"hours",
|
|
18
|
+
"monthly",
|
|
19
|
+
"daily",
|
|
20
|
+
"concurrent"
|
|
21
|
+
]);
|
|
22
|
+
function r(e) {
|
|
23
|
+
let t = e.split(".").filter(Boolean), r = t.length > 1 ? t.slice(1) : t, i = r[r.length - 1], a = (r.length > 1 && i && n.has(i) ? r.slice(0, -1) : r).join(" ").replace(/_/g, " ").trim().split(/\s+/).filter(Boolean);
|
|
24
|
+
return a.length === 0 ? e : a.map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ");
|
|
25
|
+
}
|
|
26
|
+
function i(e) {
|
|
27
|
+
let t = e.quota?.context;
|
|
28
|
+
if (t && typeof t.displayName == "string" && t.displayName) return t.displayName;
|
|
29
|
+
let n = e.quota?.name;
|
|
30
|
+
return n ? r(n) : "Unknown Quota";
|
|
31
|
+
}
|
|
12
32
|
//#endregion
|
|
13
|
-
export { e as getPlanFeatureQuantity, t as getPlanFeatureUnit };
|
|
33
|
+
export { e as getPlanFeatureQuantity, t as getPlanFeatureUnit, i as getQuotaDisplayName, r as humanizeQuotaName };
|
|
14
34
|
|
|
15
35
|
//# sourceMappingURL=planFeatureQuantity.js.map
|