@burdenoff/microfe-billing 2026.530.4 → 2026.531.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +85 -84
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +176 -195
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +173 -172
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +216 -215
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useCheckout.js +2 -0
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +79 -78
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +29 -20
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/generated/global-operations.js +5 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,89 +4,90 @@ import { ServerError as n } from "../../../shared/components/ServerError.js";
|
|
|
4
4
|
import "../../../shared/components/index.js";
|
|
5
5
|
import { formatCurrency as r, formatPlanDuration as i, formatPlanDurationLabel as a } from "../../../shared/utils/format.js";
|
|
6
6
|
import { isServerError as o } from "../../../shared/utils/error.js";
|
|
7
|
+
import { getPlanFeatureQuantity as s } from "../../../shared/utils/planFeatureQuantity.js";
|
|
7
8
|
import "../../../shared/utils/index.js";
|
|
8
|
-
import { useBillingPermissions as
|
|
9
|
-
import { useBillingNavigate as
|
|
10
|
-
import { useAddon as
|
|
9
|
+
import { useBillingPermissions as c } from "../../../hooks/useBillingPermissions.js";
|
|
10
|
+
import { useBillingNavigate as l } from "../../../hooks/useBillingNavigate.js";
|
|
11
|
+
import { useAddon as u, useAddonMutations as d } from "../hooks/useAddons.js";
|
|
11
12
|
import "../hooks/index.js";
|
|
12
|
-
import { getDisplayPrice as
|
|
13
|
-
import { useMemo as
|
|
14
|
-
import { useParams as
|
|
15
|
-
import { jsx as
|
|
16
|
-
import { useI18n as
|
|
13
|
+
import { getDisplayPrice as f, useDefaultBillingCurrency as p } from "../../../hooks/useDefaultBillingCurrency.js";
|
|
14
|
+
import { useMemo as m } from "react";
|
|
15
|
+
import { useParams as h } from "react-router-dom";
|
|
16
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
17
|
+
import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
17
18
|
//#region src/billing/modules/addons/pages/AddonDetailPage.tsx
|
|
18
|
-
function
|
|
19
|
+
function y(e) {
|
|
19
20
|
let t = /* @__PURE__ */ new Map();
|
|
20
21
|
for (let n of e) {
|
|
21
22
|
if (!n.quota) continue;
|
|
22
|
-
let e = n.quota.id, r = n.quota.limits
|
|
23
|
-
|
|
23
|
+
let e = n.quota.id, r = n.quota.limits?.type ?? "unknown", i = s(n), a = t.get(e);
|
|
24
|
+
a ? a.totalValue += i : t.set(e, {
|
|
24
25
|
quotaId: e,
|
|
25
26
|
name: n.quota.name ?? "Unknown Quota",
|
|
26
27
|
totalValue: i,
|
|
27
|
-
type:
|
|
28
|
+
type: r
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
return Array.from(t.values());
|
|
31
32
|
}
|
|
32
|
-
var
|
|
33
|
-
let { t:
|
|
34
|
-
let n =
|
|
33
|
+
var b = () => {
|
|
34
|
+
let { t: s } = v(), b = (e, t) => {
|
|
35
|
+
let n = s(e);
|
|
35
36
|
return n === e ? t : n;
|
|
36
|
-
}, { addonId: x } =
|
|
37
|
-
return C.canViewAddons ? E ? /* @__PURE__ */
|
|
37
|
+
}, { addonId: x } = h(), S = l(), C = c(), w = p(), { addon: T, isLoading: E, error: D, refetch: O } = u(x), { toggleAddon: k, isToggling: A } = d(), j = m(() => y(T?.features || []), [T?.features]);
|
|
38
|
+
return C.canViewAddons ? E ? /* @__PURE__ */ _("div", {
|
|
38
39
|
className: "space-y-6 p-6",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
+
children: [/* @__PURE__ */ g("div", { className: "h-8 w-48 bg-muted animate-pulse rounded" }), /* @__PURE__ */ _("div", {
|
|
40
41
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
41
|
-
children: [/* @__PURE__ */
|
|
42
|
+
children: [/* @__PURE__ */ g("div", {
|
|
42
43
|
className: "lg:col-span-2 space-y-4",
|
|
43
44
|
children: [
|
|
44
45
|
1,
|
|
45
46
|
2,
|
|
46
47
|
3
|
|
47
|
-
].map((e) => /* @__PURE__ */
|
|
48
|
-
}), /* @__PURE__ */
|
|
48
|
+
].map((e) => /* @__PURE__ */ g("div", { className: "h-24 bg-muted animate-pulse rounded" }, e))
|
|
49
|
+
}), /* @__PURE__ */ g("div", { className: "h-64 bg-muted animate-pulse rounded" })]
|
|
49
50
|
})]
|
|
50
|
-
}) : D && o(D) ? /* @__PURE__ */
|
|
51
|
+
}) : D && o(D) ? /* @__PURE__ */ g("div", {
|
|
51
52
|
className: "p-6",
|
|
52
|
-
children: /* @__PURE__ */
|
|
53
|
+
children: /* @__PURE__ */ g(n, {
|
|
53
54
|
title: b("billing.addons.serverUnavailable", "Server Unavailable"),
|
|
54
55
|
message: b("billing.addons.serverUnavailableMessage", "Unable to load addon details. The server might be down or experiencing issues."),
|
|
55
56
|
onRetry: () => O(),
|
|
56
57
|
showRetry: !0
|
|
57
58
|
})
|
|
58
|
-
}) : T ? /* @__PURE__ */
|
|
59
|
+
}) : T ? /* @__PURE__ */ _("div", {
|
|
59
60
|
className: "space-y-6 p-6",
|
|
60
|
-
children: [/* @__PURE__ */
|
|
61
|
+
children: [/* @__PURE__ */ g(t, {
|
|
61
62
|
title: T.name,
|
|
62
63
|
description: `${a(T.duration)} addon`
|
|
63
|
-
}), /* @__PURE__ */
|
|
64
|
+
}), /* @__PURE__ */ _("div", {
|
|
64
65
|
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
65
|
-
children: [/* @__PURE__ */
|
|
66
|
+
children: [/* @__PURE__ */ _("div", {
|
|
66
67
|
className: "lg:col-span-2 space-y-6",
|
|
67
|
-
children: [/* @__PURE__ */
|
|
68
|
+
children: [/* @__PURE__ */ _("div", {
|
|
68
69
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
69
|
-
children: [/* @__PURE__ */
|
|
70
|
+
children: [/* @__PURE__ */ g("div", {
|
|
70
71
|
className: "p-4 border-b border-border bg-muted/30",
|
|
71
|
-
children: /* @__PURE__ */
|
|
72
|
+
children: /* @__PURE__ */ g("h2", {
|
|
72
73
|
className: "font-semibold text-foreground",
|
|
73
74
|
children: b("billing.addons.pricing", "Pricing")
|
|
74
75
|
})
|
|
75
|
-
}), /* @__PURE__ */
|
|
76
|
+
}), /* @__PURE__ */ _("div", {
|
|
76
77
|
className: "p-6",
|
|
77
|
-
children: [/* @__PURE__ */
|
|
78
|
+
children: [/* @__PURE__ */ _("div", {
|
|
78
79
|
className: "flex items-baseline gap-2",
|
|
79
|
-
children: [/* @__PURE__ */
|
|
80
|
+
children: [/* @__PURE__ */ g("span", {
|
|
80
81
|
className: "text-4xl font-bold text-foreground",
|
|
81
82
|
children: (() => {
|
|
82
|
-
let { price: e, currency: t } =
|
|
83
|
+
let { price: e, currency: t } = f(T.price, T.currency, T.currencyPrices, w);
|
|
83
84
|
return r(e, t);
|
|
84
85
|
})()
|
|
85
|
-
}), /* @__PURE__ */
|
|
86
|
+
}), /* @__PURE__ */ _("span", {
|
|
86
87
|
className: "text-lg text-muted-foreground",
|
|
87
88
|
children: ["/ ", i(T.duration)]
|
|
88
89
|
})]
|
|
89
|
-
}), /* @__PURE__ */
|
|
90
|
+
}), /* @__PURE__ */ _("p", {
|
|
90
91
|
className: "text-sm text-muted-foreground mt-2",
|
|
91
92
|
children: [
|
|
92
93
|
b("billing.addons.billed", "Billed"),
|
|
@@ -95,11 +96,11 @@ var y = () => {
|
|
|
95
96
|
]
|
|
96
97
|
})]
|
|
97
98
|
})]
|
|
98
|
-
}), /* @__PURE__ */
|
|
99
|
+
}), /* @__PURE__ */ _("div", {
|
|
99
100
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
100
|
-
children: [/* @__PURE__ */
|
|
101
|
+
children: [/* @__PURE__ */ g("div", {
|
|
101
102
|
className: "p-4 border-b border-border bg-muted/30",
|
|
102
|
-
children: /* @__PURE__ */
|
|
103
|
+
children: /* @__PURE__ */ _("h2", {
|
|
103
104
|
className: "font-semibold text-foreground",
|
|
104
105
|
children: [
|
|
105
106
|
b("billing.addons.includedQuotas", "Included Quotas"),
|
|
@@ -108,44 +109,44 @@ var y = () => {
|
|
|
108
109
|
")"
|
|
109
110
|
]
|
|
110
111
|
})
|
|
111
|
-
}), /* @__PURE__ */
|
|
112
|
+
}), /* @__PURE__ */ g("div", {
|
|
112
113
|
className: "p-4",
|
|
113
|
-
children: j.length === 0 ? /* @__PURE__ */
|
|
114
|
+
children: j.length === 0 ? /* @__PURE__ */ g("p", {
|
|
114
115
|
className: "text-sm text-muted-foreground py-4 text-center",
|
|
115
116
|
children: b("billing.addons.noQuotas", "No quotas associated with this addon.")
|
|
116
|
-
}) : /* @__PURE__ */
|
|
117
|
+
}) : /* @__PURE__ */ g("div", {
|
|
117
118
|
className: "space-y-3",
|
|
118
|
-
children: j.map((e) => /* @__PURE__ */
|
|
119
|
+
children: j.map((e) => /* @__PURE__ */ _("div", {
|
|
119
120
|
className: "flex items-center justify-between p-3 border border-border rounded-lg bg-muted/20",
|
|
120
|
-
children: [/* @__PURE__ */
|
|
121
|
+
children: [/* @__PURE__ */ _("div", {
|
|
121
122
|
className: "flex items-center gap-3",
|
|
122
|
-
children: [/* @__PURE__ */
|
|
123
|
+
children: [/* @__PURE__ */ g("div", {
|
|
123
124
|
className: "size-8 rounded-full bg-primary/10 flex items-center justify-center",
|
|
124
|
-
children: /* @__PURE__ */
|
|
125
|
+
children: /* @__PURE__ */ g("svg", {
|
|
125
126
|
className: "size-4 text-primary",
|
|
126
127
|
fill: "none",
|
|
127
128
|
viewBox: "0 0 24 24",
|
|
128
129
|
stroke: "currentColor",
|
|
129
|
-
children: /* @__PURE__ */
|
|
130
|
+
children: /* @__PURE__ */ g("path", {
|
|
130
131
|
strokeLinecap: "round",
|
|
131
132
|
strokeLinejoin: "round",
|
|
132
133
|
strokeWidth: 2,
|
|
133
134
|
d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
134
135
|
})
|
|
135
136
|
})
|
|
136
|
-
}), /* @__PURE__ */
|
|
137
|
+
}), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
|
|
137
138
|
className: "font-medium text-foreground",
|
|
138
139
|
children: e.name
|
|
139
|
-
}), /* @__PURE__ */
|
|
140
|
+
}), /* @__PURE__ */ _("p", {
|
|
140
141
|
className: "text-xs text-muted-foreground capitalize",
|
|
141
142
|
children: ["Type: ", e.type]
|
|
142
143
|
})] })]
|
|
143
|
-
}), /* @__PURE__ */
|
|
144
|
+
}), /* @__PURE__ */ _("div", {
|
|
144
145
|
className: "text-right",
|
|
145
|
-
children: [/* @__PURE__ */
|
|
146
|
+
children: [/* @__PURE__ */ g("span", {
|
|
146
147
|
className: "text-lg font-semibold text-status-success-text",
|
|
147
148
|
children: e.totalValue.toLocaleString()
|
|
148
|
-
}), /* @__PURE__ */
|
|
149
|
+
}), /* @__PURE__ */ g("p", {
|
|
149
150
|
className: "text-xs text-muted-foreground",
|
|
150
151
|
children: "total"
|
|
151
152
|
})]
|
|
@@ -154,46 +155,46 @@ var y = () => {
|
|
|
154
155
|
})
|
|
155
156
|
})]
|
|
156
157
|
})]
|
|
157
|
-
}), /* @__PURE__ */
|
|
158
|
+
}), /* @__PURE__ */ _("div", {
|
|
158
159
|
className: "space-y-6",
|
|
159
160
|
children: [
|
|
160
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ _("div", {
|
|
161
162
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
162
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ g("div", {
|
|
163
164
|
className: "p-4 border-b border-border bg-muted/30",
|
|
164
|
-
children: /* @__PURE__ */
|
|
165
|
+
children: /* @__PURE__ */ g("h2", {
|
|
165
166
|
className: "font-semibold text-foreground",
|
|
166
167
|
children: b("billing.addons.statusTitle", "Status")
|
|
167
168
|
})
|
|
168
|
-
}), /* @__PURE__ */
|
|
169
|
+
}), /* @__PURE__ */ _("div", {
|
|
169
170
|
className: "p-4 space-y-4",
|
|
170
171
|
children: [
|
|
171
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ _("div", {
|
|
172
173
|
className: "flex items-center justify-between",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
174
|
+
children: [/* @__PURE__ */ g("span", {
|
|
174
175
|
className: "text-sm text-muted-foreground",
|
|
175
176
|
children: b("billing.addons.status", "Status")
|
|
176
|
-
}), /* @__PURE__ */
|
|
177
|
+
}), /* @__PURE__ */ g("span", {
|
|
177
178
|
className: `px-2 py-1 text-xs font-medium rounded ${T.isActive ? "bg-status-success-bg-subtle text-status-success-text" : "bg-muted text-muted-foreground"}`,
|
|
178
179
|
children: T.isActive ? "Active" : "Inactive"
|
|
179
180
|
})]
|
|
180
181
|
}),
|
|
181
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ _("div", {
|
|
182
183
|
className: "flex items-center justify-between",
|
|
183
|
-
children: [/* @__PURE__ */
|
|
184
|
+
children: [/* @__PURE__ */ g("span", {
|
|
184
185
|
className: "text-sm text-muted-foreground",
|
|
185
186
|
children: b("billing.addons.currency", "Currency")
|
|
186
|
-
}), /* @__PURE__ */
|
|
187
|
+
}), /* @__PURE__ */ g("span", {
|
|
187
188
|
className: "text-sm font-medium text-foreground",
|
|
188
|
-
children:
|
|
189
|
+
children: f(T.price, T.currency, T.currencyPrices, w).currency
|
|
189
190
|
})]
|
|
190
191
|
}),
|
|
191
|
-
/* @__PURE__ */
|
|
192
|
+
/* @__PURE__ */ _("div", {
|
|
192
193
|
className: "flex items-center justify-between",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
194
|
+
children: [/* @__PURE__ */ g("span", {
|
|
194
195
|
className: "text-sm text-muted-foreground",
|
|
195
196
|
children: b("billing.addons.billingLabel", "Billing")
|
|
196
|
-
}), /* @__PURE__ */
|
|
197
|
+
}), /* @__PURE__ */ g("span", {
|
|
197
198
|
className: "text-sm font-medium text-foreground capitalize",
|
|
198
199
|
children: T.duration
|
|
199
200
|
})]
|
|
@@ -201,22 +202,22 @@ var y = () => {
|
|
|
201
202
|
]
|
|
202
203
|
})]
|
|
203
204
|
}),
|
|
204
|
-
C.canManageAddons && /* @__PURE__ */
|
|
205
|
+
C.canManageAddons && /* @__PURE__ */ _("div", {
|
|
205
206
|
className: "border border-border rounded-lg bg-card overflow-hidden",
|
|
206
|
-
children: [/* @__PURE__ */
|
|
207
|
+
children: [/* @__PURE__ */ g("div", {
|
|
207
208
|
className: "p-4 border-b border-border bg-muted/30",
|
|
208
|
-
children: /* @__PURE__ */
|
|
209
|
+
children: /* @__PURE__ */ g("h2", {
|
|
209
210
|
className: "font-semibold text-foreground",
|
|
210
211
|
children: b("billing.addons.actions", "Actions")
|
|
211
212
|
})
|
|
212
|
-
}), /* @__PURE__ */
|
|
213
|
+
}), /* @__PURE__ */ _("div", {
|
|
213
214
|
className: "p-4 space-y-2",
|
|
214
|
-
children: [/* @__PURE__ */
|
|
215
|
+
children: [/* @__PURE__ */ g("button", {
|
|
215
216
|
type: "button",
|
|
216
217
|
onClick: () => S(`/addons/${T.id}/edit`),
|
|
217
218
|
className: "w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
218
219
|
children: "Edit Addon"
|
|
219
|
-
}), /* @__PURE__ */
|
|
220
|
+
}), /* @__PURE__ */ g("button", {
|
|
220
221
|
type: "button",
|
|
221
222
|
onClick: async () => {
|
|
222
223
|
try {
|
|
@@ -231,7 +232,7 @@ var y = () => {
|
|
|
231
232
|
})]
|
|
232
233
|
})]
|
|
233
234
|
}),
|
|
234
|
-
/* @__PURE__ */
|
|
235
|
+
/* @__PURE__ */ g("button", {
|
|
235
236
|
type: "button",
|
|
236
237
|
onClick: () => S("/addons"),
|
|
237
238
|
className: "w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
|
|
@@ -240,19 +241,19 @@ var y = () => {
|
|
|
240
241
|
]
|
|
241
242
|
})]
|
|
242
243
|
})]
|
|
243
|
-
}) : /* @__PURE__ */
|
|
244
|
+
}) : /* @__PURE__ */ g("div", {
|
|
244
245
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
245
|
-
children: /* @__PURE__ */
|
|
246
|
+
children: /* @__PURE__ */ _("div", {
|
|
246
247
|
className: "text-center space-y-4",
|
|
247
248
|
children: [
|
|
248
|
-
/* @__PURE__ */
|
|
249
|
+
/* @__PURE__ */ g("div", {
|
|
249
250
|
className: "size-12 mx-auto rounded-full bg-muted flex items-center justify-center",
|
|
250
|
-
children: /* @__PURE__ */
|
|
251
|
+
children: /* @__PURE__ */ g("svg", {
|
|
251
252
|
className: "size-6 text-muted-foreground",
|
|
252
253
|
fill: "none",
|
|
253
254
|
viewBox: "0 0 24 24",
|
|
254
255
|
stroke: "currentColor",
|
|
255
|
-
children: /* @__PURE__ */
|
|
256
|
+
children: /* @__PURE__ */ g("path", {
|
|
256
257
|
strokeLinecap: "round",
|
|
257
258
|
strokeLinejoin: "round",
|
|
258
259
|
strokeWidth: 2,
|
|
@@ -260,15 +261,15 @@ var y = () => {
|
|
|
260
261
|
})
|
|
261
262
|
})
|
|
262
263
|
}),
|
|
263
|
-
/* @__PURE__ */
|
|
264
|
+
/* @__PURE__ */ g("h2", {
|
|
264
265
|
className: "text-lg font-semibold text-foreground",
|
|
265
266
|
children: b("billing.addons.addonNotFound", "Addon not found")
|
|
266
267
|
}),
|
|
267
|
-
/* @__PURE__ */
|
|
268
|
+
/* @__PURE__ */ g("p", {
|
|
268
269
|
className: "text-sm text-muted-foreground",
|
|
269
270
|
children: b("billing.addons.addonNotFoundMessage", "The addon you're looking for doesn't exist or has been removed.")
|
|
270
271
|
}),
|
|
271
|
-
/* @__PURE__ */
|
|
272
|
+
/* @__PURE__ */ g("button", {
|
|
272
273
|
type: "button",
|
|
273
274
|
onClick: () => S("/addons"),
|
|
274
275
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
@@ -276,9 +277,9 @@ var y = () => {
|
|
|
276
277
|
})
|
|
277
278
|
]
|
|
278
279
|
})
|
|
279
|
-
}) : /* @__PURE__ */
|
|
280
|
+
}) : /* @__PURE__ */ g(e, { message: b("billing.addons.noViewPermission", "You don't have permission to view addon details.") });
|
|
280
281
|
};
|
|
281
282
|
//#endregion
|
|
282
|
-
export {
|
|
283
|
+
export { b as AddonDetailPage };
|
|
283
284
|
|
|
284
285
|
//# sourceMappingURL=AddonDetailPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddonDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Detail Page\n * Displays detailed information about a specific addon\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): 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 { value?: number; type?: string } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n type: limitType,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const AddonDetailPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const displayCurrency = useDefaultBillingCurrency();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { toggleAddon, isToggling } = useAddonMutations();\n\n // Memoize combined quotas before any conditional returns\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n\n // Permission check\n if (!permissions.canViewAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noViewPermission',\n \"You don't have permission to view addon details.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n // Error state\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableMessage',\n 'Unable to load addon details. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n // Not found state\n if (!addon) {\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-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=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Addons\n </button>\n </div>\n </div>\n );\n }\n\n const handleToggle = async () => {\n try {\n await toggleAddon(addon.id, !addon.isActive);\n refetch();\n } catch (err) {\n console.error('Failed to toggle addon:', err);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\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-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-lg text-muted-foreground\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </div>\n\n {/* Quotas Card */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border rounded-lg bg-muted/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-primary/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-foreground\">{quota.name}</p>\n <p className=\"text-xs text-muted-foreground capitalize\">\n Type: {quota.type}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-muted-foreground\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Status Card */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.statusTitle', 'Status')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('billing.addons.status', 'Status')}\n </span>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${\n addon.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-muted text-muted-foreground'\n }`}\n >\n {addon.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('billing.addons.currency', 'Currency')}\n </span>\n <span className=\"text-sm font-medium text-foreground\">\n {\n getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n ).currency\n }\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-foreground capitalize\">\n {addon.duration}\n </span>\n </div>\n </div>\n </div>\n\n {/* Actions Card */}\n {permissions.canManageAddons && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.actions', 'Actions')}\n </h2>\n </div>\n <div className=\"p-4 space-y-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}/edit`)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Edit Addon\n </button>\n <button\n type=\"button\"\n onClick={handleToggle}\n disabled={isToggling}\n className={`w-full px-4 py-2 text-sm font-medium border border-border rounded-md transition-colors disabled:opacity-50 ${\n addon.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.addons.processing', 'Processing...')\n : addon.isActive\n ? tr('billing.addons.deactivateAddon', 'Deactivate Addon')\n : tr('billing.addons.activateAddon', 'Activate Addon')}\n </button>\n </div>\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AddonDetailPage.js","names":[],"sources":["../../../../../src/billing/modules/addons/pages/AddonDetailPage.tsx"],"sourcesContent":["/**\n * Addons Module - Addon Detail Page\n * Displays detailed information about a specific addon\n */\n\nimport { useMemo, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useAddon, useAddonMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n type: string;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): 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 limitType = (feature.quota.limits as { type?: string } | null)?.type ?? 'unknown';\n const limitValue = getPlanFeatureQuantity(feature);\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n type: limitType,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\nexport const AddonDetailPage: 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 { addonId } = useParams<{ addonId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const displayCurrency = useDefaultBillingCurrency();\n const { addon, isLoading, error, refetch } = useAddon(addonId);\n const { toggleAddon, isToggling } = useAddonMutations();\n\n // Memoize combined quotas before any conditional returns\n const combinedQuotas = useMemo(() => combineQuotas(addon?.features || []), [addon?.features]);\n\n // Permission check\n if (!permissions.canViewAddons) {\n return (\n <AccessDenied\n message={tr(\n 'billing.addons.noViewPermission',\n \"You don't have permission to view addon details.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <div className=\"lg:col-span-2 space-y-4\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-24 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n <div className=\"h-64 bg-muted animate-pulse rounded\" />\n </div>\n </div>\n );\n }\n\n // Error state\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.addons.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.addons.serverUnavailableMessage',\n 'Unable to load addon details. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n // Not found state\n if (!addon) {\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-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=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.addons.addonNotFound', 'Addon not found')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.addons.addonNotFoundMessage',\n \"The addon you're looking for doesn't exist or has been removed.\"\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Addons\n </button>\n </div>\n </div>\n );\n }\n\n const handleToggle = async () => {\n try {\n await toggleAddon(addon.id, !addon.isActive);\n refetch();\n } catch (err) {\n console.error('Failed to toggle addon:', err);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={addon.name}\n description={`${formatPlanDurationLabel(addon.duration)} addon`}\n />\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-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.pricing', 'Pricing')}\n </h2>\n </div>\n <div className=\"p-6\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-4xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-lg text-muted-foreground\">\n / {formatPlanDuration(addon.duration)}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-2\">\n {tr('billing.addons.billed', 'Billed')}{' '}\n {addon.duration === 'monthly'\n ? tr('billing.addons.everyMonth', 'every month')\n : tr('billing.addons.annually', 'annually')}\n </p>\n </div>\n </div>\n\n {/* Quotas Card */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.includedQuotas', 'Included Quotas')} ({combinedQuotas.length})\n </h2>\n </div>\n <div className=\"p-4\">\n {combinedQuotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground py-4 text-center\">\n {tr('billing.addons.noQuotas', 'No quotas associated with this addon.')}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {combinedQuotas.map((quota) => (\n <div\n key={quota.quotaId}\n className=\"flex items-center justify-between p-3 border border-border rounded-lg bg-muted/20\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-full bg-primary/10 flex items-center justify-center\">\n <svg\n className=\"size-4 text-primary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n <div>\n <p className=\"font-medium text-foreground\">{quota.name}</p>\n <p className=\"text-xs text-muted-foreground capitalize\">\n Type: {quota.type}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <span className=\"text-lg font-semibold text-status-success-text\">\n {quota.totalValue.toLocaleString()}\n </span>\n <p className=\"text-xs text-muted-foreground\">total</p>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Sidebar */}\n <div className=\"space-y-6\">\n {/* Status Card */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.statusTitle', 'Status')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('billing.addons.status', 'Status')}\n </span>\n <span\n className={`px-2 py-1 text-xs font-medium rounded ${\n addon.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-muted text-muted-foreground'\n }`}\n >\n {addon.isActive ? 'Active' : 'Inactive'}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('billing.addons.currency', 'Currency')}\n </span>\n <span className=\"text-sm font-medium text-foreground\">\n {\n getDisplayPrice(\n addon.price,\n addon.currency,\n addon.currencyPrices as Record<string, number> | null,\n displayCurrency\n ).currency\n }\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm text-muted-foreground\">\n {tr('billing.addons.billingLabel', 'Billing')}\n </span>\n <span className=\"text-sm font-medium text-foreground capitalize\">\n {addon.duration}\n </span>\n </div>\n </div>\n </div>\n\n {/* Actions Card */}\n {permissions.canManageAddons && (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.addons.actions', 'Actions')}\n </h2>\n </div>\n <div className=\"p-4 space-y-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/addons/${addon.id}/edit`)}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Edit Addon\n </button>\n <button\n type=\"button\"\n onClick={handleToggle}\n disabled={isToggling}\n className={`w-full px-4 py-2 text-sm font-medium border border-border rounded-md transition-colors disabled:opacity-50 ${\n addon.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.addons.processing', 'Processing...')\n : addon.isActive\n ? tr('billing.addons.deactivateAddon', 'Deactivate Addon')\n : tr('billing.addons.activateAddon', 'Activate Addon')}\n </button>\n </div>\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/addons')}\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 Addons\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|