@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.
@@ -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 s } from "../../../hooks/useBillingPermissions.js";
9
- import { useBillingNavigate as c } from "../../../hooks/useBillingNavigate.js";
10
- import { useAddon as l, useAddonMutations as u } from "../hooks/useAddons.js";
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 d, useDefaultBillingCurrency as f } from "../../../hooks/useDefaultBillingCurrency.js";
13
- import { useMemo as p } from "react";
14
- import { useParams as m } from "react-router-dom";
15
- import { jsx as h, jsxs as g } from "react/jsx-runtime";
16
- import { useI18n as _ } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
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 v(e) {
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, i = r?.value ?? 0, a = r?.type ?? "unknown", o = t.get(e);
23
- o ? o.totalValue += i : t.set(e, {
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: a
28
+ type: r
28
29
  });
29
30
  }
30
31
  return Array.from(t.values());
31
32
  }
32
- var y = () => {
33
- let { t: y } = _(), b = (e, t) => {
34
- let n = y(e);
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 } = m(), S = c(), C = s(), w = f(), { addon: T, isLoading: E, error: D, refetch: O } = l(x), { toggleAddon: k, isToggling: A } = u(), j = p(() => v(T?.features || []), [T?.features]);
37
- return C.canViewAddons ? E ? /* @__PURE__ */ g("div", {
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__ */ h("div", { className: "h-8 w-48 bg-muted animate-pulse rounded" }), /* @__PURE__ */ g("div", {
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__ */ h("div", {
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__ */ h("div", { className: "h-24 bg-muted animate-pulse rounded" }, e))
48
- }), /* @__PURE__ */ h("div", { className: "h-64 bg-muted animate-pulse rounded" })]
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__ */ h("div", {
51
+ }) : D && o(D) ? /* @__PURE__ */ g("div", {
51
52
  className: "p-6",
52
- children: /* @__PURE__ */ h(n, {
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__ */ g("div", {
59
+ }) : T ? /* @__PURE__ */ _("div", {
59
60
  className: "space-y-6 p-6",
60
- children: [/* @__PURE__ */ h(t, {
61
+ children: [/* @__PURE__ */ g(t, {
61
62
  title: T.name,
62
63
  description: `${a(T.duration)} addon`
63
- }), /* @__PURE__ */ g("div", {
64
+ }), /* @__PURE__ */ _("div", {
64
65
  className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
65
- children: [/* @__PURE__ */ g("div", {
66
+ children: [/* @__PURE__ */ _("div", {
66
67
  className: "lg:col-span-2 space-y-6",
67
- children: [/* @__PURE__ */ g("div", {
68
+ children: [/* @__PURE__ */ _("div", {
68
69
  className: "border border-border rounded-lg bg-card overflow-hidden",
69
- children: [/* @__PURE__ */ h("div", {
70
+ children: [/* @__PURE__ */ g("div", {
70
71
  className: "p-4 border-b border-border bg-muted/30",
71
- children: /* @__PURE__ */ h("h2", {
72
+ children: /* @__PURE__ */ g("h2", {
72
73
  className: "font-semibold text-foreground",
73
74
  children: b("billing.addons.pricing", "Pricing")
74
75
  })
75
- }), /* @__PURE__ */ g("div", {
76
+ }), /* @__PURE__ */ _("div", {
76
77
  className: "p-6",
77
- children: [/* @__PURE__ */ g("div", {
78
+ children: [/* @__PURE__ */ _("div", {
78
79
  className: "flex items-baseline gap-2",
79
- children: [/* @__PURE__ */ h("span", {
80
+ children: [/* @__PURE__ */ g("span", {
80
81
  className: "text-4xl font-bold text-foreground",
81
82
  children: (() => {
82
- let { price: e, currency: t } = d(T.price, T.currency, T.currencyPrices, w);
83
+ let { price: e, currency: t } = f(T.price, T.currency, T.currencyPrices, w);
83
84
  return r(e, t);
84
85
  })()
85
- }), /* @__PURE__ */ g("span", {
86
+ }), /* @__PURE__ */ _("span", {
86
87
  className: "text-lg text-muted-foreground",
87
88
  children: ["/ ", i(T.duration)]
88
89
  })]
89
- }), /* @__PURE__ */ g("p", {
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__ */ g("div", {
99
+ }), /* @__PURE__ */ _("div", {
99
100
  className: "border border-border rounded-lg bg-card overflow-hidden",
100
- children: [/* @__PURE__ */ h("div", {
101
+ children: [/* @__PURE__ */ g("div", {
101
102
  className: "p-4 border-b border-border bg-muted/30",
102
- children: /* @__PURE__ */ g("h2", {
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__ */ h("div", {
112
+ }), /* @__PURE__ */ g("div", {
112
113
  className: "p-4",
113
- children: j.length === 0 ? /* @__PURE__ */ h("p", {
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__ */ h("div", {
117
+ }) : /* @__PURE__ */ g("div", {
117
118
  className: "space-y-3",
118
- children: j.map((e) => /* @__PURE__ */ g("div", {
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__ */ g("div", {
121
+ children: [/* @__PURE__ */ _("div", {
121
122
  className: "flex items-center gap-3",
122
- children: [/* @__PURE__ */ h("div", {
123
+ children: [/* @__PURE__ */ g("div", {
123
124
  className: "size-8 rounded-full bg-primary/10 flex items-center justify-center",
124
- children: /* @__PURE__ */ h("svg", {
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__ */ h("path", {
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__ */ g("div", { children: [/* @__PURE__ */ h("p", {
137
+ }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
137
138
  className: "font-medium text-foreground",
138
139
  children: e.name
139
- }), /* @__PURE__ */ g("p", {
140
+ }), /* @__PURE__ */ _("p", {
140
141
  className: "text-xs text-muted-foreground capitalize",
141
142
  children: ["Type: ", e.type]
142
143
  })] })]
143
- }), /* @__PURE__ */ g("div", {
144
+ }), /* @__PURE__ */ _("div", {
144
145
  className: "text-right",
145
- children: [/* @__PURE__ */ h("span", {
146
+ children: [/* @__PURE__ */ g("span", {
146
147
  className: "text-lg font-semibold text-status-success-text",
147
148
  children: e.totalValue.toLocaleString()
148
- }), /* @__PURE__ */ h("p", {
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__ */ g("div", {
158
+ }), /* @__PURE__ */ _("div", {
158
159
  className: "space-y-6",
159
160
  children: [
160
- /* @__PURE__ */ g("div", {
161
+ /* @__PURE__ */ _("div", {
161
162
  className: "border border-border rounded-lg bg-card overflow-hidden",
162
- children: [/* @__PURE__ */ h("div", {
163
+ children: [/* @__PURE__ */ g("div", {
163
164
  className: "p-4 border-b border-border bg-muted/30",
164
- children: /* @__PURE__ */ h("h2", {
165
+ children: /* @__PURE__ */ g("h2", {
165
166
  className: "font-semibold text-foreground",
166
167
  children: b("billing.addons.statusTitle", "Status")
167
168
  })
168
- }), /* @__PURE__ */ g("div", {
169
+ }), /* @__PURE__ */ _("div", {
169
170
  className: "p-4 space-y-4",
170
171
  children: [
171
- /* @__PURE__ */ g("div", {
172
+ /* @__PURE__ */ _("div", {
172
173
  className: "flex items-center justify-between",
173
- children: [/* @__PURE__ */ h("span", {
174
+ children: [/* @__PURE__ */ g("span", {
174
175
  className: "text-sm text-muted-foreground",
175
176
  children: b("billing.addons.status", "Status")
176
- }), /* @__PURE__ */ h("span", {
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__ */ g("div", {
182
+ /* @__PURE__ */ _("div", {
182
183
  className: "flex items-center justify-between",
183
- children: [/* @__PURE__ */ h("span", {
184
+ children: [/* @__PURE__ */ g("span", {
184
185
  className: "text-sm text-muted-foreground",
185
186
  children: b("billing.addons.currency", "Currency")
186
- }), /* @__PURE__ */ h("span", {
187
+ }), /* @__PURE__ */ g("span", {
187
188
  className: "text-sm font-medium text-foreground",
188
- children: d(T.price, T.currency, T.currencyPrices, w).currency
189
+ children: f(T.price, T.currency, T.currencyPrices, w).currency
189
190
  })]
190
191
  }),
191
- /* @__PURE__ */ g("div", {
192
+ /* @__PURE__ */ _("div", {
192
193
  className: "flex items-center justify-between",
193
- children: [/* @__PURE__ */ h("span", {
194
+ children: [/* @__PURE__ */ g("span", {
194
195
  className: "text-sm text-muted-foreground",
195
196
  children: b("billing.addons.billingLabel", "Billing")
196
- }), /* @__PURE__ */ h("span", {
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__ */ g("div", {
205
+ C.canManageAddons && /* @__PURE__ */ _("div", {
205
206
  className: "border border-border rounded-lg bg-card overflow-hidden",
206
- children: [/* @__PURE__ */ h("div", {
207
+ children: [/* @__PURE__ */ g("div", {
207
208
  className: "p-4 border-b border-border bg-muted/30",
208
- children: /* @__PURE__ */ h("h2", {
209
+ children: /* @__PURE__ */ g("h2", {
209
210
  className: "font-semibold text-foreground",
210
211
  children: b("billing.addons.actions", "Actions")
211
212
  })
212
- }), /* @__PURE__ */ g("div", {
213
+ }), /* @__PURE__ */ _("div", {
213
214
  className: "p-4 space-y-2",
214
- children: [/* @__PURE__ */ h("button", {
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__ */ h("button", {
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__ */ h("button", {
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__ */ h("div", {
244
+ }) : /* @__PURE__ */ g("div", {
244
245
  className: "flex items-center justify-center h-full min-h-[400px]",
245
- children: /* @__PURE__ */ g("div", {
246
+ children: /* @__PURE__ */ _("div", {
246
247
  className: "text-center space-y-4",
247
248
  children: [
248
- /* @__PURE__ */ h("div", {
249
+ /* @__PURE__ */ g("div", {
249
250
  className: "size-12 mx-auto rounded-full bg-muted flex items-center justify-center",
250
- children: /* @__PURE__ */ h("svg", {
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__ */ h("path", {
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__ */ h("h2", {
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__ */ h("p", {
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__ */ h("button", {
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__ */ h(e, { message: b("billing.addons.noViewPermission", "You don't have permission to view addon details.") });
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 { y as AddonDetailPage };
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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"}