@burdenoff/microfe-billing 2026.1026.2 → 2026.1027.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 +82 -12
- package/dist/billing/modules/plans/constants/enterprise.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +24 -23
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/utils/currencyPriceOverrides.js +9 -0
- package/dist/billing/modules/plans/utils/currencyPriceOverrides.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
//#region src/billing/modules/plans/constants/enterprise.ts
|
|
2
2
|
var e = {
|
|
3
3
|
highlights: [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"Audit log with custom retention",
|
|
9
|
-
"Priority onboarding & migration"
|
|
4
|
+
"Pricing tailored to your organization",
|
|
5
|
+
"Volume and multi-year terms",
|
|
6
|
+
"Contract and invoicing that fit your procurement process",
|
|
7
|
+
"Talk to our team about your requirements"
|
|
10
8
|
],
|
|
11
|
-
summary: "
|
|
9
|
+
summary: "Custom contract · Volume pricing",
|
|
12
10
|
contactEmail: "contact@burdenoff.com"
|
|
13
11
|
}, t = {
|
|
12
|
+
bigconsole: {
|
|
13
|
+
highlights: [
|
|
14
|
+
"Everything in Professional",
|
|
15
|
+
"Dedicated support",
|
|
16
|
+
"SLA guarantee",
|
|
17
|
+
"Unlimited storage",
|
|
18
|
+
"Advanced security",
|
|
19
|
+
"Built-in integrations",
|
|
20
|
+
"SSO & SAML",
|
|
21
|
+
"Audit logs",
|
|
22
|
+
"SCIM user provisioning",
|
|
23
|
+
"Dedicated account manager"
|
|
24
|
+
],
|
|
25
|
+
summary: "SSO/SCIM · SLA guarantee · Dedicated account manager",
|
|
26
|
+
contactEmail: "contact@bigconsole.com"
|
|
27
|
+
},
|
|
28
|
+
botmanor: {
|
|
29
|
+
highlights: [
|
|
30
|
+
"Everything in Pro",
|
|
31
|
+
"Unlimited scale",
|
|
32
|
+
"Multi-agent orchestration",
|
|
33
|
+
"SSO & SAML",
|
|
34
|
+
"Advanced RBAC & audit exports",
|
|
35
|
+
"Your own MCP servers & LLM endpoints",
|
|
36
|
+
"Configurable quotas",
|
|
37
|
+
"Custom SLA & dedicated CSM"
|
|
38
|
+
],
|
|
39
|
+
summary: "Multi-agent orchestration · SSO · Custom SLA",
|
|
40
|
+
contactEmail: "contact@botmanor.com"
|
|
41
|
+
},
|
|
14
42
|
fluidgrids: {
|
|
15
43
|
highlights: [
|
|
16
44
|
"Custom workflow and run volumes",
|
|
@@ -25,6 +53,46 @@ var e = {
|
|
|
25
53
|
summary: "Custom volumes · SSO/SCIM · OEM builder SDK",
|
|
26
54
|
contactEmail: "contact@fluidgrids.com"
|
|
27
55
|
},
|
|
56
|
+
hookmovies: {
|
|
57
|
+
highlights: [
|
|
58
|
+
"Everything in higher consumer tiers",
|
|
59
|
+
"Studio / channel production-ops",
|
|
60
|
+
"FAST channels & live events",
|
|
61
|
+
"Advanced audience & revenue analytics",
|
|
62
|
+
"Rights & distribution manager",
|
|
63
|
+
"Multi-seat with org-wide RBAC",
|
|
64
|
+
"API, embeddable player & webhooks",
|
|
65
|
+
"SSO & configurable rev-share"
|
|
66
|
+
],
|
|
67
|
+
summary: "Studio production-ops · FAST channels · SSO",
|
|
68
|
+
contactEmail: "contact@hookmovies.com"
|
|
69
|
+
},
|
|
70
|
+
labsofscience: {
|
|
71
|
+
highlights: [
|
|
72
|
+
"Contracted per-seat pricing that declines with volume",
|
|
73
|
+
"Academic and commercial rate cards",
|
|
74
|
+
"Unlimited experiments, runs, notebook entries and datasets",
|
|
75
|
+
"Annual contract, minimum $25,000/yr",
|
|
76
|
+
"Dedicated tenant or self-hosting (priced separately)",
|
|
77
|
+
"Data residency and customer-managed keys (priced separately)"
|
|
78
|
+
],
|
|
79
|
+
summary: "Contracted seats · Academic & commercial rates · Deployment add-ons",
|
|
80
|
+
contactEmail: "contact@labsofscience.com"
|
|
81
|
+
},
|
|
82
|
+
subscriberbot: {
|
|
83
|
+
highlights: [
|
|
84
|
+
"Everything in Pro",
|
|
85
|
+
"Enterprise Control Tower",
|
|
86
|
+
"License & usage analytics",
|
|
87
|
+
"Contract Intelligence",
|
|
88
|
+
"Risk & shadow-IT detection",
|
|
89
|
+
"SSO, SCIM & advanced RBAC",
|
|
90
|
+
"Immutable audit log",
|
|
91
|
+
"Dedicated support & SLA"
|
|
92
|
+
],
|
|
93
|
+
summary: "Control Tower · SSO/SCIM · Dedicated support & SLA",
|
|
94
|
+
contactEmail: "contact@subscriberbot.com"
|
|
95
|
+
},
|
|
28
96
|
vibecontrols: {
|
|
29
97
|
highlights: [
|
|
30
98
|
"Everything in Pro",
|
|
@@ -37,14 +105,16 @@ var e = {
|
|
|
37
105
|
summary: "Dedicated tenant · SSO/SCIM · Custom SLA",
|
|
38
106
|
contactEmail: "contact@vibecontrols.com"
|
|
39
107
|
}
|
|
40
|
-
};
|
|
41
|
-
function
|
|
42
|
-
|
|
108
|
+
}, n = { botlit: "botmanor" };
|
|
109
|
+
function r(r) {
|
|
110
|
+
if (!r) return e;
|
|
111
|
+
let i = r.trim().toLowerCase(), a = Object.hasOwn(n, i) ? n[i] : i;
|
|
112
|
+
return Object.hasOwn(t, a) ? t[a] : e;
|
|
43
113
|
}
|
|
44
|
-
function
|
|
114
|
+
function i(e) {
|
|
45
115
|
return `mailto:${e.contactEmail}?subject=Enterprise%20Plan%20Inquiry`;
|
|
46
116
|
}
|
|
47
117
|
//#endregion
|
|
48
|
-
export {
|
|
118
|
+
export { i as enterpriseMailto, r as getEnterpriseOffer };
|
|
49
119
|
|
|
50
120
|
//# sourceMappingURL=enterprise.js.map
|
|
@@ -1 +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 '
|
|
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\n/**\n * Fallback for products without curated copy. It deliberately makes no\n * capability claims (no SSO, SLA, residency, dedicated infra, ...): those\n * differ per product, and an unverified claim on a page where customers pay\n * is misleading. It only describes the commercial shape of a sales contract.\n */\nconst DEFAULT_ENTERPRISE_OFFER: EnterpriseOffer = {\n highlights: [\n 'Pricing tailored to your organization',\n 'Volume and multi-year terms',\n 'Contract and invoicing that fit your procurement process',\n 'Talk to our team about your requirements',\n ],\n summary: 'Custom contract · Volume 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 // bigconsole-website src/config/product.ts (tier \"enterprise\")\n bigconsole: {\n highlights: [\n 'Everything in Professional',\n 'Dedicated support',\n 'SLA guarantee',\n 'Unlimited storage',\n 'Advanced security',\n 'Built-in integrations',\n 'SSO & SAML',\n 'Audit logs',\n 'SCIM user provisioning',\n 'Dedicated account manager',\n ],\n summary: 'SSO/SCIM · SLA guarantee · Dedicated account manager',\n contactEmail: 'contact@bigconsole.com',\n },\n // botmanor-website src/config/product.ts (tier \"enterprise\")\n botmanor: {\n highlights: [\n 'Everything in Pro',\n 'Unlimited scale',\n 'Multi-agent orchestration',\n 'SSO & SAML',\n 'Advanced RBAC & audit exports',\n 'Your own MCP servers & LLM endpoints',\n 'Configurable quotas',\n 'Custom SLA & dedicated CSM',\n ],\n summary: 'Multi-agent orchestration · SSO · Custom SLA',\n contactEmail: 'contact@botmanor.com',\n },\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 // hookmovies-website src/config/product.ts (tier \"enterprise\")\n hookmovies: {\n highlights: [\n 'Everything in higher consumer tiers',\n 'Studio / channel production-ops',\n 'FAST channels & live events',\n 'Advanced audience & revenue analytics',\n 'Rights & distribution manager',\n 'Multi-seat with org-wide RBAC',\n 'API, embeddable player & webhooks',\n 'SSO & configurable rev-share',\n ],\n summary: 'Studio production-ops · FAST channels · SSO',\n contactEmail: 'contact@hookmovies.com',\n },\n // labsofscience-specs business/pricing/internal/01-tier-plans.md §7.1 (contracted\n // Enterprise tier). Deployment and compliance obligations are separately priced\n // line items there, so they are labelled as add-ons rather than included.\n labsofscience: {\n highlights: [\n 'Contracted per-seat pricing that declines with volume',\n 'Academic and commercial rate cards',\n 'Unlimited experiments, runs, notebook entries and datasets',\n 'Annual contract, minimum $25,000/yr',\n 'Dedicated tenant or self-hosting (priced separately)',\n 'Data residency and customer-managed keys (priced separately)',\n ],\n summary: 'Contracted seats · Academic & commercial rates · Deployment add-ons',\n contactEmail: 'contact@labsofscience.com',\n },\n // subscriberbot-website src/config/product.ts (tier \"enterprise\")\n subscriberbot: {\n highlights: [\n 'Everything in Pro',\n 'Enterprise Control Tower',\n 'License & usage analytics',\n 'Contract Intelligence',\n 'Risk & shadow-IT detection',\n 'SSO, SCIM & advanced RBAC',\n 'Immutable audit log',\n 'Dedicated support & SLA',\n ],\n summary: 'Control Tower · SSO/SCIM · Dedicated support & SLA',\n contactEmail: 'contact@subscriberbot.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\n/** Legacy product ids still accepted on input (never emitted). */\nconst PRODUCT_ID_ALIASES: Readonly<Record<string, string>> = {\n botlit: 'botmanor',\n};\n\nexport function getEnterpriseOffer(productId: string | null | undefined): EnterpriseOffer {\n if (!productId) return DEFAULT_ENTERPRISE_OFFER;\n const raw = productId.trim().toLowerCase();\n // Own-property checks so ids such as \"constructor\" never resolve to Object.prototype members.\n const key = Object.hasOwn(PRODUCT_ID_ALIASES, raw) ? PRODUCT_ID_ALIASES[raw] : raw;\n return Object.hasOwn(ENTERPRISE_OFFERS, key) ? ENTERPRISE_OFFERS[key] : DEFAULT_ENTERPRISE_OFFER;\n}\n\nexport function enterpriseMailto(offer: EnterpriseOffer): string {\n return `mailto:${offer.contactEmail}?subject=${encodeURIComponent('Enterprise Plan Inquiry')}`;\n}\n"],"mappings":";AAyBA,IAAM,IAA4C;CAChD,YAAY;EACV;EACA;EACA;EACA;CACF;CACA,SAAS;CACT,cAAc;AAChB,GAGM,IAA+D;CAEnE,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,UAAU;EACR,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,YAAY;EACV,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAIA,eAAe;EACb,YAAY;GACV;GACA;GACA;GACA;GACA;GACA;EACF;EACA,SAAS;EACT,cAAc;CAChB;CAEA,eAAe;EACb,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,GAGM,IAAuD,EAC3D,QAAQ,WACV;AAEA,SAAgB,EAAmB,GAAuD;CACxF,IAAI,CAAC,GAAW,OAAO;CACvB,IAAM,IAAM,EAAU,KAAK,EAAE,YAAY,GAEnC,IAAM,OAAO,OAAO,GAAoB,CAAG,IAAI,EAAmB,KAAO;CAC/E,OAAO,OAAO,OAAO,GAAmB,CAAG,IAAI,EAAkB,KAAO;AAC1E;AAEA,SAAgB,EAAiB,GAAgC;CAC/D,OAAO,UAAU,EAAM,aAAa;AACtC"}
|
|
@@ -8,10 +8,11 @@ import { isServerError as a } from "../../../shared/utils/error.js";
|
|
|
8
8
|
import { getPlanFeatureQuantity as o } from "../../../shared/utils/planFeatureQuantity.js";
|
|
9
9
|
import "../../../shared/utils/index.js";
|
|
10
10
|
import { useBillingPermissions as s } from "../../../hooks/useBillingPermissions.js";
|
|
11
|
-
import { useBillingNavigate as
|
|
12
|
-
import { usePlan as
|
|
13
|
-
import { useQuotas as
|
|
11
|
+
import { useBillingNavigate as ee } from "../../../hooks/useBillingNavigate.js";
|
|
12
|
+
import { usePlan as c, usePlanMutations as l } from "../hooks/usePlans.js";
|
|
13
|
+
import { useQuotas as u } from "../hooks/useQuotas.js";
|
|
14
14
|
import "../hooks/index.js";
|
|
15
|
+
import { getChangedCurrencyPriceOverrides as d } from "../utils/currencyPriceOverrides.js";
|
|
15
16
|
import { useEffect as f, useState as p } from "react";
|
|
16
17
|
import { useParams as m } from "react-router";
|
|
17
18
|
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
@@ -22,15 +23,15 @@ var b = () => {
|
|
|
22
23
|
let { t: b } = v(), x = (e, t) => {
|
|
23
24
|
let n = b(e);
|
|
24
25
|
return n === e ? t : n;
|
|
25
|
-
}, { planId: S } = m(), C =
|
|
26
|
+
}, { planId: S } = m(), C = ee(), w = s(), { plan: T, isLoading: te, error: E, refetch: D } = c(S), { quotas: O, isLoading: k } = u({ onlyActive: !0 }), { updatePlan: A, isUpdating: j } = l(), [M, N] = p(""), [P, F] = p(""), [I, L] = p("USD"), [R, z] = p(i.MONTHLY), [B, V] = p(!0), [H, U] = p(!0), [W, G] = p([]), [K, q] = p([]), [J, Y] = p([]), [X, Z] = p(null), [ne, Q] = p(!1);
|
|
26
27
|
if (f(() => {
|
|
27
28
|
if (T) {
|
|
28
29
|
if (N(T.name ?? ""), F(String(T.price ?? "")), L(T.currency ?? "USD"), z(T.duration ?? i.MONTHLY), V(T.isActive ?? !0), U(T.isPurchasable ?? !0), T.currencyPrices && typeof T.currencyPrices == "object") {
|
|
29
|
-
let e = T.currencyPrices
|
|
30
|
-
q(Object.entries(e).filter(([, e]) => typeof e == "number").map(([e, t]) => ({
|
|
30
|
+
let e = T.currencyPrices, t = Object.entries(e).filter(([, e]) => typeof e == "number").map(([e, t]) => ({
|
|
31
31
|
currency: e,
|
|
32
32
|
amount: t
|
|
33
|
-
}))
|
|
33
|
+
}));
|
|
34
|
+
q(t), Y(t);
|
|
34
35
|
}
|
|
35
36
|
if (T.features && T.features.length > 0) {
|
|
36
37
|
let e = [], t = /* @__PURE__ */ new Map();
|
|
@@ -46,7 +47,7 @@ var b = () => {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
}, [T]), !w.canManagePlans) return /* @__PURE__ */ g(e, { message: x("billing.plans.noEditPermission", "You don't have permission to edit plans.") });
|
|
49
|
-
if (
|
|
50
|
+
if (te || k) return /* @__PURE__ */ _("div", {
|
|
50
51
|
className: "space-y-6 p-6",
|
|
51
52
|
children: [/* @__PURE__ */ g("div", { className: "h-8 w-48 bg-bg-sunken animate-pulse rounded" }), /* @__PURE__ */ g("div", {
|
|
52
53
|
className: "max-w-2xl space-y-4",
|
|
@@ -83,12 +84,12 @@ var b = () => {
|
|
|
83
84
|
})]
|
|
84
85
|
})
|
|
85
86
|
});
|
|
86
|
-
let
|
|
87
|
+
let re = () => {
|
|
87
88
|
G([{
|
|
88
89
|
quotaId: "",
|
|
89
90
|
quantity: 1
|
|
90
91
|
}, ...W]);
|
|
91
|
-
},
|
|
92
|
+
}, ie = (e) => {
|
|
92
93
|
G(W.filter((t, n) => n !== e));
|
|
93
94
|
}, $ = (e, t, n) => {
|
|
94
95
|
let r = [...W];
|
|
@@ -104,20 +105,20 @@ var b = () => {
|
|
|
104
105
|
/* @__PURE__ */ g(y, { children: x("billing.plans.editPurpose", "Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.") }),
|
|
105
106
|
/* @__PURE__ */ _("form", {
|
|
106
107
|
onSubmit: async (e) => {
|
|
107
|
-
if (e.preventDefault(),
|
|
108
|
-
|
|
108
|
+
if (e.preventDefault(), Z(null), Q(!1), !M.trim()) {
|
|
109
|
+
Z("Plan name is required");
|
|
109
110
|
return;
|
|
110
111
|
}
|
|
111
112
|
if (!P || isNaN(Number(P)) || Number(P) < 0) {
|
|
112
|
-
|
|
113
|
+
Z("Valid price is required");
|
|
113
114
|
return;
|
|
114
115
|
}
|
|
115
116
|
if (W.length === 0) {
|
|
116
|
-
|
|
117
|
+
Z("At least one quota is required");
|
|
117
118
|
return;
|
|
118
119
|
}
|
|
119
120
|
if (W.filter((e) => e.quotaId === "").length > 0) {
|
|
120
|
-
|
|
121
|
+
Z("Please select a quota for all allocations");
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
123
124
|
let t = {
|
|
@@ -132,24 +133,24 @@ var b = () => {
|
|
|
132
133
|
quotaId: e.quotaId,
|
|
133
134
|
quantity: e.quantity
|
|
134
135
|
})),
|
|
135
|
-
...K.length > 0 ? { currencyPriceOverrides: K } : {}
|
|
136
|
+
...d(J, K).length > 0 ? { currencyPriceOverrides: d(J, K) } : {}
|
|
136
137
|
};
|
|
137
138
|
try {
|
|
138
|
-
await A(t),
|
|
139
|
+
await A(t), Q(!0), setTimeout(() => C(`/plans/${S}`), 1500);
|
|
139
140
|
} catch (e) {
|
|
140
|
-
|
|
141
|
+
Z(e instanceof Error ? e.message : "Failed to update plan");
|
|
141
142
|
}
|
|
142
143
|
},
|
|
143
144
|
className: "space-y-6",
|
|
144
145
|
children: [
|
|
145
|
-
|
|
146
|
+
X && /* @__PURE__ */ g("div", {
|
|
146
147
|
className: "p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg",
|
|
147
148
|
children: /* @__PURE__ */ g("p", {
|
|
148
149
|
className: "text-sm text-status-error-text",
|
|
149
|
-
children:
|
|
150
|
+
children: X
|
|
150
151
|
})
|
|
151
152
|
}),
|
|
152
|
-
|
|
153
|
+
ne && /* @__PURE__ */ g("div", {
|
|
153
154
|
className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
|
|
154
155
|
children: /* @__PURE__ */ g("p", {
|
|
155
156
|
className: "text-sm text-status-success-text",
|
|
@@ -302,7 +303,7 @@ var b = () => {
|
|
|
302
303
|
children: x("billing.plans.quotaAllocations", "Quota Allocations")
|
|
303
304
|
}), /* @__PURE__ */ g("button", {
|
|
304
305
|
type: "button",
|
|
305
|
-
onClick:
|
|
306
|
+
onClick: re,
|
|
306
307
|
className: "px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors",
|
|
307
308
|
children: "+ Add Quota"
|
|
308
309
|
})]
|
|
@@ -331,7 +332,7 @@ var b = () => {
|
|
|
331
332
|
}, e.id))]
|
|
332
333
|
}), /* @__PURE__ */ g("button", {
|
|
333
334
|
type: "button",
|
|
334
|
-
onClick: () =>
|
|
335
|
+
onClick: () => ie(t),
|
|
335
336
|
className: "p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors",
|
|
336
337
|
title: "Remove quota",
|
|
337
338
|
children: /* @__PURE__ */ g("svg", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanEditPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanEditPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Edit Page\n * Form to edit an existing plan\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { getPlanFeatureQuantity, isServerError } from '../../../shared/utils';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdatePlanInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const PlanEditPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { plan, isLoading: isPlanLoading, error: planError, refetch } = usePlan(planId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updatePlan, isUpdating } = usePlanMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [isActive, setIsActive] = useState(true);\n const [isPurchasable, setIsPurchasable] = useState(true);\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Populate form when plan data loads\n useEffect(() => {\n if (plan) {\n setName(plan.name ?? '');\n setPrice(String(plan.price ?? ''));\n setCurrency(plan.currency ?? 'USD');\n setDuration(plan.duration ?? PlanDuration.MONTHLY);\n setIsActive(plan.isActive ?? true);\n setIsPurchasable(plan.isPurchasable ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (plan.currencyPrices && typeof plan.currencyPrices === 'object') {\n const existing = plan.currencyPrices as Record<string, number>;\n setCurrencyPriceOverrides(\n Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }))\n );\n }\n // Extract quota allocations from features\n if (plan.features && plan.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of plan.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n // SubscriptionFeature now stores per-plan override in context.quantity.\n // Fall back to quota.limits.value for legacy autoseeded plans.\n const value = getPlanFeatureQuantity(feature) || 1;\n quotaMap.set(quotaId, value);\n }\n }\n\n for (const [quotaId, quantity] of quotaMap) {\n allocations.push({ quotaId, quantity });\n }\n\n setQuotaAllocations(allocations);\n }\n }\n }, [plan]);\n\n // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr('billing.plans.noEditPermission', \"You don't have permission to edit plans.\")}\n />\n );\n }\n\n // Loading state\n if (isPlanLoading || isQuotasLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (planError && isServerError(planError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableLoadPlan',\n 'Unable to load plan. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!plan) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Plan name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: UpdatePlanInput = {\n id: planId!,\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n isActive,\n isPurchasable,\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await updatePlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/plans/${planId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Plan: ${plan.name}`}\n description={tr(\n 'billing.plans.editPlanDesc',\n 'Update the plan details and quota allocations'\n )}\n />\n\n <PagePurpose>\n {tr(\n 'billing.plans.editPurpose',\n \"Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.\"\n )}\n </PagePurpose>\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.plans.updateSuccess', 'Plan updated successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.plans.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.plans.planNameLabel', 'Plan Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"49.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.planIsActive', 'Plan is active')}\n </label>\n </div>\n\n {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-text-muted mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n isPurchasable ? 'bg-action-primary-bg' : 'bg-text-muted/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.plans.quotaAllocations', 'Quota Allocations')}\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.plans.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitType = limits?.type ?? 'unknown';\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n allocation.quotaId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.plans.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\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 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.plans.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 px-3 py-1.5 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-center\"\n />\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isUpdating}\n className=\"px-6 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isUpdating\n ? tr('billing.plans.saving', 'Saving...')\n : tr('billing.plans.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/plans/${planId}`)}\n className=\"px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanEditPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanEditPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Edit Page\n * Form to edit an existing plan\n */\n\nimport { useState, useEffect, type FC, type FormEvent } from 'react';\nimport { useParams } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlan, usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n AccessDenied,\n CurrencyPriceOverridesEditor,\n PageHeader,\n ServerError,\n} from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { getPlanFeatureQuantity, isServerError } from '../../../shared/utils';\nimport {\n PlanDuration,\n type CurrencyPriceOverrideInput,\n type UpdatePlanInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { getChangedCurrencyPriceOverrides } from '../utils/currencyPriceOverrides';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const PlanEditPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { planId } = useParams<{ planId: string }>();\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const { plan, isLoading: isPlanLoading, error: planError, refetch } = usePlan(planId);\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { updatePlan, isUpdating } = usePlanMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [isActive, setIsActive] = useState(true);\n const [isPurchasable, setIsPurchasable] = useState(true);\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [initialCurrencyPriceOverrides, setInitialCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Populate form when plan data loads\n useEffect(() => {\n if (plan) {\n setName(plan.name ?? '');\n setPrice(String(plan.price ?? ''));\n setCurrency(plan.currency ?? 'USD');\n setDuration(plan.duration ?? PlanDuration.MONTHLY);\n setIsActive(plan.isActive ?? true);\n setIsPurchasable(plan.isPurchasable ?? true);\n // Pre-populate currency price overrides so existing prices are preserved on save\n if (plan.currencyPrices && typeof plan.currencyPrices === 'object') {\n const existing = plan.currencyPrices as Record<string, number>;\n const overrides = Object.entries(existing)\n .filter(([, v]) => typeof v === 'number')\n .map(([currency, amount]) => ({ currency, amount }));\n setCurrencyPriceOverrides(overrides);\n setInitialCurrencyPriceOverrides(overrides);\n }\n // Extract quota allocations from features\n if (plan.features && plan.features.length > 0) {\n const allocations: QuotaAllocation[] = [];\n const quotaMap = new Map<string, number>();\n\n for (const feature of plan.features) {\n if (feature.quota?.id) {\n const quotaId = feature.quota.id;\n // SubscriptionFeature now stores per-plan override in context.quantity.\n // Fall back to quota.limits.value for legacy autoseeded plans.\n const value = getPlanFeatureQuantity(feature) || 1;\n quotaMap.set(quotaId, value);\n }\n }\n\n for (const [quotaId, quantity] of quotaMap) {\n allocations.push({ quotaId, quantity });\n }\n\n setQuotaAllocations(allocations);\n }\n }\n }, [plan]);\n\n // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr('billing.plans.noEditPermission', \"You don't have permission to edit plans.\")}\n />\n );\n }\n\n // Loading state\n if (isPlanLoading || isQuotasLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (planError && isServerError(planError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableLoadPlan',\n 'Unable to load plan. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n if (!plan) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.plans.planNotFound', 'Plan not found')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover\"\n >\n Back to Plans\n </button>\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Plan name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: UpdatePlanInput = {\n id: planId!,\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n isActive,\n isPurchasable,\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(getChangedCurrencyPriceOverrides(initialCurrencyPriceOverrides, currencyPriceOverrides)\n .length > 0\n ? {\n currencyPriceOverrides: getChangedCurrencyPriceOverrides(\n initialCurrencyPriceOverrides,\n currencyPriceOverrides\n ),\n }\n : {}),\n };\n\n try {\n await updatePlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo(`/plans/${planId}`), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={`Edit Plan: ${plan.name}`}\n description={tr(\n 'billing.plans.editPlanDesc',\n 'Update the plan details and quota allocations'\n )}\n />\n\n <PagePurpose>\n {tr(\n 'billing.plans.editPurpose',\n \"Update this plan's price, currency/seat pricing and the quotas it grants. Changes apply to new subscriptions; existing subscribers keep the terms they signed up with until they renew or change plans.\"\n )}\n </PagePurpose>\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.plans.updateSuccess', 'Plan updated successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.plans.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.plans.planNameLabel', 'Plan Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder={tr('billing.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"price\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"49.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.plans.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Status */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"isActive\"\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <label htmlFor=\"isActive\" className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.planIsActive', 'Plan is active')}\n </label>\n </div>\n\n {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-text-muted mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n isPurchasable ? 'bg-action-primary-bg' : 'bg-text-muted/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-bg-surface shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)] h-fit\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.plans.quotaAllocations', 'Quota Allocations')}\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n className=\"px-3 py-1 text-sm font-medium text-text-link hover:bg-[var(--color-accent-soft)] rounded-md transition-colors\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'billing.plans.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitType = limits?.type ?? 'unknown';\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border-subtle rounded-lg bg-bg-sunken/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n allocation.quotaId === '' ? 'text-text-muted' : 'text-text-primary'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.plans.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\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 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"capitalize text-text-muted\">Type: {limitType}</span>\n </div>\n\n {/* Limit value — allocation.quantity is the absolute limit */}\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-text-muted\">\n {tr('billing.plans.limit', 'Limit')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) => handleQuotaChange(index, 'quantity', e.target.value)}\n className=\"w-36 px-3 py-1.5 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-center\"\n />\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isUpdating}\n className=\"px-6 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isUpdating\n ? tr('billing.plans.saving', 'Saving...')\n : tr('billing.plans.saveChanges', 'Save Changes')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/plans/${planId}`)}\n className=\"px-6 py-2 text-sm font-medium border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/billing/modules/plans/utils/currencyPriceOverrides.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
let n = new Map(e.map((e) => [e.currency, e.amount]));
|
|
4
|
+
return t.filter((e) => n.get(e.currency) !== e.amount);
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { e as getChangedCurrencyPriceOverrides };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=currencyPriceOverrides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currencyPriceOverrides.js","names":[],"sources":["../../../../../src/billing/modules/plans/utils/currencyPriceOverrides.ts"],"sourcesContent":["import type { CurrencyPriceOverrideInput } from '../../../shared/types';\n\nexport function getChangedCurrencyPriceOverrides(\n initial: CurrencyPriceOverrideInput[],\n current: CurrencyPriceOverrideInput[]\n): CurrencyPriceOverrideInput[] {\n const initialByCurrency = new Map(\n initial.map((override) => [override.currency, override.amount])\n );\n\n return current.filter((override) => initialByCurrency.get(override.currency) !== override.amount);\n}\n"],"mappings":";AAEA,SAAgB,EACd,GACA,GAC8B;CAC9B,IAAM,IAAoB,IAAI,IAC5B,EAAQ,KAAK,MAAa,CAAC,EAAS,UAAU,EAAS,MAAM,CAAC,CAChE;CAEA,OAAO,EAAQ,QAAQ,MAAa,EAAkB,IAAI,EAAS,QAAQ,MAAM,EAAS,MAAM;AAClG"}
|