@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
|
@@ -2,120 +2,121 @@ import { ServerError as e } from "../../../shared/components/ServerError.js";
|
|
|
2
2
|
import "../../../shared/components/index.js";
|
|
3
3
|
import { formatCurrency as t, formatPlanDuration as n, formatPlanDurationLabel as r } from "../../../shared/utils/format.js";
|
|
4
4
|
import { isServerError as i } from "../../../shared/utils/error.js";
|
|
5
|
+
import { getPlanFeatureQuantity as a } from "../../../shared/utils/planFeatureQuantity.js";
|
|
5
6
|
import "../../../shared/utils/index.js";
|
|
6
|
-
import { useBillingNavigate as
|
|
7
|
-
import { usePlan as
|
|
7
|
+
import { useBillingNavigate as o } from "../../../hooks/useBillingNavigate.js";
|
|
8
|
+
import { usePlan as s } from "../hooks/usePlans.js";
|
|
8
9
|
import "../hooks/index.js";
|
|
9
|
-
import { useMemo as
|
|
10
|
-
import { useParams as
|
|
11
|
-
import { jsx as
|
|
12
|
-
import { useI18n as
|
|
10
|
+
import { useMemo as c } from "react";
|
|
11
|
+
import { useParams as l } from "react-router-dom";
|
|
12
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
13
|
+
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
13
14
|
//#region src/billing/modules/plans/pages/PlanBrowseDetailPage.tsx
|
|
14
|
-
function
|
|
15
|
+
function p(e) {
|
|
15
16
|
let t = /* @__PURE__ */ new Map();
|
|
16
17
|
for (let n of e) {
|
|
17
18
|
if (!n.quota) continue;
|
|
18
|
-
let e = n.quota.id, r = n.quota.limits
|
|
19
|
-
o ? o.totalValue +=
|
|
19
|
+
let e = n.quota.id, r = n.quota.limits?.type ?? "unknown", i = a(n), o = t.get(e);
|
|
20
|
+
o ? o.totalValue += i : t.set(e, {
|
|
20
21
|
quotaId: e,
|
|
21
22
|
name: n.quota.name ?? "Unknown Quota",
|
|
22
23
|
quotaType: n.quota.quotaType ?? "",
|
|
23
|
-
limitType:
|
|
24
|
-
totalValue:
|
|
24
|
+
limitType: r,
|
|
25
|
+
totalValue: i
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
return Array.from(t.values());
|
|
28
29
|
}
|
|
29
|
-
var
|
|
30
|
-
let { t:
|
|
31
|
-
let n =
|
|
30
|
+
var m = () => {
|
|
31
|
+
let { t: a } = f(), m = (e, t) => {
|
|
32
|
+
let n = a(e);
|
|
32
33
|
return n === e ? t : n;
|
|
33
|
-
}, { planId: h } =
|
|
34
|
-
return v ? /* @__PURE__ */
|
|
34
|
+
}, { planId: h } = l(), g = o(), { plan: _, isLoading: v, error: y, refetch: b } = s(h), x = c(() => p(_?.features || []), [_?.features]);
|
|
35
|
+
return v ? /* @__PURE__ */ d("div", {
|
|
35
36
|
className: "space-y-6 p-6",
|
|
36
|
-
children: [/* @__PURE__ */
|
|
37
|
+
children: [/* @__PURE__ */ d("div", {
|
|
37
38
|
className: "flex items-center gap-4",
|
|
38
|
-
children: [/* @__PURE__ */
|
|
39
|
-
}), /* @__PURE__ */
|
|
39
|
+
children: [/* @__PURE__ */ u("div", { className: "size-10 bg-muted animate-pulse rounded" }), /* @__PURE__ */ u("div", { className: "h-8 w-48 bg-muted animate-pulse rounded" })]
|
|
40
|
+
}), /* @__PURE__ */ d("div", {
|
|
40
41
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
41
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ u("div", {
|
|
42
43
|
className: "lg:col-span-2 space-y-6",
|
|
43
|
-
children: /* @__PURE__ */
|
|
44
|
+
children: /* @__PURE__ */ d("div", {
|
|
44
45
|
className: "border border-border rounded-lg p-6 space-y-4",
|
|
45
|
-
children: [/* @__PURE__ */
|
|
46
|
+
children: [/* @__PURE__ */ u("div", { className: "h-6 w-32 bg-muted animate-pulse rounded" }), /* @__PURE__ */ u("div", { className: "h-4 w-full bg-muted animate-pulse rounded" })]
|
|
46
47
|
})
|
|
47
|
-
}), /* @__PURE__ */
|
|
48
|
+
}), /* @__PURE__ */ d("div", {
|
|
48
49
|
className: "border border-border rounded-lg p-6 space-y-4",
|
|
49
|
-
children: [/* @__PURE__ */
|
|
50
|
+
children: [/* @__PURE__ */ u("div", { className: "h-6 w-24 bg-muted animate-pulse rounded" }), /* @__PURE__ */ u("div", { className: "h-10 w-full bg-muted animate-pulse rounded" })]
|
|
50
51
|
})]
|
|
51
52
|
})]
|
|
52
|
-
}) : y && i(y) ? /* @__PURE__ */
|
|
53
|
+
}) : y && i(y) ? /* @__PURE__ */ u("div", {
|
|
53
54
|
className: "p-6",
|
|
54
|
-
children: /* @__PURE__ */
|
|
55
|
+
children: /* @__PURE__ */ u(e, {
|
|
55
56
|
title: m("billing.plans.serverUnavailable", "Server Unavailable"),
|
|
56
57
|
message: m("billing.plans.unableToLoadPlanDetails", "Unable to load plan details."),
|
|
57
58
|
onRetry: () => b(),
|
|
58
59
|
showRetry: !0
|
|
59
60
|
})
|
|
60
|
-
}) : _ ? /* @__PURE__ */
|
|
61
|
+
}) : _ ? /* @__PURE__ */ d("div", {
|
|
61
62
|
className: "space-y-6 p-6",
|
|
62
|
-
children: [/* @__PURE__ */
|
|
63
|
+
children: [/* @__PURE__ */ d("div", {
|
|
63
64
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
64
|
-
children: [/* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ d("div", {
|
|
65
66
|
className: "flex items-start gap-4",
|
|
66
|
-
children: [/* @__PURE__ */
|
|
67
|
+
children: [/* @__PURE__ */ u("button", {
|
|
67
68
|
type: "button",
|
|
68
69
|
onClick: () => g("/plans"),
|
|
69
70
|
className: "p-2 -ml-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors",
|
|
70
|
-
children: /* @__PURE__ */
|
|
71
|
+
children: /* @__PURE__ */ u("svg", {
|
|
71
72
|
className: "size-5",
|
|
72
73
|
fill: "none",
|
|
73
74
|
viewBox: "0 0 24 24",
|
|
74
75
|
stroke: "currentColor",
|
|
75
|
-
children: /* @__PURE__ */
|
|
76
|
+
children: /* @__PURE__ */ u("path", {
|
|
76
77
|
strokeLinecap: "round",
|
|
77
78
|
strokeLinejoin: "round",
|
|
78
79
|
strokeWidth: 2,
|
|
79
80
|
d: "M15 19l-7-7 7-7"
|
|
80
81
|
})
|
|
81
82
|
})
|
|
82
|
-
}), /* @__PURE__ */
|
|
83
|
+
}), /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h1", {
|
|
83
84
|
className: "text-2xl font-bold text-foreground",
|
|
84
85
|
children: _.name
|
|
85
|
-
}), /* @__PURE__ */
|
|
86
|
+
}), /* @__PURE__ */ d("p", {
|
|
86
87
|
className: "text-sm text-muted-foreground mt-1",
|
|
87
88
|
children: [r(_.duration), " billing plan"]
|
|
88
89
|
})] })]
|
|
89
|
-
}), /* @__PURE__ */
|
|
90
|
+
}), /* @__PURE__ */ u("button", {
|
|
90
91
|
type: "button",
|
|
91
92
|
onClick: () => g(`/checkout/${_.id}`),
|
|
92
93
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
93
94
|
children: "Subscribe to Plan"
|
|
94
95
|
})]
|
|
95
|
-
}), /* @__PURE__ */
|
|
96
|
+
}), /* @__PURE__ */ d("div", {
|
|
96
97
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
97
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ d("div", {
|
|
98
99
|
className: "lg:col-span-2 space-y-6",
|
|
99
|
-
children: [/* @__PURE__ */
|
|
100
|
+
children: [/* @__PURE__ */ d("div", {
|
|
100
101
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
101
|
-
children: [/* @__PURE__ */
|
|
102
|
+
children: [/* @__PURE__ */ u("div", {
|
|
102
103
|
className: "p-6 border-b border-border bg-muted/30",
|
|
103
|
-
children: /* @__PURE__ */
|
|
104
|
+
children: /* @__PURE__ */ u("h2", {
|
|
104
105
|
className: "text-lg font-semibold text-foreground",
|
|
105
106
|
children: m("billing.plans.pricing", "Pricing")
|
|
106
107
|
})
|
|
107
|
-
}), /* @__PURE__ */
|
|
108
|
+
}), /* @__PURE__ */ d("div", {
|
|
108
109
|
className: "p-6",
|
|
109
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ d("div", {
|
|
110
111
|
className: "flex items-baseline gap-1",
|
|
111
|
-
children: [/* @__PURE__ */
|
|
112
|
+
children: [/* @__PURE__ */ u("span", {
|
|
112
113
|
className: "text-4xl font-bold text-foreground",
|
|
113
114
|
children: t(_.price, _.currency)
|
|
114
|
-
}), /* @__PURE__ */
|
|
115
|
+
}), /* @__PURE__ */ d("span", {
|
|
115
116
|
className: "text-lg text-muted-foreground",
|
|
116
117
|
children: ["/ ", n(_.duration)]
|
|
117
118
|
})]
|
|
118
|
-
}), /* @__PURE__ */
|
|
119
|
+
}), /* @__PURE__ */ d("p", {
|
|
119
120
|
className: "text-sm text-muted-foreground mt-2",
|
|
120
121
|
children: [
|
|
121
122
|
m("billing.plans.billed", "Billed"),
|
|
@@ -124,11 +125,11 @@ var p = () => {
|
|
|
124
125
|
]
|
|
125
126
|
})]
|
|
126
127
|
})]
|
|
127
|
-
}), /* @__PURE__ */
|
|
128
|
+
}), /* @__PURE__ */ d("div", {
|
|
128
129
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
129
|
-
children: [/* @__PURE__ */
|
|
130
|
+
children: [/* @__PURE__ */ u("div", {
|
|
130
131
|
className: "p-6 border-b border-border bg-muted/30",
|
|
131
|
-
children: /* @__PURE__ */
|
|
132
|
+
children: /* @__PURE__ */ d("h2", {
|
|
132
133
|
className: "text-lg font-semibold text-foreground",
|
|
133
134
|
children: [
|
|
134
135
|
m("billing.plans.featuresAndQuotas", "Features & Quotas"),
|
|
@@ -137,46 +138,46 @@ var p = () => {
|
|
|
137
138
|
")"
|
|
138
139
|
]
|
|
139
140
|
})
|
|
140
|
-
}), /* @__PURE__ */
|
|
141
|
+
}), /* @__PURE__ */ u("div", {
|
|
141
142
|
className: "p-6",
|
|
142
|
-
children: x.length === 0 ? /* @__PURE__ */
|
|
143
|
+
children: x.length === 0 ? /* @__PURE__ */ u("p", {
|
|
143
144
|
className: "text-sm text-muted-foreground",
|
|
144
145
|
children: m("billing.plans.noFeatures", "No features configured for this plan.")
|
|
145
|
-
}) : /* @__PURE__ */
|
|
146
|
+
}) : /* @__PURE__ */ u("ul", {
|
|
146
147
|
className: "space-y-3",
|
|
147
|
-
children: x.map((e) => /* @__PURE__ */
|
|
148
|
+
children: x.map((e) => /* @__PURE__ */ d("li", {
|
|
148
149
|
className: "flex items-start gap-3 p-3 rounded-lg bg-muted/30",
|
|
149
|
-
children: [/* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ u("div", {
|
|
150
151
|
className: "size-8 rounded-full bg-status-success-bg-subtle flex items-center justify-center shrink-0",
|
|
151
|
-
children: /* @__PURE__ */
|
|
152
|
+
children: /* @__PURE__ */ u("svg", {
|
|
152
153
|
className: "size-4 text-status-success-text",
|
|
153
154
|
fill: "none",
|
|
154
155
|
viewBox: "0 0 24 24",
|
|
155
156
|
stroke: "currentColor",
|
|
156
|
-
children: /* @__PURE__ */
|
|
157
|
+
children: /* @__PURE__ */ u("path", {
|
|
157
158
|
strokeLinecap: "round",
|
|
158
159
|
strokeLinejoin: "round",
|
|
159
160
|
strokeWidth: 2,
|
|
160
161
|
d: "M5 13l4 4L19 7"
|
|
161
162
|
})
|
|
162
163
|
})
|
|
163
|
-
}), /* @__PURE__ */
|
|
164
|
+
}), /* @__PURE__ */ d("div", {
|
|
164
165
|
className: "flex-1",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
166
|
+
children: [/* @__PURE__ */ d("div", {
|
|
166
167
|
className: "flex items-center justify-between",
|
|
167
|
-
children: [/* @__PURE__ */
|
|
168
|
+
children: [/* @__PURE__ */ u("p", {
|
|
168
169
|
className: "font-medium text-foreground",
|
|
169
170
|
children: e.name
|
|
170
|
-
}), /* @__PURE__ */
|
|
171
|
+
}), /* @__PURE__ */ u("span", {
|
|
171
172
|
className: "font-semibold text-foreground",
|
|
172
173
|
children: e.totalValue.toLocaleString()
|
|
173
174
|
})]
|
|
174
|
-
}), /* @__PURE__ */
|
|
175
|
+
}), /* @__PURE__ */ d("div", {
|
|
175
176
|
className: "flex items-center gap-2 mt-1",
|
|
176
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ d("span", {
|
|
177
178
|
className: "text-sm text-muted-foreground capitalize",
|
|
178
179
|
children: ["Type: ", e.limitType]
|
|
179
|
-
}), e.quotaType && /* @__PURE__ */
|
|
180
|
+
}), e.quotaType && /* @__PURE__ */ u("span", {
|
|
180
181
|
className: "px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded",
|
|
181
182
|
children: e.quotaType
|
|
182
183
|
})]
|
|
@@ -186,23 +187,23 @@ var p = () => {
|
|
|
186
187
|
})
|
|
187
188
|
})]
|
|
188
189
|
})]
|
|
189
|
-
}), /* @__PURE__ */
|
|
190
|
+
}), /* @__PURE__ */ d("div", {
|
|
190
191
|
className: "space-y-6",
|
|
191
|
-
children: [/* @__PURE__ */
|
|
192
|
+
children: [/* @__PURE__ */ d("div", {
|
|
192
193
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
194
|
+
children: [/* @__PURE__ */ u("div", {
|
|
194
195
|
className: "p-6 border-b border-border bg-muted/30",
|
|
195
|
-
children: /* @__PURE__ */
|
|
196
|
+
children: /* @__PURE__ */ u("h2", {
|
|
196
197
|
className: "text-lg font-semibold text-foreground",
|
|
197
198
|
children: m("billing.plans.subscribeTitle", "Subscribe")
|
|
198
199
|
})
|
|
199
|
-
}), /* @__PURE__ */
|
|
200
|
+
}), /* @__PURE__ */ d("div", {
|
|
200
201
|
className: "p-6 space-y-4",
|
|
201
202
|
children: [
|
|
202
|
-
/* @__PURE__ */
|
|
203
|
+
/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
203
204
|
className: "text-sm text-muted-foreground",
|
|
204
205
|
children: m("billing.plans.price", "Price")
|
|
205
|
-
}), /* @__PURE__ */
|
|
206
|
+
}), /* @__PURE__ */ d("p", {
|
|
206
207
|
className: "text-sm font-medium text-foreground mt-0.5",
|
|
207
208
|
children: [
|
|
208
209
|
t(_.price, _.currency),
|
|
@@ -210,21 +211,21 @@ var p = () => {
|
|
|
210
211
|
n(_.duration)
|
|
211
212
|
]
|
|
212
213
|
})] }),
|
|
213
|
-
/* @__PURE__ */
|
|
214
|
+
/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
214
215
|
className: "text-sm text-muted-foreground",
|
|
215
216
|
children: m("billing.plans.billingPeriod", "Billing Period")
|
|
216
|
-
}), /* @__PURE__ */
|
|
217
|
+
}), /* @__PURE__ */ u("p", {
|
|
217
218
|
className: "text-sm text-foreground mt-0.5",
|
|
218
219
|
children: r(_.duration)
|
|
219
220
|
})] }),
|
|
220
|
-
/* @__PURE__ */
|
|
221
|
+
/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
221
222
|
className: "text-sm text-muted-foreground",
|
|
222
223
|
children: m("billing.plans.quotasIncluded", "Quotas Included")
|
|
223
|
-
}), /* @__PURE__ */
|
|
224
|
+
}), /* @__PURE__ */ u("p", {
|
|
224
225
|
className: "text-sm font-medium text-foreground mt-0.5",
|
|
225
226
|
children: x.length
|
|
226
227
|
})] }),
|
|
227
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ u("button", {
|
|
228
229
|
type: "button",
|
|
229
230
|
onClick: () => g(`/checkout/${_.id}`),
|
|
230
231
|
className: "w-full px-4 py-2.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
@@ -232,7 +233,7 @@ var p = () => {
|
|
|
232
233
|
})
|
|
233
234
|
]
|
|
234
235
|
})]
|
|
235
|
-
}), /* @__PURE__ */
|
|
236
|
+
}), /* @__PURE__ */ u("button", {
|
|
236
237
|
type: "button",
|
|
237
238
|
onClick: () => g("/plans"),
|
|
238
239
|
className: "w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
|
|
@@ -240,20 +241,20 @@ var p = () => {
|
|
|
240
241
|
})]
|
|
241
242
|
})]
|
|
242
243
|
})]
|
|
243
|
-
}) : /* @__PURE__ */
|
|
244
|
+
}) : /* @__PURE__ */ u("div", {
|
|
244
245
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
245
|
-
children: /* @__PURE__ */
|
|
246
|
+
children: /* @__PURE__ */ d("div", {
|
|
246
247
|
className: "text-center space-y-4",
|
|
247
248
|
children: [
|
|
248
|
-
/* @__PURE__ */
|
|
249
|
+
/* @__PURE__ */ u("h2", {
|
|
249
250
|
className: "text-lg font-semibold text-foreground",
|
|
250
251
|
children: m("billing.plans.planNotFound", "Plan not found")
|
|
251
252
|
}),
|
|
252
|
-
/* @__PURE__ */
|
|
253
|
+
/* @__PURE__ */ u("p", {
|
|
253
254
|
className: "text-sm text-muted-foreground",
|
|
254
255
|
children: y?.message || m("billing.plans.planNotFoundMessage", "The plan you are looking for does not exist.")
|
|
255
256
|
}),
|
|
256
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ u("button", {
|
|
257
258
|
type: "button",
|
|
258
259
|
onClick: () => g("/plans"),
|
|
259
260
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
@@ -264,6 +265,6 @@ var p = () => {
|
|
|
264
265
|
});
|
|
265
266
|
};
|
|
266
267
|
//#endregion
|
|
267
|
-
export {
|
|
268
|
+
export { m as PlanBrowseDetailPage };
|
|
268
269
|
|
|
269
270
|
//# sourceMappingURL=PlanBrowseDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Browse Detail Page (User-facing)\n * View plan details and subscribe. No edit/toggle actions.\n */\n\nimport { type FC, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\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 const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const PlanBrowseDetailPage: 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 { plan, isLoading, error, refetch } = usePlan(planId);\n\n const combinedQuotas = useMemo(() => combineQuotas(plan?.features || []), [plan?.features]);\n\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>\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 if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlanDetails', 'Unable to load plan details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!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 <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 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 <h1 className=\"text-2xl font-bold text-foreground\">{plan.name}</h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\n </div>\n </div>\n\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 </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 {/* Subscribe 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.subscribeTitle', 'Subscribe')}\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.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.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 <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.quotasIncluded', 'Quotas Included')}\n </p>\n <p className=\"text-sm font-medium text-foreground mt-0.5\">\n {combinedQuotas.length}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\n className=\"w-full px-4 py-2.5 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 </div>\n </div>\n\n {/* Back */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanBrowseDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanBrowseDetailPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Browse Detail Page (User-facing)\n * View plan details and subscribe. No edit/toggle actions.\n */\n\nimport { type FC, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport type { PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n quotaType: string;\n limitType: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\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 const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n quotaType: feature.quota.quotaType ?? '',\n limitType,\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\nexport const PlanBrowseDetailPage: 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 { plan, isLoading, error, refetch } = usePlan(planId);\n\n const combinedQuotas = useMemo(() => combineQuotas(plan?.features || []), [plan?.features]);\n\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>\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 if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlanDetails', 'Unable to load plan details.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!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 <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 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 <h1 className=\"text-2xl font-bold text-foreground\">{plan.name}</h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {formatPlanDurationLabel(plan.duration)} billing plan\n </p>\n </div>\n </div>\n\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 </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 {/* Subscribe 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.subscribeTitle', 'Subscribe')}\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.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.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 <p className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.quotasIncluded', 'Quotas Included')}\n </p>\n <p className=\"text-sm font-medium text-foreground mt-0.5\">\n {combinedQuotas.length}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/checkout/${plan.id}`)}\n className=\"w-full px-4 py-2.5 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 </div>\n </div>\n\n {/* Back */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|