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