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