@burdenoff/microfe-billing 2026.1014.3 → 2026.1023.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/plans/constants/enterprise.js +50 -0
- package/dist/billing/modules/plans/constants/enterprise.js.map +1 -0
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +122 -130
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
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
|
|
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,110 @@ function w(e) {
|
|
|
30
31
|
}
|
|
31
32
|
return Array.from(t.values());
|
|
32
33
|
}
|
|
33
|
-
function
|
|
34
|
-
let t =
|
|
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
|
|
41
|
-
let { t: r } =
|
|
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:
|
|
45
|
+
}, a = c(), { productId: s } = e(), l = g(s), { plans: u, isLoading: f, error: p, refetch: m } = d({
|
|
45
46
|
includeFree: !1,
|
|
46
47
|
productId: s ?? void 0
|
|
47
|
-
}), [
|
|
48
|
-
return
|
|
48
|
+
}), [h, v] = _("grid"), [y, b] = _(n.MONTHLY), E = u.filter((e) => e.isActive && e.isPurchasable !== !1 && e.duration === y), D = E.length >= 2 ? 1 : -1;
|
|
49
|
+
return f && u.length === 0 ? /* @__PURE__ */ S("div", {
|
|
49
50
|
className: "space-y-6 px-6 lg:px-8 py-6",
|
|
50
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
60
|
-
/* @__PURE__ */
|
|
61
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
-
}) :
|
|
73
|
+
}) : p && o(p) ? /* @__PURE__ */ x("div", {
|
|
73
74
|
className: "p-6",
|
|
74
|
-
children: /* @__PURE__ */
|
|
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: () =>
|
|
78
|
+
onRetry: () => m(),
|
|
78
79
|
showRetry: !0
|
|
79
80
|
})
|
|
80
|
-
}) : /* @__PURE__ */
|
|
81
|
+
}) : /* @__PURE__ */ S("div", {
|
|
81
82
|
className: "space-y-6 px-6 lg:px-8 py-6",
|
|
82
83
|
children: [
|
|
83
|
-
/* @__PURE__ */
|
|
84
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
93
|
+
/* @__PURE__ */ x(T, {
|
|
93
94
|
className: "mt-3",
|
|
94
95
|
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
96
|
})
|
|
96
97
|
] }),
|
|
97
|
-
/* @__PURE__ */
|
|
98
|
+
/* @__PURE__ */ S("div", {
|
|
98
99
|
className: "flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle",
|
|
99
|
-
children: [/* @__PURE__ */
|
|
100
|
+
children: [/* @__PURE__ */ x("div", {
|
|
100
101
|
className: "inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken",
|
|
101
|
-
children: [n.MONTHLY, n.YEARLY].map((e) => /* @__PURE__ */
|
|
102
|
+
children: [n.MONTHLY, n.YEARLY].map((e) => /* @__PURE__ */ x("button", {
|
|
102
103
|
type: "button",
|
|
103
|
-
onClick: () =>
|
|
104
|
-
className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${
|
|
104
|
+
onClick: () => b(e),
|
|
105
|
+
className: `px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${y === e ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
|
|
105
106
|
children: e === n.MONTHLY ? "Monthly" : "Yearly"
|
|
106
107
|
}, e))
|
|
107
|
-
}), /* @__PURE__ */
|
|
108
|
+
}), /* @__PURE__ */ S("div", {
|
|
108
109
|
className: "ml-auto inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken",
|
|
109
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ x("button", {
|
|
110
111
|
type: "button",
|
|
111
|
-
onClick: () =>
|
|
112
|
-
className: `p-1.5 rounded-button transition-colors ${
|
|
112
|
+
onClick: () => v("grid"),
|
|
113
|
+
className: `p-1.5 rounded-button transition-colors ${h === "grid" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
|
|
113
114
|
title: "Grid view",
|
|
114
|
-
children: /* @__PURE__ */
|
|
115
|
+
children: /* @__PURE__ */ x("svg", {
|
|
115
116
|
className: "size-4",
|
|
116
117
|
fill: "none",
|
|
117
118
|
viewBox: "0 0 24 24",
|
|
118
119
|
stroke: "currentColor",
|
|
119
|
-
children: /* @__PURE__ */
|
|
120
|
+
children: /* @__PURE__ */ x("path", {
|
|
120
121
|
strokeLinecap: "round",
|
|
121
122
|
strokeLinejoin: "round",
|
|
122
123
|
strokeWidth: 2,
|
|
123
124
|
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
125
|
})
|
|
125
126
|
})
|
|
126
|
-
}), /* @__PURE__ */
|
|
127
|
+
}), /* @__PURE__ */ x("button", {
|
|
127
128
|
type: "button",
|
|
128
|
-
onClick: () =>
|
|
129
|
-
className: `p-1.5 rounded-button transition-colors ${
|
|
129
|
+
onClick: () => v("list"),
|
|
130
|
+
className: `p-1.5 rounded-button transition-colors ${h === "list" ? "bg-bg-surface text-text-primary shadow-sm" : "text-text-secondary hover:text-text-primary"}`,
|
|
130
131
|
title: "List view",
|
|
131
|
-
children: /* @__PURE__ */
|
|
132
|
+
children: /* @__PURE__ */ x("svg", {
|
|
132
133
|
className: "size-4",
|
|
133
134
|
fill: "none",
|
|
134
135
|
viewBox: "0 0 24 24",
|
|
135
136
|
stroke: "currentColor",
|
|
136
|
-
children: /* @__PURE__ */
|
|
137
|
+
children: /* @__PURE__ */ x("path", {
|
|
137
138
|
strokeLinecap: "round",
|
|
138
139
|
strokeLinejoin: "round",
|
|
139
140
|
strokeWidth: 2,
|
|
@@ -143,104 +144,95 @@ var E = () => {
|
|
|
143
144
|
})]
|
|
144
145
|
})]
|
|
145
146
|
}),
|
|
146
|
-
|
|
147
|
+
E.length === 0 ? /* @__PURE__ */ x(w, {
|
|
147
148
|
illustration: "empty-data",
|
|
148
149
|
title: i("billing.plans.noPlansAvailable", "No plans available"),
|
|
149
150
|
description: i("billing.plans.checkBackLater", "Check back later for new plans.")
|
|
150
|
-
}) :
|
|
151
|
+
}) : h === "grid" ? /* @__PURE__ */ S("div", {
|
|
151
152
|
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-stretch",
|
|
152
|
-
children: [
|
|
153
|
+
children: [E.map((e, t) => /* @__PURE__ */ x(j, {
|
|
153
154
|
plan: e,
|
|
154
|
-
highlighted: t ===
|
|
155
|
+
highlighted: t === D,
|
|
155
156
|
onView: () => a(`/plans/${e.id}`),
|
|
156
157
|
onSubscribe: () => a(`/checkout/${e.id}`)
|
|
157
|
-
}, e.id)), /* @__PURE__ */
|
|
158
|
-
}) : /* @__PURE__ */
|
|
158
|
+
}, e.id)), /* @__PURE__ */ x(k, { offer: l })]
|
|
159
|
+
}) : /* @__PURE__ */ S("div", {
|
|
159
160
|
className: "space-y-3",
|
|
160
|
-
children: [
|
|
161
|
+
children: [E.map((e) => /* @__PURE__ */ x(M, {
|
|
161
162
|
plan: e,
|
|
162
163
|
onView: () => a(`/plans/${e.id}`),
|
|
163
164
|
onSubscribe: () => a(`/checkout/${e.id}`)
|
|
164
|
-
}, e.id)), /* @__PURE__ */
|
|
165
|
+
}, e.id)), /* @__PURE__ */ x(A, { offer: l })]
|
|
165
166
|
})
|
|
166
167
|
]
|
|
167
168
|
});
|
|
168
|
-
},
|
|
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", {
|
|
169
|
+
}, k = ({ offer: e }) => /* @__PURE__ */ S("div", {
|
|
178
170
|
className: "border border-border-strong bg-[var(--color-accent-soft)] rounded-card shadow-elevation-1 p-5 flex flex-col h-full",
|
|
179
171
|
children: [
|
|
180
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ S("div", {
|
|
181
173
|
className: "mb-4",
|
|
182
|
-
children: [/* @__PURE__ */
|
|
174
|
+
children: [/* @__PURE__ */ S("div", {
|
|
183
175
|
className: "flex items-center gap-2",
|
|
184
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ x("h3", {
|
|
185
177
|
className: "text-lg font-semibold text-text-primary",
|
|
186
178
|
children: "Enterprise"
|
|
187
|
-
}), /* @__PURE__ */
|
|
179
|
+
}), /* @__PURE__ */ x("span", {
|
|
188
180
|
className: "text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link",
|
|
189
181
|
children: "Contact Us"
|
|
190
182
|
})]
|
|
191
|
-
}), /* @__PURE__ */
|
|
183
|
+
}), /* @__PURE__ */ x("span", {
|
|
192
184
|
className: "text-sm text-text-secondary",
|
|
193
185
|
children: "Custom contract"
|
|
194
186
|
})]
|
|
195
187
|
}),
|
|
196
|
-
/* @__PURE__ */
|
|
188
|
+
/* @__PURE__ */ x("div", {
|
|
197
189
|
className: "flex items-baseline gap-1 mb-4",
|
|
198
|
-
children: /* @__PURE__ */
|
|
190
|
+
children: /* @__PURE__ */ x("span", {
|
|
199
191
|
className: "text-2xl font-semibold text-text-primary",
|
|
200
192
|
children: "Custom pricing"
|
|
201
193
|
})
|
|
202
194
|
}),
|
|
203
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ x("ul", {
|
|
204
196
|
className: "space-y-2 mb-4 flex-1",
|
|
205
|
-
children:
|
|
197
|
+
children: e.highlights.map((e) => /* @__PURE__ */ S("li", {
|
|
206
198
|
className: "flex items-center gap-2 text-sm",
|
|
207
|
-
children: [/* @__PURE__ */
|
|
199
|
+
children: [/* @__PURE__ */ x("svg", {
|
|
208
200
|
className: "size-4 text-status-success-text shrink-0",
|
|
209
201
|
fill: "none",
|
|
210
202
|
viewBox: "0 0 24 24",
|
|
211
203
|
stroke: "currentColor",
|
|
212
|
-
children: /* @__PURE__ */
|
|
204
|
+
children: /* @__PURE__ */ x("path", {
|
|
213
205
|
strokeLinecap: "round",
|
|
214
206
|
strokeLinejoin: "round",
|
|
215
207
|
strokeWidth: 2,
|
|
216
208
|
d: "M5 13l4 4L19 7"
|
|
217
209
|
})
|
|
218
|
-
}), /* @__PURE__ */
|
|
210
|
+
}), /* @__PURE__ */ x("span", {
|
|
219
211
|
className: "text-text-primary",
|
|
220
212
|
children: e
|
|
221
213
|
})]
|
|
222
214
|
}, e))
|
|
223
215
|
}),
|
|
224
|
-
/* @__PURE__ */
|
|
216
|
+
/* @__PURE__ */ x("div", {
|
|
225
217
|
className: "space-y-2 pt-4 border-t border-border-subtle mt-auto",
|
|
226
|
-
children: /* @__PURE__ */
|
|
227
|
-
href:
|
|
218
|
+
children: /* @__PURE__ */ x("a", {
|
|
219
|
+
href: h(e),
|
|
228
220
|
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
221
|
children: "Contact Us"
|
|
230
222
|
})
|
|
231
223
|
})
|
|
232
224
|
]
|
|
233
|
-
}),
|
|
225
|
+
}), A = ({ offer: e }) => /* @__PURE__ */ S("div", {
|
|
234
226
|
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
227
|
children: [
|
|
236
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ x("div", {
|
|
237
229
|
className: "size-10 rounded-lg bg-bg-surface flex items-center justify-center shrink-0 shadow-elevation-1",
|
|
238
|
-
children: /* @__PURE__ */
|
|
230
|
+
children: /* @__PURE__ */ x("svg", {
|
|
239
231
|
className: "size-5 text-text-link",
|
|
240
232
|
fill: "none",
|
|
241
233
|
viewBox: "0 0 24 24",
|
|
242
234
|
stroke: "currentColor",
|
|
243
|
-
children: /* @__PURE__ */
|
|
235
|
+
children: /* @__PURE__ */ x("path", {
|
|
244
236
|
strokeLinecap: "round",
|
|
245
237
|
strokeLinejoin: "round",
|
|
246
238
|
strokeWidth: 2,
|
|
@@ -248,91 +240,91 @@ var E = () => {
|
|
|
248
240
|
})
|
|
249
241
|
})
|
|
250
242
|
}),
|
|
251
|
-
/* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ S("div", {
|
|
252
244
|
className: "flex-1 min-w-0",
|
|
253
|
-
children: [/* @__PURE__ */
|
|
245
|
+
children: [/* @__PURE__ */ S("div", {
|
|
254
246
|
className: "flex items-center gap-2",
|
|
255
|
-
children: [/* @__PURE__ */
|
|
247
|
+
children: [/* @__PURE__ */ x("h3", {
|
|
256
248
|
className: "font-semibold text-text-primary",
|
|
257
249
|
children: "Enterprise"
|
|
258
|
-
}), /* @__PURE__ */
|
|
250
|
+
}), /* @__PURE__ */ x("span", {
|
|
259
251
|
className: "text-xs font-medium px-2 py-0.5 rounded-full bg-bg-surface text-text-link shrink-0",
|
|
260
252
|
children: "Contact Us"
|
|
261
253
|
})]
|
|
262
|
-
}), /* @__PURE__ */
|
|
254
|
+
}), /* @__PURE__ */ x("p", {
|
|
263
255
|
className: "text-sm text-text-secondary",
|
|
264
|
-
children:
|
|
256
|
+
children: e.summary
|
|
265
257
|
})]
|
|
266
258
|
}),
|
|
267
|
-
/* @__PURE__ */
|
|
259
|
+
/* @__PURE__ */ S("div", {
|
|
268
260
|
className: "text-right",
|
|
269
|
-
children: [/* @__PURE__ */
|
|
261
|
+
children: [/* @__PURE__ */ x("div", {
|
|
270
262
|
className: "font-semibold text-text-primary",
|
|
271
263
|
children: "Custom pricing"
|
|
272
|
-
}), /* @__PURE__ */
|
|
264
|
+
}), /* @__PURE__ */ x("div", {
|
|
273
265
|
className: "text-sm text-text-secondary",
|
|
274
266
|
children: "contract-based"
|
|
275
267
|
})]
|
|
276
268
|
}),
|
|
277
|
-
/* @__PURE__ */
|
|
278
|
-
href:
|
|
269
|
+
/* @__PURE__ */ x("a", {
|
|
270
|
+
href: h(e),
|
|
279
271
|
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
272
|
children: "Contact Us"
|
|
281
273
|
})
|
|
282
274
|
]
|
|
283
|
-
}),
|
|
284
|
-
let s =
|
|
285
|
-
return /* @__PURE__ */
|
|
275
|
+
}), j = ({ plan: e, highlighted: t = !1, onView: n, onSubscribe: o }) => {
|
|
276
|
+
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);
|
|
277
|
+
return /* @__PURE__ */ S("div", {
|
|
286
278
|
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
279
|
children: [
|
|
288
|
-
t && /* @__PURE__ */
|
|
280
|
+
t && /* @__PURE__ */ S("span", {
|
|
289
281
|
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__ */
|
|
282
|
+
children: [/* @__PURE__ */ x(b, {
|
|
291
283
|
className: "size-3.5",
|
|
292
284
|
"aria-hidden": "true"
|
|
293
285
|
}), "Most popular"]
|
|
294
286
|
}),
|
|
295
|
-
/* @__PURE__ */
|
|
287
|
+
/* @__PURE__ */ S("div", {
|
|
296
288
|
className: "mb-4",
|
|
297
|
-
children: [/* @__PURE__ */
|
|
289
|
+
children: [/* @__PURE__ */ x("h3", {
|
|
298
290
|
className: "text-lg font-semibold text-text-primary",
|
|
299
291
|
children: e.name
|
|
300
|
-
}), /* @__PURE__ */
|
|
292
|
+
}), /* @__PURE__ */ x("span", {
|
|
301
293
|
className: "text-sm text-text-secondary",
|
|
302
294
|
children: a(e.duration)
|
|
303
295
|
})]
|
|
304
296
|
}),
|
|
305
|
-
/* @__PURE__ */
|
|
297
|
+
/* @__PURE__ */ S("div", {
|
|
306
298
|
className: "flex items-baseline gap-1.5 mb-5",
|
|
307
|
-
children: [/* @__PURE__ */
|
|
299
|
+
children: [/* @__PURE__ */ x("span", {
|
|
308
300
|
className: "text-4xl font-bold tabular-nums tracking-tight text-text-primary",
|
|
309
301
|
children: r(p, h)
|
|
310
|
-
}), /* @__PURE__ */
|
|
302
|
+
}), /* @__PURE__ */ S("span", {
|
|
311
303
|
className: "text-text-secondary text-sm",
|
|
312
304
|
children: ["/ ", i(e.duration)]
|
|
313
305
|
})]
|
|
314
306
|
}),
|
|
315
|
-
s.length > 0 && /* @__PURE__ */
|
|
307
|
+
s.length > 0 && /* @__PURE__ */ S("ul", {
|
|
316
308
|
className: "space-y-2.5 mb-5 flex-1",
|
|
317
|
-
children: [s.map((e) => /* @__PURE__ */
|
|
309
|
+
children: [s.map((e) => /* @__PURE__ */ S("li", {
|
|
318
310
|
className: "flex items-center justify-between gap-2 text-sm",
|
|
319
|
-
children: [/* @__PURE__ */
|
|
311
|
+
children: [/* @__PURE__ */ S("span", {
|
|
320
312
|
className: "flex items-center gap-2 min-w-0",
|
|
321
|
-
children: [/* @__PURE__ */
|
|
313
|
+
children: [/* @__PURE__ */ x("span", {
|
|
322
314
|
className: "flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)]",
|
|
323
|
-
children: /* @__PURE__ */ y
|
|
315
|
+
children: /* @__PURE__ */ x(y, {
|
|
324
316
|
className: "size-3 text-action-primary-bg",
|
|
325
317
|
"aria-hidden": "true"
|
|
326
318
|
})
|
|
327
|
-
}), /* @__PURE__ */
|
|
319
|
+
}), /* @__PURE__ */ x("span", {
|
|
328
320
|
className: "truncate text-text-primary",
|
|
329
321
|
children: f[e.name] ?? e.name
|
|
330
322
|
})]
|
|
331
|
-
}), /* @__PURE__ */
|
|
323
|
+
}), /* @__PURE__ */ x("span", {
|
|
332
324
|
className: "font-semibold text-text-primary tabular-nums shrink-0",
|
|
333
325
|
children: m(e.name, e.totalValue)
|
|
334
326
|
})]
|
|
335
|
-
}, e.quotaId)), c > 0 && /* @__PURE__ */
|
|
327
|
+
}, e.quotaId)), c > 0 && /* @__PURE__ */ S("li", {
|
|
336
328
|
className: "text-sm text-text-secondary pl-7",
|
|
337
329
|
children: [
|
|
338
330
|
"+",
|
|
@@ -341,17 +333,17 @@ var E = () => {
|
|
|
341
333
|
]
|
|
342
334
|
})]
|
|
343
335
|
}),
|
|
344
|
-
/* @__PURE__ */
|
|
336
|
+
/* @__PURE__ */ S("div", {
|
|
345
337
|
className: "space-y-2 pt-4 border-t border-border-subtle mt-auto",
|
|
346
|
-
children: [/* @__PURE__ */
|
|
338
|
+
children: [/* @__PURE__ */ S("button", {
|
|
347
339
|
type: "button",
|
|
348
340
|
onClick: o,
|
|
349
341
|
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__ */
|
|
342
|
+
children: ["Subscribe", /* @__PURE__ */ x(v, {
|
|
351
343
|
className: "size-4 transition-transform duration-200",
|
|
352
344
|
"aria-hidden": "true"
|
|
353
345
|
})]
|
|
354
|
-
}), /* @__PURE__ */
|
|
346
|
+
}), /* @__PURE__ */ x("button", {
|
|
355
347
|
type: "button",
|
|
356
348
|
onClick: n,
|
|
357
349
|
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 +352,19 @@ var E = () => {
|
|
|
360
352
|
})
|
|
361
353
|
]
|
|
362
354
|
});
|
|
363
|
-
},
|
|
364
|
-
let a =
|
|
365
|
-
return /* @__PURE__ */
|
|
355
|
+
}, M = ({ plan: e, onView: t, onSubscribe: n }) => {
|
|
356
|
+
let a = D(e.features || []), o = u();
|
|
357
|
+
return /* @__PURE__ */ S("div", {
|
|
366
358
|
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
359
|
children: [
|
|
368
|
-
/* @__PURE__ */
|
|
360
|
+
/* @__PURE__ */ x("div", {
|
|
369
361
|
className: "size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center shrink-0",
|
|
370
|
-
children: /* @__PURE__ */
|
|
362
|
+
children: /* @__PURE__ */ x("svg", {
|
|
371
363
|
className: "size-5 text-text-link",
|
|
372
364
|
fill: "none",
|
|
373
365
|
viewBox: "0 0 24 24",
|
|
374
366
|
stroke: "currentColor",
|
|
375
|
-
children: /* @__PURE__ */
|
|
367
|
+
children: /* @__PURE__ */ x("path", {
|
|
376
368
|
strokeLinecap: "round",
|
|
377
369
|
strokeLinejoin: "round",
|
|
378
370
|
strokeWidth: 2,
|
|
@@ -380,12 +372,12 @@ var E = () => {
|
|
|
380
372
|
})
|
|
381
373
|
})
|
|
382
374
|
}),
|
|
383
|
-
/* @__PURE__ */
|
|
375
|
+
/* @__PURE__ */ S("div", {
|
|
384
376
|
className: "flex-1 min-w-0",
|
|
385
|
-
children: [/* @__PURE__ */
|
|
377
|
+
children: [/* @__PURE__ */ x("h3", {
|
|
386
378
|
className: "font-semibold text-text-primary truncate",
|
|
387
379
|
children: e.name
|
|
388
|
-
}), /* @__PURE__ */
|
|
380
|
+
}), /* @__PURE__ */ S("p", {
|
|
389
381
|
className: "text-sm text-text-secondary",
|
|
390
382
|
children: [
|
|
391
383
|
a.length,
|
|
@@ -395,27 +387,27 @@ var E = () => {
|
|
|
395
387
|
]
|
|
396
388
|
})]
|
|
397
389
|
}),
|
|
398
|
-
/* @__PURE__ */
|
|
390
|
+
/* @__PURE__ */ S("div", {
|
|
399
391
|
className: "text-right",
|
|
400
|
-
children: [/* @__PURE__ */
|
|
392
|
+
children: [/* @__PURE__ */ x("div", {
|
|
401
393
|
className: "font-semibold text-text-primary",
|
|
402
394
|
children: (() => {
|
|
403
395
|
let { price: t, currency: n } = l(e.price, e.currency, e.currencyPrices, o);
|
|
404
396
|
return r(t, n);
|
|
405
397
|
})()
|
|
406
|
-
}), /* @__PURE__ */
|
|
398
|
+
}), /* @__PURE__ */ S("div", {
|
|
407
399
|
className: "text-sm text-text-secondary",
|
|
408
400
|
children: ["per ", i(e.duration)]
|
|
409
401
|
})]
|
|
410
402
|
}),
|
|
411
|
-
/* @__PURE__ */
|
|
403
|
+
/* @__PURE__ */ S("div", {
|
|
412
404
|
className: "flex items-center gap-2",
|
|
413
|
-
children: [/* @__PURE__ */
|
|
405
|
+
children: [/* @__PURE__ */ x("button", {
|
|
414
406
|
type: "button",
|
|
415
407
|
onClick: n,
|
|
416
408
|
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
409
|
children: "Subscribe"
|
|
418
|
-
}), /* @__PURE__ */
|
|
410
|
+
}), /* @__PURE__ */ x("button", {
|
|
419
411
|
type: "button",
|
|
420
412
|
onClick: t,
|
|
421
413
|
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 +418,6 @@ var E = () => {
|
|
|
426
418
|
});
|
|
427
419
|
};
|
|
428
420
|
//#endregion
|
|
429
|
-
export {
|
|
421
|
+
export { O as PlansBrowsePage };
|
|
430
422
|
|
|
431
423
|
//# 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":";;;;;;;;;;;;;;;;;;;AAwCA,SAAS,EAAc,GAA0C;CAC/D,IAAM,oBAAW,IAAI,IAA2B;CAChD,KAAK,IAAM,KAAW,GAAU;EAC9B,IAAI,CAAC,EAAQ,OAAO;EACpB,IAAM,IAAU,EAAQ,MAAM,IACxB,IAAa,EAAuB,CAAO,GAC3C,IAAW,EAAS,IAAI,CAAO;EACrC,AAAI,IACF,EAAS,cAAc,IAEvB,EAAS,IAAI,GAAS;GACpB;GACA,MAAM,EAAQ,MAAM,QAAQ;GAC5B,YAAY;EACd,CAAC;CAEL;CACA,OAAO,MAAM,KAAK,EAAS,OAAO,CAAC;AACrC;AAGA,SAAS,EAAkB,GAA0C;CACnE,IAAM,IAAM,EAAc,CAAQ,GAC5B,IAAS,IAAI,IAAI,EAAI,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD,OAAO,EAAqB,SAAS,MAAS;EAC5C,IAAM,IAAI,EAAO,IAAI,CAAI;EACzB,OAAO,IAAI,CAAC,CAAC,IAAI,CAAC;CACpB,CAAC;AACH;AAEA,IAAa,UAA4B;CACvC,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GACM,IAAa,EAAmB,GAChC,EAAE,iBAAc,EAAW,GAC3B,EAAE,UAAO,cAAW,UAAO,eAAY,EAAS;EACpD,aAAa;EACb,WAAW,KAAa,KAAA;CAC1B,CAAC,GAEK,CAAC,GAAU,KAAe,EAAmB,MAAM,GACnD,CAAC,GAAiB,KAAsB,EAAuB,EAAa,OAAO,GAGnF,IAAgB,EAAM,QACzB,MAAS,EAAK,YAAY,EAAK,kBAAkB,MAAS,EAAK,aAAa,CAC/E,GAIM,IAAmB,EAAc,UAAU,IAAI,IAAI;CAuCzD,OArCI,KAAa,EAAM,WAAW,IAE9B,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAK,WAAU,mDAAoD,CAAA,GACnE,kBAAC,OAAD;GAAK,WAAU;aACZ;IAAC;IAAG;IAAG;GAAC,EAAE,KAAK,MACd,kBAAC,OAAD;IAEE,WAAU;cAFZ;KAIE,kBAAC,OAAD,EAAK,WAAU,8CAA+C,CAAA;KAC9D,kBAAC,OAAD,EAAK,WAAU,8CAA+C,CAAA;KAC9D,kBAAC,OAAD;MAAK,WAAU;gBACZ;OAAC;OAAG;OAAG;MAAC,EAAE,KAAK,MACd,kBAAC,OAAD,EAAa,WAAU,gDAAiD,GAA9D,CAA8D,CACzE;KACE,CAAA;IACF;MAVE,CAUF,CACN;EACE,CAAA,CACF;MAIL,KAAS,EAAc,CAAK,IAE5B,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,OAAO,EAAG,mCAAmC,oBAAoB;GACjE,SAAS,EAAG,mCAAmC,uBAAuB;GACtE,eAAe,EAAQ;GACvB,WAAA;EACD,CAAA;CACE,CAAA,IAKP,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,kCAAkC,OAAO;IAC3C,CAAA;IACJ,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,qCAAqC,oCAAoC;IAC5E,CAAA;IACH,kBAAC,GAAD;KAAa,WAAU;eACpB,EACC,+BACA,+NACF;IACW,CAAA;GACV,EAAA,CAAA;GAGL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACX,CAAC,EAAa,SAAS,EAAa,MAAM,EAAY,KAAK,MAC3D,kBAAC,UAAD;MACE,MAAK;MAEL,eAAe,EAAmB,CAAQ;MAC1C,WAAW,oEACT,MAAoB,IAChB,8CACA;gBAGL,MAAa,EAAa,UAAU,YAAY;KAC3C,GATD,CASC,CACT;IACE,CAAA,GAEL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAY,MAAM;MACjC,WAAW,0CACT,MAAa,SACT,8CACA;MAEN,OAAM;gBAEN,kBAAC,OAAD;OAAK,WAAU;OAAS,MAAK;OAAO,SAAQ;OAAY,QAAO;iBAC7D,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;OACH,CAAA;MACE,CAAA;KACC,CAAA,GACR,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAY,MAAM;MACjC,WAAW,0CACT,MAAa,SACT,8CACA;MAEN,OAAM;gBAEN,kBAAC,OAAD;OAAK,WAAU;OAAS,MAAK;OAAO,SAAQ;OAAY,QAAO;iBAC7D,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;OACH,CAAA;MACE,CAAA;KACC,CAAA,CACL;MACF;;GAGJ,EAAc,WAAW,IACxB,kBAAC,GAAD;IACE,cAAa;IACb,OAAO,EAAG,kCAAkC,oBAAoB;IAChE,aAAa,EAAG,gCAAgC,iCAAiC;GAClF,CAAA,IACC,MAAa,SACf,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAc,KAAK,GAAM,MACxB,kBAAC,GAAD;KAEQ;KACN,aAAa,MAAQ;KACrB,cAAc,EAAW,UAAU,EAAK,IAAI;KAC5C,mBAAmB,EAAW,aAAa,EAAK,IAAI;IACrD,GALM,EAAK,EAKX,CACF,GACD,kBAAC,GAAD,CAAiB,CAAA,CACd;QAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAc,KAAK,MAClB,kBAAC,GAAD;KAEQ;KACN,cAAc,EAAW,UAAU,EAAK,IAAI;KAC5C,mBAAmB,EAAW,aAAa,EAAK,IAAI;IACrD,GAJM,EAAK,EAIX,CACF,GACD,kBAAC,GAAD,CAAgB,CAAA,CACb;;EAEJ;;AAET,GAMM,IAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GAEM,UACJ,kBAAC,OAAD;CAAK,WAAU;WAAf;EACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA0C;IAAc,CAAA,GACtE,kBAAC,QAAD;KAAM,WAAU;eAA4E;IAEtF,CAAA,CACH;OACL,kBAAC,QAAD;IAAM,WAAU;cAA8B;GAAqB,CAAA,CAChE;;EAEL,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,QAAD;IAAM,WAAU;cAA2C;GAAoB,CAAA;EAC5E,CAAA;EAEL,kBAAC,MAAD;GAAI,WAAU;aACX,EAAsB,KAAK,MAC1B,kBAAC,MAAD;IAAe,WAAU;cAAzB,CACE,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,SAAQ;KACR,QAAO;eAEP,kBAAC,QAAD;MAAM,eAAc;MAAQ,gBAAe;MAAQ,aAAa;MAAG,GAAE;KAAkB,CAAA;IACpF,CAAA,GACL,kBAAC,QAAD;KAAM,WAAU;eAAqB;IAAW,CAAA,CAC9C;MAVK,CAUL,CACL;EACC,CAAA;EAEJ,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,KAAD;IACE,MAAK;IACL,WAAU;cACX;GAEE,CAAA;EACA,CAAA;CACF;IAGD,UACJ,kBAAC,OAAD;CAAK,WAAU;WAAf;EACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;IAAwB,MAAK;IAAO,SAAQ;IAAY,QAAO;cAC5E,kBAAC,QAAD;KACE,eAAc;KACd,gBAAe;KACf,aAAa;KACb,GAAE;IACH,CAAA;GACE,CAAA;EACF,CAAA;EAEL,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAkC;IAAc,CAAA,GAC9D,kBAAC,QAAD;KAAM,WAAU;eAAqF;IAE/F,CAAA,CACH;OACL,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAA0C,CAAA,CAClF;;EAEL,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAkC;GAAmB,CAAA,GACpE,kBAAC,OAAD;IAAK,WAAU;cAA8B;GAAmB,CAAA,CAC7D;;EAEL,kBAAC,KAAD;GACE,MAAK;GACL,WAAU;aACX;EAEE,CAAA;CACA;IAOD,KAKA,EAAE,SAAM,iBAAc,IAAO,WAAQ,qBAAkB;CAC3D,IAAM,IAAiB,EAAkB,EAAK,YAAY,CAAC,CAAC,GAEtD,IADY,EAAc,EAAK,YAAY,CAAC,CAC/B,EAAU,SAAS,EAAe,QAC/C,IAAkB,EAA0B,GAC5C,EAAE,UAAO,gBAAa,EAC1B,EAAK,OACL,EAAK,UACL,EAAK,gBACL,CACF;CAEA,OACE,kBAAC,OAAD;EACE,WAAW,kHACT,IACI,kEACA;YAJR;GAOG,KACC,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,GAAD;KAAU,WAAU;KAAW,eAAY;IAAQ,CAAA,GAAC,cAEhD;;GAGR,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA2C,EAAK;IAAS,CAAA,GACvE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAwB,EAAK,QAAQ;IAClC,CAAA,CACH;;GAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAe,GAAO,CAAQ;IAC3B,CAAA,GACN,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA8C,MAAG,EAAmB,EAAK,QAAQ,CAAQ;MACtF;;GAEJ,EAAe,SAAS,KACvB,kBAAC,MAAD;IAAI,WAAU;cAAd,CACG,EAAe,KAAK,MACnB,kBAAC,MAAD;KAAwB,WAAU;eAAlC,CACE,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,QAAD;OAAM,WAAU;iBACd,kBAAC,GAAD;QAAO,WAAU;QAAgC,eAAY;OAAQ,CAAA;MACjE,CAAA,GACN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAsB,EAAM,SAAS,EAAM;MACxC,CAAA,CACF;SACN,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAyB,EAAM,MAAM,EAAM,UAAU;KAClD,CAAA,CACJ;OAZK,EAAM,OAYX,CACL,GACA,IAAa,KACZ,kBAAC,MAAD;KAAI,WAAU;eAAd;MAAiD;MAAE;MAAW;KAAuB;MAErF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAIC,aAEC,kBAAC,GAAD;MAAY,WAAU;MAA2C,eAAY;KAAQ,CAAA,CAC/E;QACR,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;IAEO,CAAA,CACL;;EACF;;AAET,GAMM,KAAkF,EACtF,SACA,WACA,qBACI;CACJ,IAAM,IAAiB,EAAkB,EAAK,YAAY,CAAC,CAAC,GACtD,IAAkB,EAA0B;CAElD,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,SAAQ;KACR,QAAO;eAEP,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;KACH,CAAA;IACE,CAAA;GACF,CAAA;GAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA4C,EAAK;IAAS,CAAA,GACxE,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAe;MAAO;MAAa,EAAe,WAAW,IAAU,KAAN;MAAS;KAC1E;MACA;;GAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;sBACL;MACN,IAAM,EAAE,UAAO,gBAAa,EAC1B,EAAK,OACL,EAAK,UACL,EAAK,gBACL,CACF;MACA,OAAO,EAAe,GAAO,CAAQ;KACvC,GAAG;IACA,CAAA,GACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CAA6C,QAAK,EAAmB,EAAK,QAAQ,CAAO;MACtF;;GAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;IAEO,CAAA,GACR,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eACX;IAEO,CAAA,CACL;;EACF;;AAET"}
|
|
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 } = 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 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 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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"}
|