@burdenoff/microfe-billing 2026.1005.3 → 2026.1005.5
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/checkout/components/WebCheckoutRedirectView.js +82 -0
- package/dist/billing/modules/checkout/components/WebCheckoutRedirectView.js.map +1 -0
- package/dist/billing/modules/checkout/components/index.js +1 -0
- package/dist/billing/modules/checkout/index.js +1 -0
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +498 -485
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +133 -92
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/utils/planSelector.js +16 -0
- package/dist/billing/modules/plans/utils/planSelector.js.map +1 -0
- package/package.json +1 -1
|
@@ -6,214 +6,255 @@ import { useAssignPlanFreeMutation as r } from "../../../../generated/global-ope
|
|
|
6
6
|
import { useBillingNavigate as i } from "../../../hooks/useBillingNavigate.js";
|
|
7
7
|
import { usePlans as a } from "../hooks/usePlans.js";
|
|
8
8
|
import "../hooks/index.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import { filterPlans as o } from "../utils/planSelector.js";
|
|
10
|
+
import { useMemo as s, useState as c } from "react";
|
|
11
|
+
import { Check as l, ChevronsUpDown as u } from "lucide-react";
|
|
12
|
+
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
13
|
+
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
14
|
+
import { Command as m, CommandEmpty as h, CommandInput as g, CommandItem as _, CommandList as v, PagePurpose as y, Popover as b, PopoverContent as x, PopoverTrigger as S } from "@burdenoff/fe-libs/ui";
|
|
13
15
|
//#region src/billing/modules/plans/pages/PlanAssignFreePage.tsx
|
|
14
|
-
var
|
|
15
|
-
let { t:
|
|
16
|
-
let n =
|
|
16
|
+
var C = () => {
|
|
17
|
+
let { t: C } = p(), w = (e, t) => {
|
|
18
|
+
let n = C(e);
|
|
17
19
|
return n === e ? t : n;
|
|
18
|
-
},
|
|
19
|
-
if (!
|
|
20
|
-
let
|
|
21
|
-
if (e.preventDefault(),
|
|
22
|
-
|
|
20
|
+
}, T = i(), E = n(), [D, O] = c(""), [k, A] = c(""), [j, M] = c(""), [N, P] = c(!1), [F, I] = c(""), [L, R] = c(""), [z, B] = c(null), { plans: V, isLoading: H } = a({ includeFree: !0 }), U = s(() => o(V, F), [V, F]), [W, { loading: G }] = r();
|
|
21
|
+
if (!E.canManagePlans) return /* @__PURE__ */ d(e, { message: w("billing.assignFree.noPermission", "You don't have permission to access this page.") });
|
|
22
|
+
let K = async (e) => {
|
|
23
|
+
if (e.preventDefault(), B(null), !D.trim() || !k.trim() || !j) {
|
|
24
|
+
B({
|
|
23
25
|
success: !1,
|
|
24
26
|
message: "All fields are required."
|
|
25
27
|
});
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
try {
|
|
29
|
-
let { data: e } = await
|
|
30
|
-
secret:
|
|
31
|
-
billingAccountId:
|
|
32
|
-
planId:
|
|
33
|
-
...
|
|
31
|
+
let { data: e } = await W({ variables: {
|
|
32
|
+
secret: D.trim(),
|
|
33
|
+
billingAccountId: k.trim(),
|
|
34
|
+
planId: j,
|
|
35
|
+
...L.trim() ? { workspaceId: L.trim() } : {}
|
|
34
36
|
} });
|
|
35
37
|
if (e?.assignPlanFree) {
|
|
36
38
|
let t = e.assignPlanFree;
|
|
37
|
-
|
|
39
|
+
B({
|
|
38
40
|
success: !0,
|
|
39
41
|
message: `Plan assigned. Subscription ID: ${t.id} · Status: ${t.status} · Valid: ${new Date(t.startDate).toLocaleDateString()} → ${new Date(t.endDate).toLocaleDateString()}`
|
|
40
|
-
}),
|
|
42
|
+
}), O(""), A(""), M(""), P(!1), I(""), R("");
|
|
41
43
|
}
|
|
42
44
|
} catch (e) {
|
|
43
|
-
|
|
45
|
+
B({
|
|
44
46
|
success: !1,
|
|
45
47
|
message: e instanceof Error ? e.message : "Failed to assign plan."
|
|
46
48
|
});
|
|
47
49
|
}
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
+
}, q = V.find((e) => e.id === j), J = (e) => {
|
|
51
|
+
M(e), P(!1), I("");
|
|
52
|
+
};
|
|
53
|
+
return /* @__PURE__ */ f("div", {
|
|
50
54
|
className: "space-y-6 p-6 max-w-2xl",
|
|
51
55
|
children: [
|
|
52
|
-
/* @__PURE__ */
|
|
53
|
-
title:
|
|
54
|
-
description:
|
|
55
|
-
actions: /* @__PURE__ */
|
|
56
|
+
/* @__PURE__ */ d(t, {
|
|
57
|
+
title: w("billing.assignFree.title", "Assign Plan for Free"),
|
|
58
|
+
description: w("billing.assignFree.description", "Internally assign any plan to a billing account at no charge. Requires the team secret."),
|
|
59
|
+
actions: /* @__PURE__ */ d("button", {
|
|
56
60
|
type: "button",
|
|
57
|
-
onClick: () =>
|
|
61
|
+
onClick: () => T("/plans"),
|
|
58
62
|
className: "px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors",
|
|
59
63
|
children: "← Back to Plans"
|
|
60
64
|
})
|
|
61
65
|
}),
|
|
62
|
-
/* @__PURE__ */
|
|
63
|
-
/* @__PURE__ */
|
|
66
|
+
/* @__PURE__ */ d(y, { children: w("billing.assignFree.purpose", "An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.") }),
|
|
67
|
+
/* @__PURE__ */ f("div", {
|
|
64
68
|
className: "flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10",
|
|
65
|
-
children: [/* @__PURE__ */
|
|
69
|
+
children: [/* @__PURE__ */ d("svg", {
|
|
66
70
|
className: "size-5 text-status-warning-text shrink-0 mt-0.5",
|
|
67
71
|
fill: "none",
|
|
68
72
|
viewBox: "0 0 24 24",
|
|
69
73
|
stroke: "currentColor",
|
|
70
|
-
children: /* @__PURE__ */
|
|
74
|
+
children: /* @__PURE__ */ d("path", {
|
|
71
75
|
strokeLinecap: "round",
|
|
72
76
|
strokeLinejoin: "round",
|
|
73
77
|
strokeWidth: 2,
|
|
74
78
|
d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z"
|
|
75
79
|
})
|
|
76
|
-
}), /* @__PURE__ */
|
|
80
|
+
}), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
|
|
77
81
|
className: "text-sm font-medium text-status-warning-text",
|
|
78
82
|
children: "Internal admin tool"
|
|
79
|
-
}), /* @__PURE__ */
|
|
83
|
+
}), /* @__PURE__ */ d("p", {
|
|
80
84
|
className: "text-xs text-status-warning-text/80 mt-0.5",
|
|
81
85
|
children: "This bypasses all payment flows. The plan is assigned immediately with no charge. Use only for enterprise deals or internal accounts."
|
|
82
86
|
})] })]
|
|
83
87
|
}),
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
-
onSubmit:
|
|
88
|
+
/* @__PURE__ */ f("form", {
|
|
89
|
+
onSubmit: K,
|
|
86
90
|
className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]",
|
|
87
91
|
children: [
|
|
88
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ d("div", {
|
|
89
93
|
className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
|
|
90
|
-
children: /* @__PURE__ */
|
|
94
|
+
children: /* @__PURE__ */ d("h2", {
|
|
91
95
|
className: "font-semibold text-text-primary",
|
|
92
96
|
children: "Assignment Details"
|
|
93
97
|
})
|
|
94
98
|
}),
|
|
95
|
-
/* @__PURE__ */
|
|
99
|
+
/* @__PURE__ */ f("div", {
|
|
96
100
|
className: "p-4 space-y-4",
|
|
97
101
|
children: [
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ f("div", { children: [
|
|
103
|
+
/* @__PURE__ */ f("label", {
|
|
100
104
|
htmlFor: "secret",
|
|
101
105
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
102
|
-
children: ["Team Secret ", /* @__PURE__ */
|
|
106
|
+
children: ["Team Secret ", /* @__PURE__ */ d("span", {
|
|
103
107
|
className: "text-status-error-text",
|
|
104
108
|
children: "*"
|
|
105
109
|
})]
|
|
106
110
|
}),
|
|
107
|
-
/* @__PURE__ */
|
|
111
|
+
/* @__PURE__ */ d("input", {
|
|
108
112
|
id: "secret",
|
|
109
113
|
type: "password",
|
|
110
|
-
value:
|
|
111
|
-
onChange: (e) =>
|
|
114
|
+
value: D,
|
|
115
|
+
onChange: (e) => O(e.target.value),
|
|
112
116
|
autoComplete: "off",
|
|
113
117
|
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)] font-mono",
|
|
114
118
|
placeholder: "Enter PLAN_ASSIGN_SECRET",
|
|
115
119
|
required: !0
|
|
116
120
|
}),
|
|
117
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ d("p", {
|
|
118
122
|
className: "text-xs text-text-muted mt-1",
|
|
119
123
|
children: "The secret key shared within the team. Never expose this to users."
|
|
120
124
|
})
|
|
121
125
|
] }),
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ f("div", { children: [
|
|
127
|
+
/* @__PURE__ */ f("label", {
|
|
124
128
|
htmlFor: "billingAccountId",
|
|
125
129
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
126
|
-
children: ["Billing Account ID ", /* @__PURE__ */
|
|
130
|
+
children: ["Billing Account ID ", /* @__PURE__ */ d("span", {
|
|
127
131
|
className: "text-status-error-text",
|
|
128
132
|
children: "*"
|
|
129
133
|
})]
|
|
130
134
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
135
|
+
/* @__PURE__ */ d("input", {
|
|
132
136
|
id: "billingAccountId",
|
|
133
137
|
type: "text",
|
|
134
|
-
value:
|
|
135
|
-
onChange: (e) =>
|
|
138
|
+
value: k,
|
|
139
|
+
onChange: (e) => A(e.target.value),
|
|
136
140
|
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)] font-mono text-sm",
|
|
137
141
|
placeholder: "e.g. ba_01jk3n2...",
|
|
138
142
|
required: !0
|
|
139
143
|
}),
|
|
140
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ d("p", {
|
|
141
145
|
className: "text-xs text-text-muted mt-1",
|
|
142
146
|
children: "Find this in the user's billing account settings or via the billing accounts list."
|
|
143
147
|
})
|
|
144
148
|
] }),
|
|
145
|
-
/* @__PURE__ */
|
|
146
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ f("div", { children: [
|
|
150
|
+
/* @__PURE__ */ f("label", {
|
|
147
151
|
htmlFor: "workspaceId",
|
|
148
152
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
149
|
-
children: ["Workspace ID ", /* @__PURE__ */
|
|
153
|
+
children: ["Workspace ID ", /* @__PURE__ */ d("span", {
|
|
150
154
|
className: "text-text-secondary font-normal",
|
|
151
155
|
children: "(optional)"
|
|
152
156
|
})]
|
|
153
157
|
}),
|
|
154
|
-
/* @__PURE__ */
|
|
158
|
+
/* @__PURE__ */ d("input", {
|
|
155
159
|
id: "workspaceId",
|
|
156
160
|
type: "text",
|
|
157
|
-
value:
|
|
158
|
-
onChange: (e) =>
|
|
161
|
+
value: L,
|
|
162
|
+
onChange: (e) => R(e.target.value),
|
|
159
163
|
className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm",
|
|
160
164
|
placeholder: "e.g. ws_01jk3n2… (leave blank to auto-resolve)"
|
|
161
165
|
}),
|
|
162
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ d("p", {
|
|
163
167
|
className: "text-xs text-text-secondary mt-1",
|
|
164
168
|
children: "If blank, the first active workspace for this org is used automatically. Set explicitly when the org has multiple workspaces."
|
|
165
169
|
})
|
|
166
170
|
] }),
|
|
167
|
-
/* @__PURE__ */
|
|
168
|
-
/* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ f("div", { children: [
|
|
172
|
+
/* @__PURE__ */ f("label", {
|
|
169
173
|
htmlFor: "planId",
|
|
170
174
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
171
|
-
children: ["Plan ", /* @__PURE__ */
|
|
175
|
+
children: ["Plan ", /* @__PURE__ */ d("span", {
|
|
172
176
|
className: "text-status-error-text",
|
|
173
177
|
children: "*"
|
|
174
178
|
})]
|
|
175
179
|
}),
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
180
|
+
H ? /* @__PURE__ */ d("div", { className: "h-10 bg-bg-sunken animate-pulse rounded-md" }) : /* @__PURE__ */ f(b, {
|
|
181
|
+
open: N,
|
|
182
|
+
onOpenChange: (e) => {
|
|
183
|
+
P(e), e || I("");
|
|
184
|
+
},
|
|
185
|
+
children: [/* @__PURE__ */ d(S, {
|
|
186
|
+
asChild: !0,
|
|
187
|
+
children: /* @__PURE__ */ f("button", {
|
|
188
|
+
id: "planId",
|
|
189
|
+
type: "button",
|
|
190
|
+
role: "combobox",
|
|
191
|
+
"aria-expanded": N,
|
|
192
|
+
"aria-required": "true",
|
|
193
|
+
className: "w-full flex items-center justify-between gap-2 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-left text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
194
|
+
children: [/* @__PURE__ */ d("span", {
|
|
195
|
+
className: q ? "truncate" : "truncate text-text-muted",
|
|
196
|
+
children: q ? `${q.name}${q.productID ? ` · ${q.productID}` : ""}` : "Select a plan…"
|
|
197
|
+
}), /* @__PURE__ */ d(u, {
|
|
198
|
+
className: "size-4 shrink-0 text-text-muted",
|
|
199
|
+
"aria-hidden": "true"
|
|
200
|
+
})]
|
|
201
|
+
})
|
|
202
|
+
}), /* @__PURE__ */ d(x, {
|
|
203
|
+
align: "start",
|
|
204
|
+
className: "w-[var(--radix-popover-trigger-width)] p-0",
|
|
205
|
+
children: /* @__PURE__ */ f(m, {
|
|
206
|
+
shouldFilter: !1,
|
|
207
|
+
children: [/* @__PURE__ */ d(g, {
|
|
208
|
+
value: F,
|
|
209
|
+
onValueChange: I,
|
|
210
|
+
placeholder: "Search plans by name, product, or ID…"
|
|
211
|
+
}), /* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(h, { children: "No matching plans found." }), U.map((e) => /* @__PURE__ */ f(_, {
|
|
212
|
+
value: e.id,
|
|
213
|
+
onSelect: () => J(e.id),
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ d(l, {
|
|
216
|
+
className: `size-4 shrink-0 ${e.id === j ? "opacity-100" : "opacity-0"}`,
|
|
217
|
+
"aria-hidden": "true"
|
|
218
|
+
}),
|
|
219
|
+
/* @__PURE__ */ f("span", {
|
|
220
|
+
className: "truncate",
|
|
221
|
+
children: [e.name, e.isPurchasable ? "" : " (internal)"]
|
|
222
|
+
}),
|
|
223
|
+
/* @__PURE__ */ f("span", {
|
|
224
|
+
className: "ml-auto text-xs text-text-muted",
|
|
225
|
+
children: [
|
|
226
|
+
e.productID,
|
|
227
|
+
" · ",
|
|
228
|
+
e.duration
|
|
229
|
+
]
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
}, e.id))] })]
|
|
233
|
+
})
|
|
234
|
+
})]
|
|
194
235
|
}),
|
|
195
|
-
|
|
236
|
+
q && /* @__PURE__ */ f("p", {
|
|
196
237
|
className: "text-xs text-text-muted mt-1",
|
|
197
|
-
children: [
|
|
238
|
+
children: [q.isPurchasable ? "Public plan" : "Internal / non-purchasable plan", q.productID ? ` · Product: ${q.productID}` : ""]
|
|
198
239
|
})
|
|
199
240
|
] })
|
|
200
241
|
]
|
|
201
242
|
}),
|
|
202
|
-
|
|
203
|
-
className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${
|
|
204
|
-
children:
|
|
243
|
+
z && /* @__PURE__ */ d("div", {
|
|
244
|
+
className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${z.success ? "border-status-success-border bg-status-success-bg/10 text-status-success-text" : "border-status-error-border bg-status-error-bg/10 text-status-error-text"}`,
|
|
245
|
+
children: z.message
|
|
205
246
|
}),
|
|
206
|
-
/* @__PURE__ */
|
|
247
|
+
/* @__PURE__ */ f("div", {
|
|
207
248
|
className: "px-4 pb-4 flex items-center gap-3",
|
|
208
|
-
children: [/* @__PURE__ */
|
|
249
|
+
children: [/* @__PURE__ */ d("button", {
|
|
209
250
|
type: "submit",
|
|
210
|
-
disabled:
|
|
251
|
+
disabled: G || !D || !k || !j,
|
|
211
252
|
className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
212
|
-
children:
|
|
213
|
-
}), /* @__PURE__ */
|
|
253
|
+
children: G ? "Assigning…" : "Assign Plan for Free"
|
|
254
|
+
}), /* @__PURE__ */ d("button", {
|
|
214
255
|
type: "button",
|
|
215
256
|
onClick: () => {
|
|
216
|
-
|
|
257
|
+
O(""), A(""), M(""), P(!1), I(""), R(""), B(null);
|
|
217
258
|
},
|
|
218
259
|
className: "px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors",
|
|
219
260
|
children: "Clear"
|
|
@@ -225,6 +266,6 @@ var d = () => {
|
|
|
225
266
|
});
|
|
226
267
|
};
|
|
227
268
|
//#endregion
|
|
228
|
-
export {
|
|
269
|
+
export { C as PlanAssignFreePage };
|
|
229
270
|
|
|
230
271
|
//# sourceMappingURL=PlanAssignFreePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { usePlans } from '../hooks';\n\nexport const PlanAssignFreePage: 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\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.assignFree.purpose',\n 'An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.'\n )}\n </PagePurpose>\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.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=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\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)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(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)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label\n htmlFor=\"workspaceId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"planId\"\n value={planId}\n onChange={(e) => setPlanId(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 required\n >\n <option value=\"\" disabled>\n Select a plan…\n </option>\n {plans.map((plan) => (\n <option key={plan.id} value={plan.id}>\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n {plan.price != null && plan.price > 0\n ? ` — ${plan.currency ?? 'USD'} ${plan.price}`\n : ''}\n </option>\n ))}\n </select>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { Check, ChevronsUpDown } from 'lucide-react';\nimport { useMemo, useState, type FC, type FormEvent } from 'react';\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n PagePurpose,\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { usePlans } from '../hooks';\nimport { filterPlans } from '../utils/planSelector';\n\nexport const PlanAssignFreePage: 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\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [planPickerOpen, setPlanPickerOpen] = useState(false);\n const [planSearch, setPlanSearch] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n const filteredPlans = useMemo(() => filterPlans(plans, planSearch), [plans, planSearch]);\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setPlanPickerOpen(false);\n setPlanSearch('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n const handlePlanSelect = (nextPlanId: string) => {\n setPlanId(nextPlanId);\n setPlanPickerOpen(false);\n setPlanSearch('');\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.assignFree.purpose',\n 'An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.'\n )}\n </PagePurpose>\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.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=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\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)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(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)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label\n htmlFor=\"workspaceId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <Popover\n open={planPickerOpen}\n onOpenChange={(open) => {\n setPlanPickerOpen(open);\n if (!open) {\n setPlanSearch('');\n }\n }}\n >\n <PopoverTrigger asChild>\n <button\n id=\"planId\"\n type=\"button\"\n role=\"combobox\"\n aria-expanded={planPickerOpen}\n aria-required=\"true\"\n className=\"w-full flex items-center justify-between gap-2 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-left text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <span className={selectedPlan ? 'truncate' : 'truncate text-text-muted'}>\n {selectedPlan\n ? `${selectedPlan.name}${selectedPlan.productID ? ` · ${selectedPlan.productID}` : ''}`\n : 'Select a plan…'}\n </span>\n <ChevronsUpDown\n className=\"size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-[var(--radix-popover-trigger-width)] p-0\"\n >\n <Command shouldFilter={false}>\n <CommandInput\n value={planSearch}\n onValueChange={setPlanSearch}\n placeholder=\"Search plans by name, product, or ID…\"\n />\n <CommandList>\n <CommandEmpty>No matching plans found.</CommandEmpty>\n {filteredPlans.map((plan) => (\n <CommandItem\n key={plan.id}\n value={plan.id}\n onSelect={() => handlePlanSelect(plan.id)}\n >\n <Check\n className={`size-4 shrink-0 ${\n plan.id === planId ? 'opacity-100' : 'opacity-0'\n }`}\n aria-hidden=\"true\"\n />\n <span className=\"truncate\">\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n </span>\n <span className=\"ml-auto text-xs text-text-muted\">\n {plan.productID} · {plan.duration}\n </span>\n </CommandItem>\n ))}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setPlanPickerOpen(false);\n setPlanSearch('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/billing/modules/plans/utils/planSelector.ts
|
|
2
|
+
var e = (e) => e?.toLowerCase() ?? "";
|
|
3
|
+
function t(t, n) {
|
|
4
|
+
let r = n.trim().toLowerCase();
|
|
5
|
+
return r ? t.filter((t) => [
|
|
6
|
+
t.name,
|
|
7
|
+
t.id,
|
|
8
|
+
t.productID,
|
|
9
|
+
t.product?.name,
|
|
10
|
+
t.duration
|
|
11
|
+
].some((t) => e(t).includes(r))) : [...t];
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { t as filterPlans };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=planSelector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planSelector.js","names":[],"sources":["../../../../../src/billing/modules/plans/utils/planSelector.ts"],"sourcesContent":["import type { Plan } from '../../../shared/types';\n\nconst getSearchableValue = (value: string | null | undefined): string => value?.toLowerCase() ?? '';\n\nexport function filterPlans(plans: readonly Plan[], search: string): Plan[] {\n const normalizedSearch = search.trim().toLowerCase();\n\n if (!normalizedSearch) {\n return [...plans];\n }\n\n return plans.filter((plan) =>\n [plan.name, plan.id, plan.productID, plan.product?.name, plan.duration].some((value) =>\n getSearchableValue(value).includes(normalizedSearch)\n )\n );\n}\n"],"mappings":";AAEA,IAAM,KAAsB,MAA6C,GAAO,YAAY,KAAK;AAEjG,SAAgB,EAAY,GAAwB,GAAwB;CAC1E,IAAM,IAAmB,EAAO,KAAK,EAAE,YAAY;CAMnD,OAJK,IAIE,EAAM,QAAQ,MACnB;EAAC,EAAK;EAAM,EAAK;EAAI,EAAK;EAAW,EAAK,SAAS;EAAM,EAAK;CAAQ,EAAE,MAAM,MAC5E,EAAmB,CAAK,EAAE,SAAS,CAAgB,CACrD,CACF,IAPS,CAAC,GAAG,CAAK;AAQpB"}
|