@burdenoff/microfe-billing 2026.1014.3 → 2026.1023.2

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.
@@ -0,0 +1,50 @@
1
+ //#region src/billing/modules/plans/constants/enterprise.ts
2
+ var e = {
3
+ highlights: [
4
+ "Custom usage volumes",
5
+ "SSO / SCIM provisioning",
6
+ "Custom SLA & support contract",
7
+ "Volume-based custom pricing",
8
+ "Audit log with custom retention",
9
+ "Priority onboarding & migration"
10
+ ],
11
+ summary: "SSO/SCIM · Custom SLA · Custom pricing",
12
+ contactEmail: "contact@burdenoff.com"
13
+ }, t = {
14
+ fluidgrids: {
15
+ highlights: [
16
+ "Custom workflow and run volumes",
17
+ "Custom storage and retention",
18
+ "SSO & SAML",
19
+ "SCIM provisioning",
20
+ "99.95%+ uptime SLA",
21
+ "Dedicated support with SLA",
22
+ "OEM embedding of the builder SDK",
23
+ "Data residency options"
24
+ ],
25
+ summary: "Custom volumes · SSO/SCIM · OEM builder SDK",
26
+ contactEmail: "contact@fluidgrids.com"
27
+ },
28
+ vibecontrols: {
29
+ highlights: [
30
+ "Everything in Pro",
31
+ "Unlimited agents & seats",
32
+ "Production SSO/SCIM · SIEM streaming",
33
+ "Data residency · customer-managed keys",
34
+ "Dedicated tenant / self-host",
35
+ "Custom SLA · dedicated support"
36
+ ],
37
+ summary: "Dedicated tenant · SSO/SCIM · Custom SLA",
38
+ contactEmail: "contact@vibecontrols.com"
39
+ }
40
+ };
41
+ function n(n) {
42
+ return n ? t[n.toLowerCase()] ?? e : e;
43
+ }
44
+ function r(e) {
45
+ return `mailto:${e.contactEmail}?subject=Enterprise%20Plan%20Inquiry`;
46
+ }
47
+ //#endregion
48
+ export { r as enterpriseMailto, n as getEnterpriseOffer };
49
+
50
+ //# sourceMappingURL=enterprise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enterprise.js","names":[],"sources":["../../../../../src/billing/modules/plans/constants/enterprise.ts"],"sourcesContent":["/**\n * Enterprise \"Contact Us\" copy for the plans browse page.\n *\n * Enterprise is contact-sales in every product and has no backend plan\n * record, so its card is static. The highlights are product-specific and must\n * match the product website's Enterprise card, so a FluidGrids shell never\n * shows VibeControls wording (and vice versa). Products without curated copy\n * fall back to product-neutral highlights.\n */\n\nexport interface EnterpriseOffer {\n /** Bullet points on the grid card, in display order. */\n highlights: readonly string[];\n /** One-line summary for the compact list row. */\n summary: string;\n /** Sales inbox the Contact Us button opens. */\n contactEmail: string;\n}\n\nconst DEFAULT_ENTERPRISE_OFFER: EnterpriseOffer = {\n highlights: [\n 'Custom usage volumes',\n 'SSO / SCIM provisioning',\n 'Custom SLA & support contract',\n 'Volume-based custom pricing',\n 'Audit log with custom retention',\n 'Priority onboarding & migration',\n ],\n summary: 'SSO/SCIM · Custom SLA · Custom pricing',\n contactEmail: 'contact@burdenoff.com',\n};\n\n/** Keyed by billing product id. Mirrors each product website's Enterprise card. */\nconst ENTERPRISE_OFFERS: Readonly<Record<string, EnterpriseOffer>> = {\n // fluidgrids-website src/config/product.ts (plan key \"enterprise\")\n fluidgrids: {\n highlights: [\n 'Custom workflow and run volumes',\n 'Custom storage and retention',\n 'SSO & SAML',\n 'SCIM provisioning',\n '99.95%+ uptime SLA',\n 'Dedicated support with SLA',\n 'OEM embedding of the builder SDK',\n 'Data residency options',\n ],\n summary: 'Custom volumes · SSO/SCIM · OEM builder SDK',\n contactEmail: 'contact@fluidgrids.com',\n },\n // vibecontrols-website src/config/product.ts VC_ENTERPRISE_FEATURES\n vibecontrols: {\n highlights: [\n 'Everything in Pro',\n 'Unlimited agents & seats',\n 'Production SSO/SCIM · SIEM streaming',\n 'Data residency · customer-managed keys',\n 'Dedicated tenant / self-host',\n 'Custom SLA · dedicated support',\n ],\n summary: 'Dedicated tenant · SSO/SCIM · Custom SLA',\n contactEmail: 'contact@vibecontrols.com',\n },\n};\n\nexport function getEnterpriseOffer(productId: string | null | undefined): EnterpriseOffer {\n if (!productId) return DEFAULT_ENTERPRISE_OFFER;\n return ENTERPRISE_OFFERS[productId.toLowerCase()] ?? DEFAULT_ENTERPRISE_OFFER;\n}\n\nexport function enterpriseMailto(offer: EnterpriseOffer): string {\n return `mailto:${offer.contactEmail}?subject=${encodeURIComponent('Enterprise Plan Inquiry')}`;\n}\n"],"mappings":";AAmBA,IAAM,IAA4C;CAChD,YAAY;EACV;EACA;EACA;EACA;EACA;EACA;CACF;CACA,SAAS;CACT,cAAc;AAChB,GAGM,IAA+D;CAEnE,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,cAAc;EACZ,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;AACF;AAEA,SAAgB,EAAmB,GAAuD;CAExF,OADK,IACE,EAAkB,EAAU,YAAY,MAAM,IAD9B;AAEzB;AAEA,SAAgB,EAAiB,GAAgC;CAC/D,OAAO,UAAU,EAAM,aAAa;AACtC"}
@@ -11,13 +11,14 @@ import { getDisplayPrice as l, useDefaultBillingCurrency as u } from "../../../h
11
11
  import { usePlans as d } from "../hooks/usePlans.js";
12
12
  import "../hooks/index.js";
13
13
  import { FEATURED_QUOTA_LABELS as f, FEATURED_QUOTA_NAMES as p, formatFeaturedQuotaValue as m } from "../constants/featuredQuotas.js";
14
- import { useState as h } from "react";
15
- import { ArrowRight as g, Check as _, Sparkles as v } from "lucide-react";
16
- import { jsx as y, jsxs as b } from "react/jsx-runtime";
17
- import { useI18n as x } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
18
- import { IllustratedEmptyState as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
14
+ import { enterpriseMailto as h, getEnterpriseOffer as g } from "../constants/enterprise.js";
15
+ import { useState as _ } from "react";
16
+ import { ArrowRight as v, Check as y, Sparkles as b } from "lucide-react";
17
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
18
+ import { useI18n as C } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
19
+ import { IllustratedEmptyState as w, PagePurpose as T } from "@burdenoff/fe-libs/ui";
19
20
  //#region src/billing/modules/plans/pages/PlansBrowsePage.tsx
20
- function w(e) {
21
+ function E(e) {
21
22
  let t = /* @__PURE__ */ new Map();
22
23
  for (let n of e) {
23
24
  if (!n.quota) continue;
@@ -30,110 +31,112 @@ function w(e) {
30
31
  }
31
32
  return Array.from(t.values());
32
33
  }
33
- function T(e) {
34
- let t = w(e), n = new Map(t.map((e) => [e.name, e]));
34
+ function D(e) {
35
+ let t = E(e), n = new Map(t.map((e) => [e.name, e]));
35
36
  return p.flatMap((e) => {
36
37
  let t = n.get(e);
37
38
  return t ? [t] : [];
38
39
  });
39
40
  }
40
- var E = () => {
41
- let { t: r } = x(), i = (e, t) => {
41
+ var O = () => {
42
+ let { t: r } = C(), i = (e, t) => {
42
43
  let n = r(e);
43
44
  return n === e ? t : n;
44
- }, a = c(), { productId: s } = e(), { plans: l, isLoading: u, error: f, refetch: p } = d({
45
+ }, a = c(), { productId: s, plansDocsUrl: l } = e(), u = g(s), { plans: f, isLoading: p, error: m, refetch: h } = d({
45
46
  includeFree: !1,
46
47
  productId: s ?? void 0
47
- }), [m, g] = h("grid"), [_, v] = h(n.MONTHLY), w = l.filter((e) => e.isActive && e.isPurchasable !== !1 && e.duration === _), T = w.length >= 2 ? 1 : -1;
48
- return u && l.length === 0 ? /* @__PURE__ */ b("div", {
48
+ }), [v, y] = _("grid"), [b, E] = _(n.MONTHLY), D = f.filter((e) => e.isActive && e.isPurchasable !== !1 && e.duration === b), O = D.length >= 2 ? 1 : -1;
49
+ return p && f.length === 0 ? /* @__PURE__ */ S("div", {
49
50
  className: "space-y-6 px-6 lg:px-8 py-6",
50
- children: [/* @__PURE__ */ y("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded-card" }), /* @__PURE__ */ y("div", {
51
+ children: [/* @__PURE__ */ x("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded-card" }), /* @__PURE__ */ x("div", {
51
52
  className: "grid grid-cols-1 md:grid-cols-3 gap-6",
52
53
  children: [
53
54
  1,
54
55
  2,
55
56
  3
56
- ].map((e) => /* @__PURE__ */ b("div", {
57
+ ].map((e) => /* @__PURE__ */ S("div", {
57
58
  className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4",
58
59
  children: [
59
- /* @__PURE__ */ y("div", { className: "h-6 w-24 bg-bg-sunken animate-pulse rounded" }),
60
- /* @__PURE__ */ y("div", { className: "h-8 w-32 bg-bg-sunken animate-pulse rounded" }),
61
- /* @__PURE__ */ y("div", {
60
+ /* @__PURE__ */ x("div", { className: "h-6 w-24 bg-bg-sunken animate-pulse rounded" }),
61
+ /* @__PURE__ */ x("div", { className: "h-8 w-32 bg-bg-sunken animate-pulse rounded" }),
62
+ /* @__PURE__ */ x("div", {
62
63
  className: "space-y-2",
63
64
  children: [
64
65
  1,
65
66
  2,
66
67
  3
67
- ].map((e) => /* @__PURE__ */ y("div", { className: "h-4 w-full bg-bg-sunken animate-pulse rounded" }, e))
68
+ ].map((e) => /* @__PURE__ */ x("div", { className: "h-4 w-full bg-bg-sunken animate-pulse rounded" }, e))
68
69
  })
69
70
  ]
70
71
  }, e))
71
72
  })]
72
- }) : f && o(f) ? /* @__PURE__ */ y("div", {
73
+ }) : m && o(m) ? /* @__PURE__ */ x("div", {
73
74
  className: "p-6",
74
- children: /* @__PURE__ */ y(t, {
75
+ children: /* @__PURE__ */ x(t, {
75
76
  title: i("billing.plans.serverUnavailable", "Server Unavailable"),
76
77
  message: i("billing.plans.unableToLoadPlans", "Unable to load plans."),
77
- onRetry: () => p(),
78
+ onRetry: () => h(),
78
79
  showRetry: !0
79
80
  })
80
- }) : /* @__PURE__ */ b("div", {
81
+ }) : /* @__PURE__ */ S("div", {
81
82
  className: "space-y-6 px-6 lg:px-8 py-6",
82
83
  children: [
83
- /* @__PURE__ */ b("div", { children: [
84
- /* @__PURE__ */ y("h1", {
84
+ /* @__PURE__ */ S("div", { children: [
85
+ /* @__PURE__ */ x("h1", {
85
86
  className: "text-2xl md:text-3xl font-semibold tracking-tight text-text-primary",
86
87
  children: i("billing.plans.browsePlansTitle", "Plans")
87
88
  }),
88
- /* @__PURE__ */ y("p", {
89
+ /* @__PURE__ */ x("p", {
89
90
  className: "text-sm text-text-secondary mt-1",
90
91
  children: i("billing.plans.browsePlansSubtitle", "Choose a plan that fits your needs")
91
92
  }),
92
- /* @__PURE__ */ y(C, {
93
+ /* @__PURE__ */ x(T, {
93
94
  className: "mt-3",
95
+ learnMoreHref: l,
96
+ learnMoreLabel: i("billing.plans.learnMore", "Learn more"),
94
97
  children: i("billing.plans.browsePurpose", "Compare what each plan includes — quotas, features and price — and subscribe to the one that fits your team. Switch between monthly and yearly billing to see the difference, or contact us for a custom Enterprise contract.")
95
98
  })
96
99
  ] }),
97
- /* @__PURE__ */ b("div", {
100
+ /* @__PURE__ */ S("div", {
98
101
  className: "flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle",
99
- children: [/* @__PURE__ */ y("div", {
102
+ children: [/* @__PURE__ */ x("div", {
100
103
  className: "inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken",
101
- children: [n.MONTHLY, n.YEARLY].map((e) => /* @__PURE__ */ y("button", {
104
+ children: [n.MONTHLY, n.YEARLY].map((e) => /* @__PURE__ */ x("button", {
102
105
  type: "button",
103
- onClick: () => v(e),
104
- className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${_ === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
106
+ onClick: () => E(e),
107
+ className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${b === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
105
108
  children: e === n.MONTHLY ? "Monthly" : "Yearly"
106
109
  }, e))
107
- }), /* @__PURE__ */ b("div", {
110
+ }), /* @__PURE__ */ S("div", {
108
111
  className: "ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken",
109
- children: [/* @__PURE__ */ y("button", {
112
+ children: [/* @__PURE__ */ x("button", {
110
113
  type: "button",
111
- onClick: () => g("grid"),
112
- className: `p-1.5 rounded-button transition-colors ${m === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
114
+ onClick: () => y("grid"),
115
+ className: `p-1.5 rounded-button transition-colors ${v === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
113
116
  title: "Grid view",
114
- children: /* @__PURE__ */ y("svg", {
117
+ children: /* @__PURE__ */ x("svg", {
115
118
  className: "size-4",
116
119
  fill: "none",
117
120
  viewBox: "0 0 24 24",
118
121
  stroke: "currentColor",
119
- children: /* @__PURE__ */ y("path", {
122
+ children: /* @__PURE__ */ x("path", {
120
123
  strokeLinecap: "round",
121
124
  strokeLinejoin: "round",
122
125
  strokeWidth: 2,
123
126
  d: "M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"
124
127
  })
125
128
  })
126
- }), /* @__PURE__ */ y("button", {
129
+ }), /* @__PURE__ */ x("button", {
127
130
  type: "button",
128
- onClick: () => g("list"),
129
- className: `p-1.5 rounded-button transition-colors ${m === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
131
+ onClick: () => y("list"),
132
+ className: `p-1.5 rounded-button transition-colors ${v === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
130
133
  title: "List view",
131
- children: /* @__PURE__ */ y("svg", {
134
+ children: /* @__PURE__ */ x("svg", {
132
135
  className: "size-4",
133
136
  fill: "none",
134
137
  viewBox: "0 0 24 24",
135
138
  stroke: "currentColor",
136
- children: /* @__PURE__ */ y("path", {
139
+ children: /* @__PURE__ */ x("path", {
137
140
  strokeLinecap: "round",
138
141
  strokeLinejoin: "round",
139
142
  strokeWidth: 2,
@@ -143,104 +146,95 @@ var E = () => {
143
146
  })]
144
147
  })]
145
148
  }),
146
- w.length === 0 ? /* @__PURE__ */ y(S, {
149
+ D.length === 0 ? /* @__PURE__ */ x(w, {
147
150
  illustration: "empty-data",
148
151
  title: i("billing.plans.noPlansAvailable", "No plans available"),
149
152
  description: i("billing.plans.checkBackLater", "Check back later for new plans.")
150
- }) : m === "grid" ? /* @__PURE__ */ b("div", {
153
+ }) : v === "grid" ? /* @__PURE__ */ S("div", {
151
154
  className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch",
152
- children: [w.map((e, t) => /* @__PURE__ */ y(A, {
155
+ children: [D.map((e, t) => /* @__PURE__ */ x(j, {
153
156
  plan: e,
154
- highlighted: t === T,
157
+ highlighted: t === O,
155
158
  onView: () => a(`/plans/${e.id}`),
156
159
  onSubscribe: () => a(`/checkout/${e.id}`)
157
- }, e.id)), /* @__PURE__ */ y(O, {})]
158
- }) : /* @__PURE__ */ b("div", {
160
+ }, e.id)), /* @__PURE__ */ x(k, { offer: u })]
161
+ }) : /* @__PURE__ */ S("div", {
159
162
  className: "space-y-3",
160
- children: [w.map((e) => /* @__PURE__ */ y(j, {
163
+ children: [D.map((e) => /* @__PURE__ */ x(M, {
161
164
  plan: e,
162
165
  onView: () => a(`/plans/${e.id}`),
163
166
  onSubscribe: () => a(`/checkout/${e.id}`)
164
- }, e.id)), /* @__PURE__ */ y(k, {})]
167
+ }, e.id)), /* @__PURE__ */ x(A, { offer: u })]
165
168
  })
166
169
  ]
167
170
  });
168
- }, D = [
169
- "Unlimited Agents & Sessions",
170
- "Dedicated single-tenant infra",
171
- "Customer-managed encryption keys",
172
- "SSO / SCIM provisioning",
173
- "Custom SLA & support contract",
174
- "Volume-based custom pricing",
175
- "Audit log with custom retention",
176
- "Priority onboarding & migration"
177
- ], O = () => /* @__PURE__ */ b("div", {
171
+ }, k = ({ offer: e }) => /* @__PURE__ */ S("div", {
178
172
  className: "border border-border-strong bg-[var(--color-accent-soft)] rounded-card shadow-elevation-1 p-5 flex flex-col h-full",
179
173
  children: [
180
- /* @__PURE__ */ b("div", {
174
+ /* @__PURE__ */ S("div", {
181
175
  className: "mb-4",
182
- children: [/* @__PURE__ */ b("div", {
176
+ children: [/* @__PURE__ */ S("div", {
183
177
  className: "flex items-center gap-2",
184
- children: [/* @__PURE__ */ y("h3", {
178
+ children: [/* @__PURE__ */ x("h3", {
185
179
  className: "text-lg font-semibold text-text-primary",
186
180
  children: "Enterprise"
187
- }), /* @__PURE__ */ y("span", {
181
+ }), /* @__PURE__ */ x("span", {
188
182
  className: "text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link",
189
183
  children: "Contact Us"
190
184
  })]
191
- }), /* @__PURE__ */ y("span", {
185
+ }), /* @__PURE__ */ x("span", {
192
186
  className: "text-sm text-text-secondary",
193
187
  children: "Custom contract"
194
188
  })]
195
189
  }),
196
- /* @__PURE__ */ y("div", {
190
+ /* @__PURE__ */ x("div", {
197
191
  className: "flex items-baseline gap-1 mb-4",
198
- children: /* @__PURE__ */ y("span", {
192
+ children: /* @__PURE__ */ x("span", {
199
193
  className: "text-2xl font-semibold text-text-primary",
200
194
  children: "Custom pricing"
201
195
  })
202
196
  }),
203
- /* @__PURE__ */ y("ul", {
197
+ /* @__PURE__ */ x("ul", {
204
198
  className: "space-y-2 mb-4 flex-1",
205
- children: D.map((e) => /* @__PURE__ */ b("li", {
199
+ children: e.highlights.map((e) => /* @__PURE__ */ S("li", {
206
200
  className: "flex items-center gap-2 text-sm",
207
- children: [/* @__PURE__ */ y("svg", {
201
+ children: [/* @__PURE__ */ x("svg", {
208
202
  className: "size-4 text-status-success-text shrink-0",
209
203
  fill: "none",
210
204
  viewBox: "0 0 24 24",
211
205
  stroke: "currentColor",
212
- children: /* @__PURE__ */ y("path", {
206
+ children: /* @__PURE__ */ x("path", {
213
207
  strokeLinecap: "round",
214
208
  strokeLinejoin: "round",
215
209
  strokeWidth: 2,
216
210
  d: "M5 13l4 4L19 7"
217
211
  })
218
- }), /* @__PURE__ */ y("span", {
212
+ }), /* @__PURE__ */ x("span", {
219
213
  className: "text-text-primary",
220
214
  children: e
221
215
  })]
222
216
  }, e))
223
217
  }),
224
- /* @__PURE__ */ y("div", {
218
+ /* @__PURE__ */ x("div", {
225
219
  className: "space-y-2 pt-4 border-t border-border-subtle mt-auto",
226
- children: /* @__PURE__ */ y("a", {
227
- href: "mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry",
220
+ children: /* @__PURE__ */ x("a", {
221
+ href: h(e),
228
222
  className: "block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200",
229
223
  children: "Contact Us"
230
224
  })
231
225
  })
232
226
  ]
233
- }), k = () => /* @__PURE__ */ b("div", {
227
+ }), A = ({ offer: e }) => /* @__PURE__ */ S("div", {
234
228
  className: "flex items-center gap-4 p-5 border border-border-strong rounded-card bg-[var(--color-accent-soft)] shadow-elevation-1 transition-all duration-200 hover:shadow-elevation-2",
235
229
  children: [
236
- /* @__PURE__ */ y("div", {
230
+ /* @__PURE__ */ x("div", {
237
231
  className: "size-10 rounded-lg bg-bg-surface flex items-center justify-center shrink-0 shadow-elevation-1",
238
- children: /* @__PURE__ */ y("svg", {
232
+ children: /* @__PURE__ */ x("svg", {
239
233
  className: "size-5 text-text-link",
240
234
  fill: "none",
241
235
  viewBox: "0 0 24 24",
242
236
  stroke: "currentColor",
243
- children: /* @__PURE__ */ y("path", {
237
+ children: /* @__PURE__ */ x("path", {
244
238
  strokeLinecap: "round",
245
239
  strokeLinejoin: "round",
246
240
  strokeWidth: 2,
@@ -248,91 +242,91 @@ var E = () => {
248
242
  })
249
243
  })
250
244
  }),
251
- /* @__PURE__ */ b("div", {
245
+ /* @__PURE__ */ S("div", {
252
246
  className: "flex-1 min-w-0",
253
- children: [/* @__PURE__ */ b("div", {
247
+ children: [/* @__PURE__ */ S("div", {
254
248
  className: "flex items-center gap-2",
255
- children: [/* @__PURE__ */ y("h3", {
249
+ children: [/* @__PURE__ */ x("h3", {
256
250
  className: "font-semibold text-text-primary",
257
251
  children: "Enterprise"
258
- }), /* @__PURE__ */ y("span", {
252
+ }), /* @__PURE__ */ x("span", {
259
253
  className: "text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link shrink-0",
260
254
  children: "Contact Us"
261
255
  })]
262
- }), /* @__PURE__ */ y("p", {
256
+ }), /* @__PURE__ */ x("p", {
263
257
  className: "text-sm text-text-secondary",
264
- children: "Dedicated infra · SSO/SCIM · Custom SLA"
258
+ children: e.summary
265
259
  })]
266
260
  }),
267
- /* @__PURE__ */ b("div", {
261
+ /* @__PURE__ */ S("div", {
268
262
  className: "text-right",
269
- children: [/* @__PURE__ */ y("div", {
263
+ children: [/* @__PURE__ */ x("div", {
270
264
  className: "font-semibold text-text-primary",
271
265
  children: "Custom pricing"
272
- }), /* @__PURE__ */ y("div", {
266
+ }), /* @__PURE__ */ x("div", {
273
267
  className: "text-sm text-text-secondary",
274
268
  children: "contract-based"
275
269
  })]
276
270
  }),
277
- /* @__PURE__ */ y("a", {
278
- href: "mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry",
271
+ /* @__PURE__ */ x("a", {
272
+ href: h(e),
279
273
  className: "px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 shrink-0",
280
274
  children: "Contact Us"
281
275
  })
282
276
  ]
283
- }), A = ({ plan: e, highlighted: t = !1, onView: n, onSubscribe: o }) => {
284
- let s = T(e.features || []), c = w(e.features || []).length - s.length, d = u(), { price: p, currency: h } = l(e.price, e.currency, e.currencyPrices, d);
285
- return /* @__PURE__ */ b("div", {
277
+ }), j = ({ plan: e, highlighted: t = !1, onView: n, onSubscribe: o }) => {
278
+ let s = D(e.features || []), c = E(e.features || []).length - s.length, d = u(), { price: p, currency: h } = l(e.price, e.currency, e.currencyPrices, d);
279
+ return /* @__PURE__ */ S("div", {
286
280
  className: `relative flex flex-col h-full rounded-card bg-bg-surface p-6 transition-[border-color,box-shadow] duration-200 ${t ? "border-2 border-action-primary-bg shadow-[var(--shadow-glow)]" : "border border-border-subtle shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2"}`,
287
281
  children: [
288
- t && /* @__PURE__ */ b("span", {
282
+ t && /* @__PURE__ */ S("span", {
289
283
  className: "absolute -top-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1 rounded-full bg-action-primary-bg px-3 py-1 text-xs font-semibold text-action-primary-text shadow-elevation-2",
290
- children: [/* @__PURE__ */ y(v, {
284
+ children: [/* @__PURE__ */ x(b, {
291
285
  className: "size-3.5",
292
286
  "aria-hidden": "true"
293
287
  }), "Most popular"]
294
288
  }),
295
- /* @__PURE__ */ b("div", {
289
+ /* @__PURE__ */ S("div", {
296
290
  className: "mb-4",
297
- children: [/* @__PURE__ */ y("h3", {
291
+ children: [/* @__PURE__ */ x("h3", {
298
292
  className: "text-lg font-semibold text-text-primary",
299
293
  children: e.name
300
- }), /* @__PURE__ */ y("span", {
294
+ }), /* @__PURE__ */ x("span", {
301
295
  className: "text-sm text-text-secondary",
302
296
  children: a(e.duration)
303
297
  })]
304
298
  }),
305
- /* @__PURE__ */ b("div", {
299
+ /* @__PURE__ */ S("div", {
306
300
  className: "flex items-baseline gap-1.5 mb-5",
307
- children: [/* @__PURE__ */ y("span", {
301
+ children: [/* @__PURE__ */ x("span", {
308
302
  className: "text-4xl font-bold tabular-nums tracking-tight text-text-primary",
309
303
  children: r(p, h)
310
- }), /* @__PURE__ */ b("span", {
304
+ }), /* @__PURE__ */ S("span", {
311
305
  className: "text-text-secondary text-sm",
312
306
  children: ["/ ", i(e.duration)]
313
307
  })]
314
308
  }),
315
- s.length > 0 && /* @__PURE__ */ b("ul", {
309
+ s.length > 0 && /* @__PURE__ */ S("ul", {
316
310
  className: "space-y-2.5 mb-5 flex-1",
317
- children: [s.map((e) => /* @__PURE__ */ b("li", {
311
+ children: [s.map((e) => /* @__PURE__ */ S("li", {
318
312
  className: "flex items-center justify-between gap-2 text-sm",
319
- children: [/* @__PURE__ */ b("span", {
313
+ children: [/* @__PURE__ */ S("span", {
320
314
  className: "flex items-center gap-2 min-w-0",
321
- children: [/* @__PURE__ */ y("span", {
315
+ children: [/* @__PURE__ */ x("span", {
322
316
  className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]",
323
- children: /* @__PURE__ */ y(_, {
317
+ children: /* @__PURE__ */ x(y, {
324
318
  className: "size-3 text-action-primary-bg",
325
319
  "aria-hidden": "true"
326
320
  })
327
- }), /* @__PURE__ */ y("span", {
321
+ }), /* @__PURE__ */ x("span", {
328
322
  className: "truncate text-text-primary",
329
323
  children: f[e.name] ?? e.name
330
324
  })]
331
- }), /* @__PURE__ */ y("span", {
325
+ }), /* @__PURE__ */ x("span", {
332
326
  className: "font-semibold text-text-primary tabular-nums shrink-0",
333
327
  children: m(e.name, e.totalValue)
334
328
  })]
335
- }, e.quotaId)), c > 0 && /* @__PURE__ */ b("li", {
329
+ }, e.quotaId)), c > 0 && /* @__PURE__ */ S("li", {
336
330
  className: "text-sm text-text-secondary pl-7",
337
331
  children: [
338
332
  "+",
@@ -341,17 +335,17 @@ var E = () => {
341
335
  ]
342
336
  })]
343
337
  }),
344
- /* @__PURE__ */ b("div", {
338
+ /* @__PURE__ */ S("div", {
345
339
  className: "space-y-2 pt-4 border-t border-border-subtle mt-auto",
346
- children: [/* @__PURE__ */ b("button", {
340
+ children: [/* @__PURE__ */ S("button", {
347
341
  type: "button",
348
342
  onClick: o,
349
343
  className: "group inline-flex w-full items-center justify-center gap-2 rounded-button bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
350
- children: ["Subscribe", /* @__PURE__ */ y(g, {
344
+ children: ["Subscribe", /* @__PURE__ */ x(v, {
351
345
  className: "size-4 transition-transform duration-200",
352
346
  "aria-hidden": "true"
353
347
  })]
354
- }), /* @__PURE__ */ y("button", {
348
+ }), /* @__PURE__ */ x("button", {
355
349
  type: "button",
356
350
  onClick: n,
357
351
  className: "w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-secondary rounded-button hover:bg-bg-sunken hover:text-text-primary hover:border-border-strong transition-colors duration-200",
@@ -360,19 +354,19 @@ var E = () => {
360
354
  })
361
355
  ]
362
356
  });
363
- }, j = ({ plan: e, onView: t, onSubscribe: n }) => {
364
- let a = T(e.features || []), o = u();
365
- return /* @__PURE__ */ b("div", {
357
+ }, M = ({ plan: e, onView: t, onSubscribe: n }) => {
358
+ let a = D(e.features || []), o = u();
359
+ return /* @__PURE__ */ S("div", {
366
360
  className: "flex items-center gap-4 p-5 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 transition-all duration-200 hover:border-border-strong hover:shadow-elevation-2",
367
361
  children: [
368
- /* @__PURE__ */ y("div", {
362
+ /* @__PURE__ */ x("div", {
369
363
  className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0",
370
- children: /* @__PURE__ */ y("svg", {
364
+ children: /* @__PURE__ */ x("svg", {
371
365
  className: "size-5 text-text-link",
372
366
  fill: "none",
373
367
  viewBox: "0 0 24 24",
374
368
  stroke: "currentColor",
375
- children: /* @__PURE__ */ y("path", {
369
+ children: /* @__PURE__ */ x("path", {
376
370
  strokeLinecap: "round",
377
371
  strokeLinejoin: "round",
378
372
  strokeWidth: 2,
@@ -380,12 +374,12 @@ var E = () => {
380
374
  })
381
375
  })
382
376
  }),
383
- /* @__PURE__ */ b("div", {
377
+ /* @__PURE__ */ S("div", {
384
378
  className: "flex-1 min-w-0",
385
- children: [/* @__PURE__ */ y("h3", {
379
+ children: [/* @__PURE__ */ x("h3", {
386
380
  className: "font-semibold text-text-primary truncate",
387
381
  children: e.name
388
- }), /* @__PURE__ */ b("p", {
382
+ }), /* @__PURE__ */ S("p", {
389
383
  className: "text-sm text-text-secondary",
390
384
  children: [
391
385
  a.length,
@@ -395,27 +389,27 @@ var E = () => {
395
389
  ]
396
390
  })]
397
391
  }),
398
- /* @__PURE__ */ b("div", {
392
+ /* @__PURE__ */ S("div", {
399
393
  className: "text-right",
400
- children: [/* @__PURE__ */ y("div", {
394
+ children: [/* @__PURE__ */ x("div", {
401
395
  className: "font-semibold text-text-primary",
402
396
  children: (() => {
403
397
  let { price: t, currency: n } = l(e.price, e.currency, e.currencyPrices, o);
404
398
  return r(t, n);
405
399
  })()
406
- }), /* @__PURE__ */ b("div", {
400
+ }), /* @__PURE__ */ S("div", {
407
401
  className: "text-sm text-text-secondary",
408
402
  children: ["per ", i(e.duration)]
409
403
  })]
410
404
  }),
411
- /* @__PURE__ */ b("div", {
405
+ /* @__PURE__ */ S("div", {
412
406
  className: "flex items-center gap-2",
413
- children: [/* @__PURE__ */ y("button", {
407
+ children: [/* @__PURE__ */ x("button", {
414
408
  type: "button",
415
409
  onClick: n,
416
410
  className: "px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200",
417
411
  children: "Subscribe"
418
- }), /* @__PURE__ */ y("button", {
412
+ }), /* @__PURE__ */ x("button", {
419
413
  type: "button",
420
414
  onClick: t,
421
415
  className: "px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200",
@@ -426,6 +420,6 @@ var E = () => {
426
420
  });
427
421
  };
428
422
  //#endregion
429
- export { E as PlansBrowsePage };
423
+ export { O as PlansBrowsePage };
430
424
 
431
425
  //# sourceMappingURL=PlansBrowsePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { Check, ArrowRight, Sparkles } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport { PlanDuration } from '../../../shared/types';\nimport type { Plan, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\nimport {\n FEATURED_QUOTA_NAMES,\n FEATURED_QUOTA_LABELS,\n formatFeaturedQuotaValue,\n} from '../constants/featuredQuotas';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: 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 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 totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\n/** Returns only the featured quotas, in the canonical display order. */\nfunction getFeaturedQuotas(features: PlanFeature[]): CombinedQuota[] {\n const all = combineQuotas(features);\n const byName = new Map(all.map((q) => [q.name, q]));\n return FEATURED_QUOTA_NAMES.flatMap((name) => {\n const q = byName.get(name);\n return q ? [q] : [];\n });\n}\n\nexport const PlansBrowsePage: 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 navigateTo = useBillingNavigate();\n const { productId } = useBilling();\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration>(PlanDuration.MONTHLY);\n\n // Only show active, publicly purchasable plans to users\n const filteredPlans = plans.filter(\n (plan) => plan.isActive && plan.isPurchasable !== false && plan.duration === billingInterval\n );\n\n // Spotlight a recommended tier (the second plan when there are 2+, the common\n // \"mid / Pro\" sweet-spot) so the eye lands on a clear primary choice.\n const recommendedIndex = filteredPlans.length >= 2 ? 1 : -1;\n\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded-card\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4\"\n >\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\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.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.plans.browsePurpose',\n 'Compare what each plan includes — quotas, features and price — and subscribe to the one that fits your team. Switch between monthly and yearly billing to see the difference, or contact us for a custom Enterprise contract.'\n )}\n </PagePurpose>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {([PlanDuration.MONTHLY, PlanDuration.YEARLY] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n billingInterval === interval\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {interval === PlanDuration.MONTHLY ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.plans.noPlansAvailable', 'No plans available')}\n description={tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n />\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch\">\n {filteredPlans.map((plan, idx) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n highlighted={idx === recommendedIndex}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseCard />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseRow />\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Static Enterprise \"Contact Us\" card — no backend plan record\n// ============================================================================\n\nconst ENTERPRISE_HIGHLIGHTS = [\n 'Unlimited Agents & Sessions',\n 'Dedicated single-tenant infra',\n 'Customer-managed encryption keys',\n 'SSO / SCIM provisioning',\n 'Custom SLA & support contract',\n 'Volume-based custom pricing',\n 'Audit log with custom retention',\n 'Priority onboarding & migration',\n];\n\nconst EnterpriseCard: FC = () => (\n <div className=\"border border-border-strong bg-[var(--color-accent-soft)] rounded-card shadow-elevation-1 p-5 flex flex-col h-full\">\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link\">\n Contact Us\n </span>\n </div>\n <span className=\"text-sm text-text-secondary\">Custom contract</span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-semibold text-text-primary\">Custom pricing</span>\n </div>\n\n <ul className=\"space-y-2 mb-4 flex-1\">\n {ENTERPRISE_HIGHLIGHTS.map((item) => (\n <li key={item} className=\"flex items-center gap-2 text-sm\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n <span className=\"text-text-primary\">{item}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <a\n href=\"mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry\"\n className=\"block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Contact Us\n </a>\n </div>\n </div>\n);\n\nconst EnterpriseRow: FC = () => (\n <div className=\"flex items-center gap-4 p-5 border border-border-strong rounded-card bg-[var(--color-accent-soft)] shadow-elevation-1 transition-all duration-200 hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center shrink-0 shadow-elevation-1\">\n <svg className=\"size-5 text-text-link\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link shrink-0\">\n Contact Us\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">Dedicated infra · SSO/SCIM · Custom SLA</p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">Custom pricing</div>\n <div className=\"text-sm text-text-secondary\">contract-based</div>\n </div>\n\n <a\n href=\"mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry\"\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 shrink-0\"\n >\n Contact Us\n </a>\n </div>\n);\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{\n plan: Plan;\n highlighted?: boolean;\n onView: () => void;\n onSubscribe: () => void;\n}> = ({ plan, highlighted = false, onView, onSubscribe }) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const allQuotas = combineQuotas(plan.features || []);\n const extraCount = allQuotas.length - featuredQuotas.length;\n const displayCurrency = useDefaultBillingCurrency();\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n\n return (\n <div\n className={`relative flex flex-col h-full rounded-card bg-bg-surface p-6 transition-[border-color,box-shadow] duration-200 ${\n highlighted\n ? 'border-2 border-action-primary-bg shadow-[var(--shadow-glow)]'\n : 'border border-border-subtle shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2'\n }`}\n >\n {highlighted && (\n <span className=\"absolute -top-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1 rounded-full bg-action-primary-bg px-3 py-1 text-xs font-semibold text-action-primary-text shadow-elevation-2\">\n <Sparkles className=\"size-3.5\" aria-hidden=\"true\" />\n Most popular\n </span>\n )}\n\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{plan.name}</h3>\n <span className=\"text-sm text-text-secondary\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1.5 mb-5\">\n <span className=\"text-4xl font-bold tabular-nums tracking-tight text-text-primary\">\n {formatCurrency(price, currency)}\n </span>\n <span className=\"text-text-secondary text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {featuredQuotas.length > 0 && (\n <ul className=\"space-y-2.5 mb-5 flex-1\">\n {featuredQuotas.map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"flex items-center gap-2 min-w-0\">\n <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Check className=\"size-3 text-action-primary-bg\" aria-hidden=\"true\" />\n </span>\n <span className=\"truncate text-text-primary\">\n {FEATURED_QUOTA_LABELS[quota.name] ?? quota.name}\n </span>\n </span>\n <span className=\"font-semibold text-text-primary tabular-nums shrink-0\">\n {formatFeaturedQuotaValue(quota.name, quota.totalValue)}\n </span>\n </li>\n ))}\n {extraCount > 0 && (\n <li className=\"text-sm text-text-secondary pl-7\">+{extraCount} more — see details</li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"group inline-flex w-full items-center justify-center gap-2 rounded-button bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n Subscribe\n <ArrowRight className=\"size-4 transition-transform duration-200\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-secondary rounded-button hover:bg-bg-sunken hover:text-text-primary hover:border-border-strong transition-colors duration-200\"\n >\n View details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-5 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 transition-all duration-200 hover:border-border-strong hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate\">{plan.name}</h3>\n <p className=\"text-sm text-text-secondary\">\n {featuredQuotas.length} key feature{featuredQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-secondary\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n View\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { Check, ArrowRight, Sparkles } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport { PlanDuration } from '../../../shared/types';\nimport type { Plan, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\nimport {\n FEATURED_QUOTA_NAMES,\n FEATURED_QUOTA_LABELS,\n formatFeaturedQuotaValue,\n} from '../constants/featuredQuotas';\nimport {\n enterpriseMailto,\n getEnterpriseOffer,\n type EnterpriseOffer,\n} from '../constants/enterprise';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: 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 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 totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\n/** Returns only the featured quotas, in the canonical display order. */\nfunction getFeaturedQuotas(features: PlanFeature[]): CombinedQuota[] {\n const all = combineQuotas(features);\n const byName = new Map(all.map((q) => [q.name, q]));\n return FEATURED_QUOTA_NAMES.flatMap((name) => {\n const q = byName.get(name);\n return q ? [q] : [];\n });\n}\n\nexport const PlansBrowsePage: 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 navigateTo = useBillingNavigate();\n const { productId, plansDocsUrl } = useBilling();\n const enterpriseOffer = getEnterpriseOffer(productId);\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration>(PlanDuration.MONTHLY);\n\n // Only show active, publicly purchasable plans to users\n const filteredPlans = plans.filter(\n (plan) => plan.isActive && plan.isPurchasable !== false && plan.duration === billingInterval\n );\n\n // Spotlight a recommended tier (the second plan when there are 2+, the common\n // \"mid / Pro\" sweet-spot) so the eye lands on a clear primary choice.\n const recommendedIndex = filteredPlans.length >= 2 ? 1 : -1;\n\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded-card\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 space-y-4\"\n >\n <div className=\"h-6 w-24 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\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.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n <PagePurpose\n className=\"mt-3\"\n learnMoreHref={plansDocsUrl}\n learnMoreLabel={tr('billing.plans.learnMore', 'Learn more')}\n >\n {tr(\n 'billing.plans.browsePurpose',\n 'Compare what each plan includes — quotas, features and price — and subscribe to the one that fits your team. Switch between monthly and yearly billing to see the difference, or contact us for a custom Enterprise contract.'\n )}\n </PagePurpose>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {([PlanDuration.MONTHLY, PlanDuration.YEARLY] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval)}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n billingInterval === interval\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {interval === PlanDuration.MONTHLY ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-button transition-colors ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.plans.noPlansAvailable', 'No plans available')}\n description={tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n />\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch\">\n {filteredPlans.map((plan, idx) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n highlighted={idx === recommendedIndex}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseCard offer={enterpriseOffer} />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseRow offer={enterpriseOffer} />\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Static Enterprise \"Contact Us\" card — no backend plan record\n// ============================================================================\n\nconst EnterpriseCard: FC<{ offer: EnterpriseOffer }> = ({ offer }) => (\n <div className=\"border border-border-strong bg-[var(--color-accent-soft)] rounded-card shadow-elevation-1 p-5 flex flex-col h-full\">\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link\">\n Contact Us\n </span>\n </div>\n <span className=\"text-sm text-text-secondary\">Custom contract</span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-semibold text-text-primary\">Custom pricing</span>\n </div>\n\n <ul className=\"space-y-2 mb-4 flex-1\">\n {offer.highlights.map((item) => (\n <li key={item} className=\"flex items-center gap-2 text-sm\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n <span className=\"text-text-primary\">{item}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <a\n href={enterpriseMailto(offer)}\n className=\"block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Contact Us\n </a>\n </div>\n </div>\n);\n\nconst EnterpriseRow: FC<{ offer: EnterpriseOffer }> = ({ offer }) => (\n <div className=\"flex items-center gap-4 p-5 border border-border-strong rounded-card bg-[var(--color-accent-soft)] shadow-elevation-1 transition-all duration-200 hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center shrink-0 shadow-elevation-1\">\n <svg className=\"size-5 text-text-link\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link shrink-0\">\n Contact Us\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">{offer.summary}</p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">Custom pricing</div>\n <div className=\"text-sm text-text-secondary\">contract-based</div>\n </div>\n\n <a\n href={enterpriseMailto(offer)}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200 shrink-0\"\n >\n Contact Us\n </a>\n </div>\n);\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{\n plan: Plan;\n highlighted?: boolean;\n onView: () => void;\n onSubscribe: () => void;\n}> = ({ plan, highlighted = false, onView, onSubscribe }) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const allQuotas = combineQuotas(plan.features || []);\n const extraCount = allQuotas.length - featuredQuotas.length;\n const displayCurrency = useDefaultBillingCurrency();\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n\n return (\n <div\n className={`relative flex flex-col h-full rounded-card bg-bg-surface p-6 transition-[border-color,box-shadow] duration-200 ${\n highlighted\n ? 'border-2 border-action-primary-bg shadow-[var(--shadow-glow)]'\n : 'border border-border-subtle shadow-elevation-1 hover:border-border-strong hover:shadow-elevation-2'\n }`}\n >\n {highlighted && (\n <span className=\"absolute -top-3 left-1/2 -translate-x-1/2 inline-flex items-center gap-1 rounded-full bg-action-primary-bg px-3 py-1 text-xs font-semibold text-action-primary-text shadow-elevation-2\">\n <Sparkles className=\"size-3.5\" aria-hidden=\"true\" />\n Most popular\n </span>\n )}\n\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{plan.name}</h3>\n <span className=\"text-sm text-text-secondary\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1.5 mb-5\">\n <span className=\"text-4xl font-bold tabular-nums tracking-tight text-text-primary\">\n {formatCurrency(price, currency)}\n </span>\n <span className=\"text-text-secondary text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {featuredQuotas.length > 0 && (\n <ul className=\"space-y-2.5 mb-5 flex-1\">\n {featuredQuotas.map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"flex items-center gap-2 min-w-0\">\n <span className=\"flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <Check className=\"size-3 text-action-primary-bg\" aria-hidden=\"true\" />\n </span>\n <span className=\"truncate text-text-primary\">\n {FEATURED_QUOTA_LABELS[quota.name] ?? quota.name}\n </span>\n </span>\n <span className=\"font-semibold text-text-primary tabular-nums shrink-0\">\n {formatFeaturedQuotaValue(quota.name, quota.totalValue)}\n </span>\n </li>\n ))}\n {extraCount > 0 && (\n <li className=\"text-sm text-text-secondary pl-7\">+{extraCount} more — see details</li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border-subtle mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"group inline-flex w-full items-center justify-center gap-2 rounded-button bg-action-primary-bg px-4 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n Subscribe\n <ArrowRight className=\"size-4 transition-transform duration-200\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border-subtle text-text-secondary rounded-button hover:bg-bg-sunken hover:text-text-primary hover:border-border-strong transition-colors duration-200\"\n >\n View details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-5 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 transition-all duration-200 hover:border-border-strong hover:shadow-elevation-2\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-text-link\"\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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-text-primary truncate\">{plan.name}</h3>\n <p className=\"text-sm text-text-secondary\">\n {featuredQuotas.length} key feature{featuredQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-text-primary\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-text-secondary\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n View\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -7,32 +7,33 @@ var a = e(null), o = () => {
7
7
  let e = t(a);
8
8
  if (!e) throw Error("useBilling must be used within BillingProvider");
9
9
  return e;
10
- }, s = ({ children: e, basePath: t, navigate: o, currentUser: s, workspaceId: c, orgId: l, productId: u, apiGatewayUrl: d, authToken: f, stripePublishableKey: p, razorpayKey: m, onSubscriptionCreate: h, onSubscriptionUpdate: g, onPaymentProcess: _, registerNavItems: v, registerFooterItems: y }) => {
11
- let b = {
10
+ }, s = ({ children: e, basePath: t, navigate: o, currentUser: s, workspaceId: c, orgId: l, productId: u, plansDocsUrl: d, apiGatewayUrl: f, authToken: p, stripePublishableKey: m, razorpayKey: h, onSubscriptionCreate: g, onSubscriptionUpdate: _, onPaymentProcess: v, registerNavItems: y, registerFooterItems: b }) => {
11
+ let x = {
12
12
  basePath: t,
13
13
  navigate: o,
14
14
  currentUser: s,
15
15
  workspaceId: c,
16
16
  orgId: l,
17
17
  productId: u,
18
- apiGatewayUrl: d,
19
- authToken: f,
20
- stripePublishableKey: p,
21
- razorpayKey: m,
22
- onSubscriptionCreate: h,
23
- onSubscriptionUpdate: g,
24
- onPaymentProcess: _,
25
- registerNavItems: v,
26
- registerFooterItems: y
27
- }, x = /* @__PURE__ */ i(a.Provider, {
28
- value: b,
18
+ plansDocsUrl: d,
19
+ apiGatewayUrl: f,
20
+ authToken: p,
21
+ stripePublishableKey: m,
22
+ razorpayKey: h,
23
+ onSubscriptionCreate: g,
24
+ onSubscriptionUpdate: _,
25
+ onPaymentProcess: v,
26
+ registerNavItems: y,
27
+ registerFooterItems: b
28
+ }, S = /* @__PURE__ */ i(a.Provider, {
29
+ value: x,
29
30
  children: /* @__PURE__ */ i(r, {
30
31
  scopeId: l,
31
32
  gateway: "global",
32
33
  children: e
33
34
  })
34
35
  });
35
- return t ? x : /* @__PURE__ */ i(n, { children: x });
36
+ return t ? S : /* @__PURE__ */ i(n, { children: S });
36
37
  };
37
38
  //#endregion
38
39
  export { s as BillingProvider, o as useBilling };
@@ -1 +1 @@
1
- {"version":3,"file":"BillingProvider.js","names":[],"sources":["../../../src/billing/providers/BillingProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { BillingRootProps } from '../types';\n\n/**\n * Context value for Billing microfrontend\n */\ntype BillingContextValue = BillingRootProps;\n\nconst BillingContext = createContext<BillingContextValue | null>(null);\n\n/**\n * Hook to access Billing context\n */\nexport const useBilling = () => {\n const context = useContext(BillingContext);\n if (!context) {\n throw new Error('useBilling must be used within BillingProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Billing microfrontend\n * Sets up routing and context\n *\n * When integrated into a shell (basePath provided), the shell already has a router\n * so we don't create another one. When standalone (no basePath), use MemoryRouter.\n */\nexport const BillingProvider: FC<PropsWithChildren<BillingRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n}) => {\n const contextValue: BillingContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n };\n\n const content = (\n <BillingContext.Provider value={contextValue}>\n <PermissionProvider scopeId={orgId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </BillingContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAiB,EAA0C,IAAI,GAKxD,UAAmB;CAC9B,IAAM,IAAU,EAAW,CAAc;CACzC,IAAI,CAAC,GACH,MAAU,MAAM,gDAAgD;CAElE,OAAO;AACT,GASa,KAA4D,EACvE,aACA,aACA,aACA,gBACA,gBACA,UACA,cACA,kBACA,cACA,yBACA,gBACA,yBACA,yBACA,qBACA,qBACA,6BACI;CACJ,IAAM,IAAoC;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GAEM,IACJ,kBAAC,EAAe,UAAhB;EAAyB,OAAO;YAC9B,kBAAC,GAAD;GAAoB,SAAS;GAAO,SAAQ;GACzC;EACiB,CAAA;CACG,CAAA;CAU3B,OALI,IACK,IAIF,kBAAC,GAAD,EAAA,UAAe,EAAsB,CAAA;AAC9C"}
1
+ {"version":3,"file":"BillingProvider.js","names":[],"sources":["../../../src/billing/providers/BillingProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { BillingRootProps } from '../types';\n\n/**\n * Context value for Billing microfrontend\n */\ntype BillingContextValue = BillingRootProps;\n\nconst BillingContext = createContext<BillingContextValue | null>(null);\n\n/**\n * Hook to access Billing context\n */\nexport const useBilling = () => {\n const context = useContext(BillingContext);\n if (!context) {\n throw new Error('useBilling must be used within BillingProvider');\n }\n return context;\n};\n\n/**\n * Provider component for Billing microfrontend\n * Sets up routing and context\n *\n * When integrated into a shell (basePath provided), the shell already has a router\n * so we don't create another one. When standalone (no basePath), use MemoryRouter.\n */\nexport const BillingProvider: FC<PropsWithChildren<BillingRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n plansDocsUrl,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n}) => {\n const contextValue: BillingContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n orgId,\n productId,\n plansDocsUrl,\n apiGatewayUrl,\n authToken,\n stripePublishableKey,\n razorpayKey,\n onSubscriptionCreate,\n onSubscriptionUpdate,\n onPaymentProcess,\n registerNavItems,\n registerFooterItems,\n };\n\n const content = (\n <BillingContext.Provider value={contextValue}>\n <PermissionProvider scopeId={orgId} gateway=\"global\">\n {children}\n </PermissionProvider>\n </BillingContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // so we don't create another one. When standalone (no basePath), use MemoryRouter.\n if (basePath) {\n return content;\n }\n\n // Standalone mode: create MemoryRouter for independent operation\n return <MemoryRouter>{content}</MemoryRouter>;\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAiB,EAA0C,IAAI,GAKxD,UAAmB;CAC9B,IAAM,IAAU,EAAW,CAAc;CACzC,IAAI,CAAC,GACH,MAAU,MAAM,gDAAgD;CAElE,OAAO;AACT,GASa,KAA4D,EACvE,aACA,aACA,aACA,gBACA,gBACA,UACA,cACA,iBACA,kBACA,cACA,yBACA,gBACA,yBACA,yBACA,qBACA,qBACA,6BACI;CACJ,IAAM,IAAoC;EACxC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GAEM,IACJ,kBAAC,EAAe,UAAhB;EAAyB,OAAO;YAC9B,kBAAC,GAAD;GAAoB,SAAS;GAAO,SAAQ;GACzC;EACiB,CAAA;CACG,CAAA;CAU3B,OALI,IACK,IAIF,kBAAC,GAAD,EAAA,UAAe,EAAsB,CAAA;AAC9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.1014.3",
3
+ "version": "2026.1023.2",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [