@burdenoff/microfe-billing 2026.528.1 → 2026.529.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 +82 -81
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +105 -104
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +162 -161
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +85 -84
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +115 -114
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/shared/utils/index.js +1 -0
- package/dist/billing/shared/utils/planFeatureQuantity.js +11 -0
- package/dist/billing/shared/utils/planFeatureQuantity.js.map +1 -0
- package/dist/generated/global-operations.js +1 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { formatCurrency as e, formatPlanDuration as t, formatPlanDurationLabel as n } from "../../../shared/utils/format.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { getPlanFeatureQuantity as r } from "../../../shared/utils/planFeatureQuantity.js";
|
|
3
|
+
import { useBillingPermissions as i } from "../../../hooks/useBillingPermissions.js";
|
|
4
|
+
import { useBillingNavigate as a } from "../../../hooks/useBillingNavigate.js";
|
|
5
|
+
import { usePlan as o, usePlanMutations as s } from "../hooks/usePlans.js";
|
|
5
6
|
import "../hooks/index.js";
|
|
6
|
-
import { useMemo as
|
|
7
|
-
import { useParams as
|
|
8
|
-
import { Fragment as
|
|
9
|
-
import { useI18n as
|
|
7
|
+
import { useMemo as c } from "react";
|
|
8
|
+
import { useParams as l } from "react-router-dom";
|
|
9
|
+
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
10
|
+
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
11
|
//#region src/billing/modules/plans/pages/PlanDetailPage.tsx
|
|
11
|
-
function
|
|
12
|
+
function m(e) {
|
|
12
13
|
let t = /* @__PURE__ */ new Map();
|
|
13
14
|
for (let n of e) {
|
|
14
15
|
if (!n.quota) continue;
|
|
15
|
-
let e = n.quota.id,
|
|
16
|
+
let e = n.quota.id, i = n.quota.limits?.type ?? "unknown", a = r(n), o = t.get(e);
|
|
16
17
|
o ? (o.totalValue += a, o.count += 1) : t.set(e, {
|
|
17
18
|
quotaId: e,
|
|
18
19
|
name: n.quota.name ?? "Unknown Quota",
|
|
@@ -24,46 +25,46 @@ function p(e) {
|
|
|
24
25
|
}
|
|
25
26
|
return Array.from(t.values());
|
|
26
27
|
}
|
|
27
|
-
var
|
|
28
|
-
let { t:
|
|
29
|
-
let n =
|
|
28
|
+
var h = () => {
|
|
29
|
+
let { t: r } = p(), h = (e, t) => {
|
|
30
|
+
let n = r(e);
|
|
30
31
|
return n === e ? t : n;
|
|
31
|
-
}, { planId: g } =
|
|
32
|
-
return v.canViewPlans ? b ? /* @__PURE__ */
|
|
32
|
+
}, { planId: g } = l(), _ = a(), v = i(), { plan: y, isLoading: b, error: x, refetch: S } = o(g), { togglePlan: C, isToggling: w } = s(), T = c(() => m(y?.features || []), [y?.features]);
|
|
33
|
+
return v.canViewPlans ? b ? /* @__PURE__ */ f("div", {
|
|
33
34
|
className: "space-y-6 p-6",
|
|
34
|
-
children: [/* @__PURE__ */
|
|
35
|
+
children: [/* @__PURE__ */ f("div", {
|
|
35
36
|
className: "flex items-center gap-4",
|
|
36
|
-
children: [/* @__PURE__ */
|
|
37
|
-
}), /* @__PURE__ */
|
|
37
|
+
children: [/* @__PURE__ */ d("div", { className: "size-10 bg-muted animate-pulse rounded" }), /* @__PURE__ */ d("div", { className: "h-8 w-48 bg-muted animate-pulse rounded" })]
|
|
38
|
+
}), /* @__PURE__ */ f("div", {
|
|
38
39
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
+
children: [/* @__PURE__ */ d("div", {
|
|
40
41
|
className: "lg:col-span-2 space-y-6",
|
|
41
|
-
children: /* @__PURE__ */
|
|
42
|
+
children: /* @__PURE__ */ f("div", {
|
|
42
43
|
className: "border border-border rounded-lg p-6 space-y-4",
|
|
43
44
|
children: [
|
|
44
|
-
/* @__PURE__ */
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
/* @__PURE__ */
|
|
45
|
+
/* @__PURE__ */ d("div", { className: "h-6 w-32 bg-muted animate-pulse rounded" }),
|
|
46
|
+
/* @__PURE__ */ d("div", { className: "h-4 w-full bg-muted animate-pulse rounded" }),
|
|
47
|
+
/* @__PURE__ */ d("div", { className: "h-4 w-3/4 bg-muted animate-pulse rounded" })
|
|
47
48
|
]
|
|
48
49
|
})
|
|
49
|
-
}), /* @__PURE__ */
|
|
50
|
+
}), /* @__PURE__ */ f("div", {
|
|
50
51
|
className: "border border-border rounded-lg p-6 space-y-4",
|
|
51
|
-
children: [/* @__PURE__ */
|
|
52
|
+
children: [/* @__PURE__ */ d("div", { className: "h-6 w-24 bg-muted animate-pulse rounded" }), /* @__PURE__ */ d("div", { className: "h-10 w-full bg-muted animate-pulse rounded" })]
|
|
52
53
|
})]
|
|
53
54
|
})]
|
|
54
|
-
}) : x || !y ? /* @__PURE__ */
|
|
55
|
+
}) : x || !y ? /* @__PURE__ */ d("div", {
|
|
55
56
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
56
|
-
children: /* @__PURE__ */
|
|
57
|
+
children: /* @__PURE__ */ f("div", {
|
|
57
58
|
className: "text-center space-y-4",
|
|
58
59
|
children: [
|
|
59
|
-
/* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ d("div", {
|
|
60
61
|
className: "size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center",
|
|
61
|
-
children: /* @__PURE__ */
|
|
62
|
+
children: /* @__PURE__ */ d("svg", {
|
|
62
63
|
className: "size-6 text-destructive",
|
|
63
64
|
fill: "none",
|
|
64
65
|
viewBox: "0 0 24 24",
|
|
65
66
|
stroke: "currentColor",
|
|
66
|
-
children: /* @__PURE__ */
|
|
67
|
+
children: /* @__PURE__ */ d("path", {
|
|
67
68
|
strokeLinecap: "round",
|
|
68
69
|
strokeLinejoin: "round",
|
|
69
70
|
strokeWidth: 2,
|
|
@@ -71,15 +72,15 @@ var m = () => {
|
|
|
71
72
|
})
|
|
72
73
|
})
|
|
73
74
|
}),
|
|
74
|
-
/* @__PURE__ */
|
|
75
|
+
/* @__PURE__ */ d("h2", {
|
|
75
76
|
className: "text-lg font-semibold text-foreground",
|
|
76
77
|
children: h("billing.plans.planNotFound", "Plan not found")
|
|
77
78
|
}),
|
|
78
|
-
/* @__PURE__ */
|
|
79
|
+
/* @__PURE__ */ d("p", {
|
|
79
80
|
className: "text-sm text-muted-foreground",
|
|
80
81
|
children: x?.message || h("billing.plans.planNotFoundMessage", "The plan you are looking for does not exist.")
|
|
81
82
|
}),
|
|
82
|
-
/* @__PURE__ */
|
|
83
|
+
/* @__PURE__ */ d("button", {
|
|
83
84
|
type: "button",
|
|
84
85
|
onClick: () => _("/plans"),
|
|
85
86
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
@@ -87,54 +88,54 @@ var m = () => {
|
|
|
87
88
|
})
|
|
88
89
|
]
|
|
89
90
|
})
|
|
90
|
-
}) : /* @__PURE__ */
|
|
91
|
+
}) : /* @__PURE__ */ f("div", {
|
|
91
92
|
className: "space-y-6 p-6",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
93
|
+
children: [/* @__PURE__ */ f("div", {
|
|
93
94
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
94
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ f("div", {
|
|
95
96
|
className: "flex items-start gap-4",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
97
|
+
children: [/* @__PURE__ */ d("button", {
|
|
97
98
|
type: "button",
|
|
98
99
|
onClick: () => _("/plans"),
|
|
99
100
|
className: "p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors",
|
|
100
|
-
children: /* @__PURE__ */
|
|
101
|
+
children: /* @__PURE__ */ d("svg", {
|
|
101
102
|
className: "size-5",
|
|
102
103
|
fill: "none",
|
|
103
104
|
viewBox: "0 0 24 24",
|
|
104
105
|
stroke: "currentColor",
|
|
105
|
-
children: /* @__PURE__ */
|
|
106
|
+
children: /* @__PURE__ */ d("path", {
|
|
106
107
|
strokeLinecap: "round",
|
|
107
108
|
strokeLinejoin: "round",
|
|
108
109
|
strokeWidth: 2,
|
|
109
110
|
d: "M15 19l-7-7 7-7"
|
|
110
111
|
})
|
|
111
112
|
})
|
|
112
|
-
}), /* @__PURE__ */
|
|
113
|
+
}), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ f("div", {
|
|
113
114
|
className: "flex items-center gap-3",
|
|
114
|
-
children: [/* @__PURE__ */
|
|
115
|
+
children: [/* @__PURE__ */ d("h1", {
|
|
115
116
|
className: "text-2xl font-bold text-foreground",
|
|
116
117
|
children: y.name
|
|
117
|
-
}), /* @__PURE__ */
|
|
118
|
+
}), /* @__PURE__ */ d("span", {
|
|
118
119
|
className: `px-2 py-1 text-xs font-medium rounded ${y.isActive ? "bg-status-success-bg-subtle text-status-success-text" : "bg-muted text-muted-foreground"}`,
|
|
119
120
|
children: y.isActive ? "Active" : "Inactive"
|
|
120
121
|
})]
|
|
121
|
-
}), /* @__PURE__ */
|
|
122
|
+
}), /* @__PURE__ */ f("p", {
|
|
122
123
|
className: "text-sm text-muted-foreground mt-1",
|
|
123
124
|
children: [n(y.duration), " billing plan"]
|
|
124
125
|
})] })]
|
|
125
|
-
}), /* @__PURE__ */
|
|
126
|
+
}), /* @__PURE__ */ f("div", {
|
|
126
127
|
className: "flex items-center gap-2",
|
|
127
|
-
children: [v.canCreateSubscription && y.isActive && /* @__PURE__ */
|
|
128
|
+
children: [v.canCreateSubscription && y.isActive && /* @__PURE__ */ d("button", {
|
|
128
129
|
type: "button",
|
|
129
130
|
onClick: () => _(`/checkout/${y.id}`),
|
|
130
131
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
131
132
|
children: "Subscribe to Plan"
|
|
132
|
-
}), v.canManagePlans && /* @__PURE__ */
|
|
133
|
+
}), v.canManagePlans && /* @__PURE__ */ f(u, { children: [/* @__PURE__ */ d("button", {
|
|
133
134
|
type: "button",
|
|
134
135
|
onClick: () => _(`/plans/${y.id}/edit`),
|
|
135
136
|
className: "px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
|
|
136
137
|
children: "Edit Plan"
|
|
137
|
-
}), /* @__PURE__ */
|
|
138
|
+
}), /* @__PURE__ */ d("button", {
|
|
138
139
|
type: "button",
|
|
139
140
|
onClick: async () => {
|
|
140
141
|
if (v.canManagePlans) try {
|
|
@@ -148,30 +149,30 @@ var m = () => {
|
|
|
148
149
|
children: w ? h("billing.plans.processing", "Processing...") : y.isActive ? h("billing.plans.deactivate", "Deactivate") : h("billing.plans.activate", "Activate")
|
|
149
150
|
})] })]
|
|
150
151
|
})]
|
|
151
|
-
}), /* @__PURE__ */
|
|
152
|
+
}), /* @__PURE__ */ f("div", {
|
|
152
153
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
154
|
+
children: [/* @__PURE__ */ f("div", {
|
|
154
155
|
className: "lg:col-span-2 space-y-6",
|
|
155
|
-
children: [/* @__PURE__ */
|
|
156
|
+
children: [/* @__PURE__ */ f("div", {
|
|
156
157
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
157
|
-
children: [/* @__PURE__ */
|
|
158
|
+
children: [/* @__PURE__ */ d("div", {
|
|
158
159
|
className: "p-6 border-b border-border bg-muted/30",
|
|
159
|
-
children: /* @__PURE__ */
|
|
160
|
+
children: /* @__PURE__ */ d("h2", {
|
|
160
161
|
className: "text-lg font-semibold text-foreground",
|
|
161
162
|
children: h("billing.plans.pricing", "Pricing")
|
|
162
163
|
})
|
|
163
|
-
}), /* @__PURE__ */
|
|
164
|
+
}), /* @__PURE__ */ f("div", {
|
|
164
165
|
className: "p-6",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
166
|
+
children: [/* @__PURE__ */ f("div", {
|
|
166
167
|
className: "flex items-baseline gap-1",
|
|
167
|
-
children: [/* @__PURE__ */
|
|
168
|
+
children: [/* @__PURE__ */ d("span", {
|
|
168
169
|
className: "text-4xl font-bold text-foreground",
|
|
169
170
|
children: e(y.price, y.currency)
|
|
170
|
-
}), /* @__PURE__ */
|
|
171
|
+
}), /* @__PURE__ */ f("span", {
|
|
171
172
|
className: "text-lg text-muted-foreground",
|
|
172
173
|
children: ["/ ", t(y.duration)]
|
|
173
174
|
})]
|
|
174
|
-
}), /* @__PURE__ */
|
|
175
|
+
}), /* @__PURE__ */ f("p", {
|
|
175
176
|
className: "text-sm text-muted-foreground mt-2",
|
|
176
177
|
children: [
|
|
177
178
|
h("billing.plans.billed", "Billed"),
|
|
@@ -180,11 +181,11 @@ var m = () => {
|
|
|
180
181
|
]
|
|
181
182
|
})]
|
|
182
183
|
})]
|
|
183
|
-
}), /* @__PURE__ */
|
|
184
|
+
}), /* @__PURE__ */ f("div", {
|
|
184
185
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
185
|
-
children: [/* @__PURE__ */
|
|
186
|
+
children: [/* @__PURE__ */ d("div", {
|
|
186
187
|
className: "p-6 border-b border-border bg-muted/30",
|
|
187
|
-
children: /* @__PURE__ */
|
|
188
|
+
children: /* @__PURE__ */ f("h2", {
|
|
188
189
|
className: "text-lg font-semibold text-foreground",
|
|
189
190
|
children: [
|
|
190
191
|
h("billing.plans.featuresAndQuotas", "Features & Quotas"),
|
|
@@ -193,46 +194,46 @@ var m = () => {
|
|
|
193
194
|
")"
|
|
194
195
|
]
|
|
195
196
|
})
|
|
196
|
-
}), /* @__PURE__ */
|
|
197
|
+
}), /* @__PURE__ */ d("div", {
|
|
197
198
|
className: "p-6",
|
|
198
|
-
children: T.length === 0 ? /* @__PURE__ */
|
|
199
|
+
children: T.length === 0 ? /* @__PURE__ */ d("p", {
|
|
199
200
|
className: "text-sm text-muted-foreground",
|
|
200
201
|
children: h("billing.plans.noFeatures", "No features configured for this plan.")
|
|
201
|
-
}) : /* @__PURE__ */
|
|
202
|
+
}) : /* @__PURE__ */ d("ul", {
|
|
202
203
|
className: "space-y-3",
|
|
203
|
-
children: T.map((e) => /* @__PURE__ */
|
|
204
|
+
children: T.map((e) => /* @__PURE__ */ f("li", {
|
|
204
205
|
className: "flex items-start gap-3 p-3 rounded-lg bg-muted/30",
|
|
205
|
-
children: [/* @__PURE__ */
|
|
206
|
+
children: [/* @__PURE__ */ d("div", {
|
|
206
207
|
className: "size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0",
|
|
207
|
-
children: /* @__PURE__ */
|
|
208
|
+
children: /* @__PURE__ */ d("svg", {
|
|
208
209
|
className: "size-4 text-status-success-text",
|
|
209
210
|
fill: "none",
|
|
210
211
|
viewBox: "0 0 24 24",
|
|
211
212
|
stroke: "currentColor",
|
|
212
|
-
children: /* @__PURE__ */
|
|
213
|
+
children: /* @__PURE__ */ d("path", {
|
|
213
214
|
strokeLinecap: "round",
|
|
214
215
|
strokeLinejoin: "round",
|
|
215
216
|
strokeWidth: 2,
|
|
216
217
|
d: "M5 13l4 4L19 7"
|
|
217
218
|
})
|
|
218
219
|
})
|
|
219
|
-
}), /* @__PURE__ */
|
|
220
|
+
}), /* @__PURE__ */ f("div", {
|
|
220
221
|
className: "flex-1",
|
|
221
|
-
children: [/* @__PURE__ */
|
|
222
|
+
children: [/* @__PURE__ */ f("div", {
|
|
222
223
|
className: "flex items-center justify-between",
|
|
223
|
-
children: [/* @__PURE__ */
|
|
224
|
+
children: [/* @__PURE__ */ d("p", {
|
|
224
225
|
className: "font-medium text-foreground",
|
|
225
226
|
children: e.name
|
|
226
|
-
}), /* @__PURE__ */
|
|
227
|
+
}), /* @__PURE__ */ d("span", {
|
|
227
228
|
className: "font-semibold text-foreground",
|
|
228
229
|
children: e.totalValue.toLocaleString()
|
|
229
230
|
})]
|
|
230
|
-
}), /* @__PURE__ */
|
|
231
|
+
}), /* @__PURE__ */ f("div", {
|
|
231
232
|
className: "flex items-center gap-2 mt-1",
|
|
232
|
-
children: [/* @__PURE__ */
|
|
233
|
+
children: [/* @__PURE__ */ f("span", {
|
|
233
234
|
className: "text-sm text-muted-foreground capitalize",
|
|
234
235
|
children: ["Type: ", e.limitType]
|
|
235
|
-
}), e.quotaType && /* @__PURE__ */
|
|
236
|
+
}), e.quotaType && /* @__PURE__ */ d("span", {
|
|
236
237
|
className: "px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded",
|
|
237
238
|
children: e.quotaType
|
|
238
239
|
})]
|
|
@@ -242,40 +243,40 @@ var m = () => {
|
|
|
242
243
|
})
|
|
243
244
|
})]
|
|
244
245
|
})]
|
|
245
|
-
}), /* @__PURE__ */
|
|
246
|
+
}), /* @__PURE__ */ f("div", {
|
|
246
247
|
className: "space-y-6",
|
|
247
|
-
children: [/* @__PURE__ */
|
|
248
|
+
children: [/* @__PURE__ */ f("div", {
|
|
248
249
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
249
|
-
children: [/* @__PURE__ */
|
|
250
|
+
children: [/* @__PURE__ */ d("div", {
|
|
250
251
|
className: "p-6 border-b border-border bg-muted/30",
|
|
251
|
-
children: /* @__PURE__ */
|
|
252
|
+
children: /* @__PURE__ */ d("h2", {
|
|
252
253
|
className: "text-lg font-semibold text-foreground",
|
|
253
254
|
children: h("billing.plans.planDetails", "Plan Details")
|
|
254
255
|
})
|
|
255
|
-
}), /* @__PURE__ */
|
|
256
|
+
}), /* @__PURE__ */ f("div", {
|
|
256
257
|
className: "p-6 space-y-4",
|
|
257
258
|
children: [
|
|
258
|
-
/* @__PURE__ */
|
|
259
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
259
260
|
className: "text-sm text-muted-foreground",
|
|
260
261
|
children: h("billing.plans.planName", "Plan Name")
|
|
261
|
-
}), /* @__PURE__ */
|
|
262
|
+
}), /* @__PURE__ */ d("p", {
|
|
262
263
|
className: "text-sm font-medium text-foreground mt-0.5",
|
|
263
264
|
children: y.name
|
|
264
265
|
})] }),
|
|
265
|
-
/* @__PURE__ */
|
|
266
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
266
267
|
className: "text-sm text-muted-foreground",
|
|
267
268
|
children: h("billing.plans.status", "Status")
|
|
268
|
-
}), /* @__PURE__ */
|
|
269
|
+
}), /* @__PURE__ */ d("p", {
|
|
269
270
|
className: "text-sm text-foreground mt-0.5",
|
|
270
|
-
children: /* @__PURE__ */
|
|
271
|
+
children: /* @__PURE__ */ d("span", {
|
|
271
272
|
className: `inline-flex items-center px-2 py-0.5 text-xs font-medium rounded ${y.isActive ? "bg-status-success-bg-subtle text-status-success-text" : "bg-muted text-muted-foreground"}`,
|
|
272
273
|
children: y.isActive ? "Active" : "Inactive"
|
|
273
274
|
})
|
|
274
275
|
})] }),
|
|
275
|
-
/* @__PURE__ */
|
|
276
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
276
277
|
className: "text-sm text-muted-foreground",
|
|
277
278
|
children: h("billing.plans.price", "Price")
|
|
278
|
-
}), /* @__PURE__ */
|
|
279
|
+
}), /* @__PURE__ */ f("p", {
|
|
279
280
|
className: "text-sm font-medium text-foreground mt-0.5",
|
|
280
281
|
children: [
|
|
281
282
|
e(y.price, y.currency),
|
|
@@ -283,38 +284,38 @@ var m = () => {
|
|
|
283
284
|
t(y.duration)
|
|
284
285
|
]
|
|
285
286
|
})] }),
|
|
286
|
-
/* @__PURE__ */
|
|
287
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
287
288
|
className: "text-sm text-muted-foreground",
|
|
288
289
|
children: h("billing.plans.currency", "Currency")
|
|
289
|
-
}), /* @__PURE__ */
|
|
290
|
+
}), /* @__PURE__ */ d("p", {
|
|
290
291
|
className: "text-sm text-foreground mt-0.5",
|
|
291
292
|
children: y.currency
|
|
292
293
|
})] }),
|
|
293
|
-
/* @__PURE__ */
|
|
294
|
+
/* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
294
295
|
className: "text-sm text-muted-foreground",
|
|
295
296
|
children: h("billing.plans.billingPeriod", "Billing Period")
|
|
296
|
-
}), /* @__PURE__ */
|
|
297
|
+
}), /* @__PURE__ */ d("p", {
|
|
297
298
|
className: "text-sm text-foreground mt-0.5",
|
|
298
299
|
children: n(y.duration)
|
|
299
300
|
})] })
|
|
300
301
|
]
|
|
301
302
|
})]
|
|
302
|
-
}), /* @__PURE__ */
|
|
303
|
+
}), /* @__PURE__ */ f("div", {
|
|
303
304
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
304
|
-
children: [/* @__PURE__ */
|
|
305
|
+
children: [/* @__PURE__ */ d("div", {
|
|
305
306
|
className: "p-6 border-b border-border bg-muted/30",
|
|
306
|
-
children: /* @__PURE__ */
|
|
307
|
+
children: /* @__PURE__ */ d("h2", {
|
|
307
308
|
className: "text-lg font-semibold text-foreground",
|
|
308
309
|
children: h("billing.plans.statistics", "Statistics")
|
|
309
310
|
})
|
|
310
|
-
}), /* @__PURE__ */
|
|
311
|
+
}), /* @__PURE__ */ d("div", {
|
|
311
312
|
className: "p-6 space-y-4",
|
|
312
|
-
children: /* @__PURE__ */
|
|
313
|
+
children: /* @__PURE__ */ f("div", {
|
|
313
314
|
className: "flex items-center justify-between",
|
|
314
|
-
children: [/* @__PURE__ */
|
|
315
|
+
children: [/* @__PURE__ */ d("p", {
|
|
315
316
|
className: "text-sm text-muted-foreground",
|
|
316
317
|
children: h("billing.plans.uniqueQuotas", "Unique Quotas")
|
|
317
|
-
}), /* @__PURE__ */
|
|
318
|
+
}), /* @__PURE__ */ d("p", {
|
|
318
319
|
className: "font-semibold text-foreground",
|
|
319
320
|
children: T.length
|
|
320
321
|
})]
|
|
@@ -323,19 +324,19 @@ var m = () => {
|
|
|
323
324
|
})]
|
|
324
325
|
})]
|
|
325
326
|
})]
|
|
326
|
-
}) : /* @__PURE__ */
|
|
327
|
+
}) : /* @__PURE__ */ d("div", {
|
|
327
328
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
328
|
-
children: /* @__PURE__ */
|
|
329
|
+
children: /* @__PURE__ */ f("div", {
|
|
329
330
|
className: "text-center space-y-2",
|
|
330
331
|
children: [
|
|
331
|
-
/* @__PURE__ */
|
|
332
|
+
/* @__PURE__ */ d("div", {
|
|
332
333
|
className: "size-12 mx-auto rounded-full bg-muted flex items-center justify-center",
|
|
333
|
-
children: /* @__PURE__ */
|
|
334
|
+
children: /* @__PURE__ */ d("svg", {
|
|
334
335
|
className: "size-6 text-muted-foreground",
|
|
335
336
|
fill: "none",
|
|
336
337
|
viewBox: "0 0 24 24",
|
|
337
338
|
stroke: "currentColor",
|
|
338
|
-
children: /* @__PURE__ */
|
|
339
|
+
children: /* @__PURE__ */ d("path", {
|
|
339
340
|
strokeLinecap: "round",
|
|
340
341
|
strokeLinejoin: "round",
|
|
341
342
|
strokeWidth: 2,
|
|
@@ -343,11 +344,11 @@ var m = () => {
|
|
|
343
344
|
})
|
|
344
345
|
})
|
|
345
346
|
}),
|
|
346
|
-
/* @__PURE__ */
|
|
347
|
+
/* @__PURE__ */ d("h2", {
|
|
347
348
|
className: "text-lg font-semibold text-foreground",
|
|
348
349
|
children: h("billing.accessDenied.title", "Access Denied")
|
|
349
350
|
}),
|
|
350
|
-
/* @__PURE__ */
|
|
351
|
+
/* @__PURE__ */ d("p", {
|
|
351
352
|
className: "text-sm text-muted-foreground max-w-sm",
|
|
352
353
|
children: h("billing.plans.noViewPlanPermission", "You don't have permission to view this plan.")
|
|
353
354
|
})
|
|
@@ -356,6 +357,6 @@ var m = () => {
|
|
|
356
357
|
});
|
|
357
358
|
};
|
|
358
359
|
//#endregion
|
|
359
|
-
export {
|
|
360
|
+
export { h as PlanDetailPage };
|
|
360
361
|
|
|
361
362
|
//# 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-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n} from '../../../shared/utils/format';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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}\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 = limits?.value ?? 0;\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 });\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-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\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-foreground\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground 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-muted animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-muted 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 rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-3/4 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n <div className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-10 w-full bg-muted 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-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\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-foreground\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\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-primary text-primary-foreground rounded-md hover:bg-primary/90 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-muted-foreground hover:text-foreground hover:bg-muted 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 font-bold text-foreground\">{plan.name}</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-muted text-muted-foreground'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\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-primary text-primary-foreground rounded-md hover:bg-primary/90 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 text-foreground rounded-md hover:bg-muted 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 rounded-md transition-colors ${\n plan.isActive\n ? 'text-destructive hover:bg-destructive/10'\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 */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.plans.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-4xl font-bold text-foreground\">\n {formatCurrency(plan.price, plan.currency)}\n </span>\n <span className=\"text-lg text-muted-foreground\">\n / {formatPlanDuration(plan.duration)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground 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 </div>\n </div>\n\n {/* Features Card */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\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-muted-foreground\">\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-muted/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-foreground\">{quota.name}</p>\n <span className=\"font-semibold text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className=\"text-sm text-muted-foreground capitalize\">\n Type: {quota.limitType}\n </span>\n {quota.quotaType && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground 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 rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\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-muted-foreground\">\n {tr('billing.plans.planName', 'Plan Name')}\n </p>\n <p className=\"text-sm font-medium text-foreground mt-0.5\">{plan.name}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.status', 'Status')}\n </p>\n <p className=\"text-sm text-foreground 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-muted text-muted-foreground'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.price', 'Price')}\n </p>\n <p className=\"text-sm font-medium text-foreground mt-0.5\">\n {formatCurrency(plan.price, plan.currency)} / {formatPlanDuration(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.currency', 'Currency')}\n </p>\n <p className=\"text-sm text-foreground mt-0.5\">{plan.currency}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.billingPeriod', 'Billing Period')}\n </p>\n <p className=\"text-sm text-foreground 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 rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\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-muted-foreground\">\n {tr('billing.plans.uniqueQuotas', 'Unique Quotas')}\n </p>\n <p className=\"font-semibold text-foreground\">{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-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n} from '../../../shared/utils/format';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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}\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 });\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-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\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-foreground\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground 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-muted animate-pulse rounded\" />\n <div className=\"h-8 w-48 bg-muted 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 rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n <div className=\"h-4 w-3/4 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n <div className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-10 w-full bg-muted 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-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\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-foreground\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\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-primary text-primary-foreground rounded-md hover:bg-primary/90 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-muted-foreground hover:text-foreground hover:bg-muted 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 font-bold text-foreground\">{plan.name}</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-muted text-muted-foreground'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\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-primary text-primary-foreground rounded-md hover:bg-primary/90 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 text-foreground rounded-md hover:bg-muted 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 rounded-md transition-colors ${\n plan.isActive\n ? 'text-destructive hover:bg-destructive/10'\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 */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.plans.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-1\">\n <span className=\"text-4xl font-bold text-foreground\">\n {formatCurrency(plan.price, plan.currency)}\n </span>\n <span className=\"text-lg text-muted-foreground\">\n / {formatPlanDuration(plan.duration)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground 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 </div>\n </div>\n\n {/* Features Card */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\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-muted-foreground\">\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-muted/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-foreground\">{quota.name}</p>\n <span className=\"font-semibold text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </div>\n <div className=\"flex items-center gap-2 mt-1\">\n <span className=\"text-sm text-muted-foreground capitalize\">\n Type: {quota.limitType}\n </span>\n {quota.quotaType && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground 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 rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\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-muted-foreground\">\n {tr('billing.plans.planName', 'Plan Name')}\n </p>\n <p className=\"text-sm font-medium text-foreground mt-0.5\">{plan.name}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.status', 'Status')}\n </p>\n <p className=\"text-sm text-foreground 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-muted text-muted-foreground'\n }`}\n >\n {plan.isActive ? 'Active' : 'Inactive'}\n </span>\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.price', 'Price')}\n </p>\n <p className=\"text-sm font-medium text-foreground mt-0.5\">\n {formatCurrency(plan.price, plan.currency)} / {formatPlanDuration(plan.duration)}\n </p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.currency', 'Currency')}\n </p>\n <p className=\"text-sm text-foreground mt-0.5\">{plan.currency}</p>\n </div>\n <div>\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.billingPeriod', 'Billing Period')}\n </p>\n <p className=\"text-sm text-foreground 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 rounded-lg bg-card overflow-hidden\">\n <div className=\"p-6 border-b border-border bg-muted/30\">\n <h2 className=\"text-lg font-semibold text-foreground\">\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-muted-foreground\">\n {tr('billing.plans.uniqueQuotas', 'Unique Quotas')}\n </p>\n <p className=\"font-semibold text-foreground\">{combinedQuotas.length}</p>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|