@burdenoff/microfe-billing 2026.1005.1 → 2026.1005.3
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/pages/CheckoutPage.js +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaAssignFreePage.js +144 -110
- package/dist/billing/modules/quota/pages/QuotaAssignFreePage.js.map +1 -1
- package/dist/billing/modules/quota/utils/quotaSelector.js +15 -0
- package/dist/billing/modules/quota/utils/quotaSelector.js.map +1 -0
- package/dist/generated/global-operations.js +2 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,80 +4,84 @@ import "../../../shared/components/index.js";
|
|
|
4
4
|
import { useBillingPermissions as n } from "../../../hooks/useBillingPermissions.js";
|
|
5
5
|
import { useAssignQuotaFreeMutation as r } from "../../../../generated/global-operations.js";
|
|
6
6
|
import { useBillingNavigate as i } from "../../../hooks/useBillingNavigate.js";
|
|
7
|
-
import {
|
|
8
|
-
import "
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { useQuotas as a } from "../../plans/hooks/useQuotas.js";
|
|
8
|
+
import "../../plans/hooks/index.js";
|
|
9
|
+
import { filterQuotas as o, getQuotaScopeLabel as s } from "../utils/quotaSelector.js";
|
|
10
|
+
import { useMemo as c, useState as l } from "react";
|
|
11
|
+
import { Check as u, ChevronsUpDown as d } from "lucide-react";
|
|
12
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
13
|
+
import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
14
|
+
import { Command as h, CommandEmpty as g, CommandInput as _, CommandItem as v, CommandList as y, PagePurpose as b, Popover as x, PopoverContent as S, PopoverTrigger as C } from "@burdenoff/fe-libs/ui";
|
|
13
15
|
//#region src/billing/modules/quota/pages/QuotaAssignFreePage.tsx
|
|
14
|
-
var
|
|
15
|
-
let { t:
|
|
16
|
-
let n =
|
|
16
|
+
var w = () => {
|
|
17
|
+
let { t: w } = m(), T = (e, t) => {
|
|
18
|
+
let n = w(e);
|
|
17
19
|
return n === e ? t : n;
|
|
18
|
-
},
|
|
19
|
-
|
|
20
|
+
}, E = i(), D = n(), [O, k] = l(""), [A, j] = l(""), [M, N] = l(""), [P, F] = l(!1), [I, L] = l(""), [R, z] = l(""), [B, V] = l(""), [H, U] = l(""), [W, G] = l(null), { quotas: K, isLoading: q } = a({ onlyActive: !0 }), [J, { loading: Y }] = r(), X = K.find((e) => e.id === M), Z = X?.scope === "WORKSPACE", Q = c(() => o(K, I), [K, I]), $ = c(() => X?.limits?.value, [X]), ee = (e) => {
|
|
21
|
+
N(e), z(""), F(!1), L("");
|
|
22
|
+
};
|
|
23
|
+
return D.canManageQuotas ? /* @__PURE__ */ p("div", {
|
|
20
24
|
className: "space-y-6 p-6 max-w-2xl",
|
|
21
25
|
children: [
|
|
22
|
-
/* @__PURE__ */
|
|
23
|
-
title:
|
|
24
|
-
description:
|
|
25
|
-
actions: /* @__PURE__ */
|
|
26
|
+
/* @__PURE__ */ f(t, {
|
|
27
|
+
title: T("billing.quotaAssignFree.title", "Assign Quota for Free"),
|
|
28
|
+
description: T("billing.quotaAssignFree.description", "Internally assign a single quota to a billing account at no charge. Requires the team secret."),
|
|
29
|
+
actions: /* @__PURE__ */ f("button", {
|
|
26
30
|
type: "button",
|
|
27
|
-
onClick: () =>
|
|
31
|
+
onClick: () => E("/quota"),
|
|
28
32
|
className: "px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors",
|
|
29
33
|
children: "← Back to Quotas"
|
|
30
34
|
})
|
|
31
35
|
}),
|
|
32
|
-
/* @__PURE__ */
|
|
33
|
-
/* @__PURE__ */
|
|
36
|
+
/* @__PURE__ */ f(b, { children: T("billing.quotaAssignFree.purpose", "An internal tool to grant a single quota to a billing account without assigning a whole plan — useful for one-off comps or fixing a missing entitlement. It bypasses payment, so it requires the team secret and should be used deliberately.") }),
|
|
37
|
+
/* @__PURE__ */ p("div", {
|
|
34
38
|
className: "flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10",
|
|
35
|
-
children: [/* @__PURE__ */
|
|
39
|
+
children: [/* @__PURE__ */ f("svg", {
|
|
36
40
|
className: "size-5 text-status-warning-text shrink-0 mt-0.5",
|
|
37
41
|
fill: "none",
|
|
38
42
|
viewBox: "0 0 24 24",
|
|
39
43
|
stroke: "currentColor",
|
|
40
|
-
children: /* @__PURE__ */
|
|
44
|
+
children: /* @__PURE__ */ f("path", {
|
|
41
45
|
strokeLinecap: "round",
|
|
42
46
|
strokeLinejoin: "round",
|
|
43
47
|
strokeWidth: 2,
|
|
44
48
|
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"
|
|
45
49
|
})
|
|
46
|
-
}), /* @__PURE__ */
|
|
50
|
+
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
|
|
47
51
|
className: "text-sm font-medium text-status-warning-text",
|
|
48
52
|
children: "Internal admin tool"
|
|
49
|
-
}), /* @__PURE__ */
|
|
53
|
+
}), /* @__PURE__ */ f("p", {
|
|
50
54
|
className: "text-xs text-status-warning-text/80 mt-0.5",
|
|
51
55
|
children: "This bypasses all payment flows. The quota is granted immediately with no charge. Use only for enterprise deals, comped accounts, or fixing a missing entitlement."
|
|
52
56
|
})] })]
|
|
53
57
|
}),
|
|
54
|
-
/* @__PURE__ */
|
|
58
|
+
/* @__PURE__ */ p("form", {
|
|
55
59
|
onSubmit: async (e) => {
|
|
56
|
-
if (e.preventDefault(),
|
|
57
|
-
|
|
60
|
+
if (e.preventDefault(), G(null), !O.trim() || !A.trim() || !M) {
|
|
61
|
+
G({
|
|
58
62
|
success: !1,
|
|
59
63
|
message: "All fields are required."
|
|
60
64
|
});
|
|
61
65
|
return;
|
|
62
66
|
}
|
|
63
67
|
try {
|
|
64
|
-
let { data: e } = await
|
|
65
|
-
secret:
|
|
66
|
-
billingAccountId:
|
|
67
|
-
quotaId:
|
|
68
|
-
...
|
|
69
|
-
...
|
|
70
|
-
...
|
|
68
|
+
let { data: e } = await J({ variables: {
|
|
69
|
+
secret: O.trim(),
|
|
70
|
+
billingAccountId: A.trim(),
|
|
71
|
+
quotaId: M,
|
|
72
|
+
...R.trim() ? { workspaceId: R.trim() } : {},
|
|
73
|
+
...B.trim() ? { limit: Number(B) } : {},
|
|
74
|
+
...H.trim() ? { durationDays: Number(H) } : {}
|
|
71
75
|
} });
|
|
72
76
|
if (e?.assignQuotaFree) {
|
|
73
77
|
let t = e.assignQuotaFree;
|
|
74
|
-
|
|
78
|
+
G({
|
|
75
79
|
success: !0,
|
|
76
80
|
message: `Quota "${t.quotaName}" assigned (${t.scope}). Limit: ${t.limit ?? "default"}` + (t.workspaceId ? ` · Workspace: ${t.workspaceId}` : "") + (t.endtime ? ` · Valid until: ${new Date(t.endtime).toLocaleDateString()}` : "")
|
|
77
|
-
}),
|
|
81
|
+
}), k(""), j(""), N(""), F(!1), L(""), z(""), V(""), U("");
|
|
78
82
|
}
|
|
79
83
|
} catch (e) {
|
|
80
|
-
|
|
84
|
+
G({
|
|
81
85
|
success: !1,
|
|
82
86
|
message: e instanceof Error ? e.message : "Failed to assign quota."
|
|
83
87
|
});
|
|
@@ -85,182 +89,212 @@ var f = () => {
|
|
|
85
89
|
},
|
|
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__ */ f("div", {
|
|
89
93
|
className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
|
|
90
|
-
children: /* @__PURE__ */
|
|
94
|
+
children: /* @__PURE__ */ f("h2", {
|
|
91
95
|
className: "font-semibold text-text-primary",
|
|
92
96
|
children: "Assignment Details"
|
|
93
97
|
})
|
|
94
98
|
}),
|
|
95
|
-
/* @__PURE__ */
|
|
99
|
+
/* @__PURE__ */ p("div", {
|
|
96
100
|
className: "p-4 space-y-4",
|
|
97
101
|
children: [
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ p("div", { children: [
|
|
103
|
+
/* @__PURE__ */ p("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__ */ f("span", {
|
|
103
107
|
className: "text-status-error-text",
|
|
104
108
|
children: "*"
|
|
105
109
|
})]
|
|
106
110
|
}),
|
|
107
|
-
/* @__PURE__ */
|
|
111
|
+
/* @__PURE__ */ f("input", {
|
|
108
112
|
id: "secret",
|
|
109
113
|
type: "password",
|
|
110
|
-
value:
|
|
111
|
-
onChange: (e) =>
|
|
114
|
+
value: O,
|
|
115
|
+
onChange: (e) => k(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__ */ f("p", {
|
|
118
122
|
className: "text-xs text-text-muted mt-1",
|
|
119
123
|
children: "Same team secret used by Assign Plan Free. Never expose this to users."
|
|
120
124
|
})
|
|
121
125
|
] }),
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ p("div", { children: [
|
|
127
|
+
/* @__PURE__ */ p("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__ */ f("span", {
|
|
127
131
|
className: "text-status-error-text",
|
|
128
132
|
children: "*"
|
|
129
133
|
})]
|
|
130
134
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
135
|
+
/* @__PURE__ */ f("input", {
|
|
132
136
|
id: "billingAccountId",
|
|
133
137
|
type: "text",
|
|
134
|
-
value:
|
|
135
|
-
onChange: (e) =>
|
|
138
|
+
value: A,
|
|
139
|
+
onChange: (e) => j(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__ */ f("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__ */ p("div", { children: [
|
|
150
|
+
/* @__PURE__ */ p("label", {
|
|
147
151
|
htmlFor: "quotaId",
|
|
148
152
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
149
|
-
children: ["Quota ", /* @__PURE__ */
|
|
153
|
+
children: ["Quota ", /* @__PURE__ */ f("span", {
|
|
150
154
|
className: "text-status-error-text",
|
|
151
155
|
children: "*"
|
|
152
156
|
})]
|
|
153
157
|
}),
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
x(e.target.value), C("");
|
|
158
|
+
q ? /* @__PURE__ */ f("div", { className: "h-10 bg-bg-sunken animate-pulse rounded-md" }) : /* @__PURE__ */ p(x, {
|
|
159
|
+
open: P,
|
|
160
|
+
onOpenChange: (e) => {
|
|
161
|
+
F(e), e || L("");
|
|
159
162
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
163
|
+
children: [/* @__PURE__ */ f(C, {
|
|
164
|
+
asChild: !0,
|
|
165
|
+
children: /* @__PURE__ */ p("button", {
|
|
166
|
+
id: "quotaId",
|
|
167
|
+
type: "button",
|
|
168
|
+
role: "combobox",
|
|
169
|
+
"aria-expanded": P,
|
|
170
|
+
"aria-required": "true",
|
|
171
|
+
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)]",
|
|
172
|
+
children: [/* @__PURE__ */ f("span", {
|
|
173
|
+
className: X ? "truncate" : "truncate text-text-muted",
|
|
174
|
+
children: X ? `${X.name} (${s(X)})` : "Select a quota…"
|
|
175
|
+
}), /* @__PURE__ */ f(d, {
|
|
176
|
+
className: "size-4 shrink-0 text-text-muted",
|
|
177
|
+
"aria-hidden": "true"
|
|
178
|
+
})]
|
|
179
|
+
})
|
|
180
|
+
}), /* @__PURE__ */ f(S, {
|
|
181
|
+
align: "start",
|
|
182
|
+
className: "w-[var(--radix-popover-trigger-width)] p-0",
|
|
183
|
+
children: /* @__PURE__ */ p(h, {
|
|
184
|
+
shouldFilter: !1,
|
|
185
|
+
children: [/* @__PURE__ */ f(_, {
|
|
186
|
+
value: I,
|
|
187
|
+
onValueChange: L,
|
|
188
|
+
placeholder: "Search quotas by name or ID…"
|
|
189
|
+
}), /* @__PURE__ */ p(y, { children: [/* @__PURE__ */ f(g, { children: "No matching quotas found." }), Q.map((e) => /* @__PURE__ */ p(v, {
|
|
190
|
+
value: e.id,
|
|
191
|
+
onSelect: () => ee(e.id),
|
|
192
|
+
children: [
|
|
193
|
+
/* @__PURE__ */ f(u, {
|
|
194
|
+
className: `size-4 shrink-0 ${e.id === M ? "opacity-100" : "opacity-0"}`,
|
|
195
|
+
"aria-hidden": "true"
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ f("span", {
|
|
198
|
+
className: "truncate",
|
|
199
|
+
children: e.name
|
|
200
|
+
}),
|
|
201
|
+
/* @__PURE__ */ f("span", {
|
|
202
|
+
className: "ml-auto text-xs text-text-muted",
|
|
203
|
+
children: s(e)
|
|
204
|
+
})
|
|
205
|
+
]
|
|
206
|
+
}, e.id))] })]
|
|
207
|
+
})
|
|
208
|
+
})]
|
|
175
209
|
}),
|
|
176
|
-
|
|
210
|
+
X && /* @__PURE__ */ p("p", {
|
|
177
211
|
className: "text-xs text-text-muted mt-1",
|
|
178
212
|
children: [
|
|
179
213
|
"Scope: ",
|
|
180
|
-
|
|
214
|
+
s(X),
|
|
181
215
|
" · Type: ",
|
|
182
|
-
|
|
183
|
-
|
|
216
|
+
X.quotaType,
|
|
217
|
+
$ == null ? "" : ` · Default limit: ${$}`
|
|
184
218
|
]
|
|
185
219
|
})
|
|
186
220
|
] }),
|
|
187
|
-
|
|
188
|
-
/* @__PURE__ */
|
|
221
|
+
Z && /* @__PURE__ */ p("div", { children: [
|
|
222
|
+
/* @__PURE__ */ p("label", {
|
|
189
223
|
htmlFor: "workspaceId",
|
|
190
224
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
191
|
-
children: ["Workspace ID ", /* @__PURE__ */
|
|
225
|
+
children: ["Workspace ID ", /* @__PURE__ */ f("span", {
|
|
192
226
|
className: "text-text-secondary font-normal",
|
|
193
227
|
children: "(optional)"
|
|
194
228
|
})]
|
|
195
229
|
}),
|
|
196
|
-
/* @__PURE__ */
|
|
230
|
+
/* @__PURE__ */ f("input", {
|
|
197
231
|
id: "workspaceId",
|
|
198
232
|
type: "text",
|
|
199
|
-
value:
|
|
200
|
-
onChange: (e) =>
|
|
233
|
+
value: R,
|
|
234
|
+
onChange: (e) => z(e.target.value),
|
|
201
235
|
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",
|
|
202
236
|
placeholder: "e.g. ws_01jk3n2… (leave blank to auto-resolve)"
|
|
203
237
|
}),
|
|
204
|
-
/* @__PURE__ */
|
|
238
|
+
/* @__PURE__ */ f("p", {
|
|
205
239
|
className: "text-xs text-text-secondary mt-1",
|
|
206
240
|
children: "This quota is workspace-scoped. If blank, the first active workspace for this org is used automatically."
|
|
207
241
|
})
|
|
208
242
|
] }),
|
|
209
|
-
/* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ p("label", {
|
|
210
244
|
htmlFor: "limit",
|
|
211
245
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
212
|
-
children: ["Limit ", /* @__PURE__ */
|
|
246
|
+
children: ["Limit ", /* @__PURE__ */ f("span", {
|
|
213
247
|
className: "text-text-secondary font-normal",
|
|
214
248
|
children: "(optional)"
|
|
215
249
|
})]
|
|
216
|
-
}), /* @__PURE__ */
|
|
250
|
+
}), /* @__PURE__ */ f("input", {
|
|
217
251
|
id: "limit",
|
|
218
252
|
type: "number",
|
|
219
|
-
value:
|
|
220
|
-
onChange: (e) =>
|
|
253
|
+
value: B,
|
|
254
|
+
onChange: (e) => V(e.target.value),
|
|
221
255
|
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)]",
|
|
222
|
-
placeholder:
|
|
256
|
+
placeholder: $ == null ? "Leave blank to use the quota default" : `Leave blank to use default (${$})`
|
|
223
257
|
})] }),
|
|
224
|
-
!
|
|
225
|
-
/* @__PURE__ */
|
|
258
|
+
!Z && X && /* @__PURE__ */ p("div", { children: [
|
|
259
|
+
/* @__PURE__ */ p("label", {
|
|
226
260
|
htmlFor: "durationDays",
|
|
227
261
|
className: "block text-sm font-medium text-text-primary mb-1",
|
|
228
|
-
children: ["Duration (days) ", /* @__PURE__ */
|
|
262
|
+
children: ["Duration (days) ", /* @__PURE__ */ f("span", {
|
|
229
263
|
className: "text-text-secondary font-normal",
|
|
230
264
|
children: "(optional)"
|
|
231
265
|
})]
|
|
232
266
|
}),
|
|
233
|
-
/* @__PURE__ */
|
|
267
|
+
/* @__PURE__ */ f("input", {
|
|
234
268
|
id: "durationDays",
|
|
235
269
|
type: "number",
|
|
236
270
|
min: 1,
|
|
237
|
-
value:
|
|
238
|
-
onChange: (e) =>
|
|
271
|
+
value: H,
|
|
272
|
+
onChange: (e) => U(e.target.value),
|
|
239
273
|
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)]",
|
|
240
274
|
placeholder: "Leave blank for 365 days"
|
|
241
275
|
}),
|
|
242
|
-
/* @__PURE__ */
|
|
276
|
+
/* @__PURE__ */ f("p", {
|
|
243
277
|
className: "text-xs text-text-muted mt-1",
|
|
244
278
|
children: "Ignored if this quota attaches to an existing subscription — it then expires with that subscription instead."
|
|
245
279
|
})
|
|
246
280
|
] })
|
|
247
281
|
]
|
|
248
282
|
}),
|
|
249
|
-
|
|
250
|
-
className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${
|
|
251
|
-
children:
|
|
283
|
+
W && /* @__PURE__ */ f("div", {
|
|
284
|
+
className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${W.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"}`,
|
|
285
|
+
children: W.message
|
|
252
286
|
}),
|
|
253
|
-
/* @__PURE__ */
|
|
287
|
+
/* @__PURE__ */ p("div", {
|
|
254
288
|
className: "px-4 pb-4 flex items-center gap-3",
|
|
255
|
-
children: [/* @__PURE__ */
|
|
289
|
+
children: [/* @__PURE__ */ f("button", {
|
|
256
290
|
type: "submit",
|
|
257
|
-
disabled:
|
|
291
|
+
disabled: Y || !O || !A || !M,
|
|
258
292
|
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",
|
|
259
|
-
children:
|
|
260
|
-
}), /* @__PURE__ */
|
|
293
|
+
children: Y ? "Assigning…" : "Assign Quota for Free"
|
|
294
|
+
}), /* @__PURE__ */ f("button", {
|
|
261
295
|
type: "button",
|
|
262
296
|
onClick: () => {
|
|
263
|
-
|
|
297
|
+
k(""), j(""), N(""), z(""), V(""), U(""), G(null);
|
|
264
298
|
},
|
|
265
299
|
className: "px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors",
|
|
266
300
|
children: "Clear"
|
|
@@ -269,9 +303,9 @@ var f = () => {
|
|
|
269
303
|
]
|
|
270
304
|
})
|
|
271
305
|
]
|
|
272
|
-
}) : /* @__PURE__ */
|
|
306
|
+
}) : /* @__PURE__ */ f(e, { message: T("billing.quotaAssignFree.noPermission", "You don't have permission to access this page.") });
|
|
273
307
|
};
|
|
274
308
|
//#endregion
|
|
275
|
-
export {
|
|
309
|
+
export { w as QuotaAssignFreePage };
|
|
276
310
|
|
|
277
311
|
//# sourceMappingURL=QuotaAssignFreePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/quota/pages/QuotaAssignFreePage.tsx"],"sourcesContent":["/**\n * Quota Module - Assign Quota Free (Admin only)\n *\n * Internal tool for assigning a single quota template directly to a billing\n * account (and, for workspace-scoped quotas, a workspace) at no charge and\n * without purchasing a whole plan. Protected by the same team secret as\n * \"Assign Plan Free\" — never expose to end users.\n *\n * Route: /billing/quota/assign-free\n */\n\nimport { useMemo, 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 { useAssignQuotaFreeMutation } from '../../../../generated/global-operations';\nimport { useQuotaTemplates } from '../hooks';\n\nexport const QuotaAssignFreePage: 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 [quotaId, setQuotaId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [limit, setLimit] = useState('');\n const [durationDays, setDurationDays] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n const { quotas, isLoading: isQuotasLoading } = useQuotaTemplates({ onlyActive: true });\n const [assignQuotaFree, { loading: isAssigning }] = useAssignQuotaFreeMutation();\n\n // Scope comes from the quota template itself, not an independent toggle —\n // this mirrors the backend, which routes on quota.scope and ignores any\n // client-supplied scope, so the UI can't drift from what will actually happen.\n const selectedQuota = quotas.find((q) => q.id === quotaId);\n const isWorkspaceScoped = selectedQuota?.scope === 'WORKSPACE';\n\n const defaultLimit = useMemo(() => {\n const limits = selectedQuota?.limits as { value?: number } | undefined;\n return limits?.value;\n }, [selectedQuota]);\n\n if (!permissions.canManageQuotas) {\n return (\n <AccessDenied\n message={tr(\n 'billing.quotaAssignFree.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() || !quotaId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignQuotaFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n quotaId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n ...(limit.trim() ? { limit: Number(limit) } : {}),\n ...(durationDays.trim() ? { durationDays: Number(durationDays) } : {}),\n },\n });\n\n if (data?.assignQuotaFree) {\n const assigned = data.assignQuotaFree;\n setResult({\n success: true,\n message:\n `Quota \"${assigned.quotaName}\" assigned (${assigned.scope}). Limit: ${assigned.limit ?? 'default'}` +\n (assigned.workspaceId ? ` · Workspace: ${assigned.workspaceId}` : '') +\n (assigned.endtime\n ? ` · Valid until: ${new Date(assigned.endtime).toLocaleDateString()}`\n : ''),\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setQuotaId('');\n setWorkspaceId('');\n setLimit('');\n setDurationDays('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign quota.';\n setResult({ success: false, message: msg });\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.quotaAssignFree.title', 'Assign Quota for Free')}\n description={tr(\n 'billing.quotaAssignFree.description',\n 'Internally assign a single quota to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/quota')}\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 Quotas\n </button>\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.quotaAssignFree.purpose',\n 'An internal tool to grant a single quota to a billing account without assigning a whole plan — useful for one-off comps or fixing a missing entitlement. 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 quota is granted immediately with no charge. Use\n only for enterprise deals, comped accounts, or fixing a missing entitlement.\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 Same team secret used by Assign Plan Free. 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 {/* Quota selector */}\n <div>\n <label htmlFor=\"quotaId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Quota <span className=\"text-status-error-text\">*</span>\n </label>\n {isQuotasLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"quotaId\"\n value={quotaId}\n onChange={(e) => {\n setQuotaId(e.target.value);\n setWorkspaceId('');\n }}\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 quota…\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name} ({quota.scope ?? 'WORKSPACE'})\n </option>\n ))}\n </select>\n )}\n {selectedQuota && (\n <p className=\"text-xs text-text-muted mt-1\">\n Scope: {selectedQuota.scope ?? 'WORKSPACE'} · Type: {selectedQuota.quotaType}\n {defaultLimit != null ? ` · Default limit: ${defaultLimit}` : ''}\n </p>\n )}\n </div>\n\n {/* Workspace ID — required for WORKSPACE-scoped quotas, hidden otherwise */}\n {isWorkspaceScoped && (\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 This quota is workspace-scoped. If blank, the first active workspace for this org is\n used automatically.\n </p>\n </div>\n )}\n\n {/* Limit override (optional) */}\n <div>\n <label htmlFor=\"limit\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Limit <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"limit\"\n type=\"number\"\n value={limit}\n onChange={(e) => setLimit(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={\n defaultLimit != null\n ? `Leave blank to use default (${defaultLimit})`\n : 'Leave blank to use the quota default'\n }\n />\n </div>\n\n {/* Duration in days (optional, GLOBAL scope only) */}\n {!isWorkspaceScoped && selectedQuota && (\n <div>\n <label\n htmlFor=\"durationDays\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Duration (days) <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"durationDays\"\n type=\"number\"\n min={1}\n value={durationDays}\n onChange={(e) => setDurationDays(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=\"Leave blank for 365 days\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Ignored if this quota attaches to an existing subscription — it then expires with\n that subscription instead.\n </p>\n </div>\n )}\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 || !quotaId}\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 Quota for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setQuotaId('');\n setWorkspaceId('');\n setLimit('');\n setDurationDays('');\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":"QuotaAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/quota/pages/QuotaAssignFreePage.tsx"],"sourcesContent":["/**\n * Quota Module - Assign Quota Free (Admin only)\n *\n * Internal tool for assigning a single quota template directly to a billing\n * account (and, for workspace-scoped quotas, a workspace) at no charge and\n * without purchasing a whole plan. Protected by the same team secret as\n * \"Assign Plan Free\" — never expose to end users.\n *\n * Route: /billing/quota/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 { useAssignQuotaFreeMutation } from '../../../../generated/global-operations';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useQuotas } from '../../plans/hooks';\nimport { filterQuotas, getQuotaScopeLabel } from '../utils/quotaSelector';\n\nexport const QuotaAssignFreePage: 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 [quotaId, setQuotaId] = useState('');\n const [quotaPickerOpen, setQuotaPickerOpen] = useState(false);\n const [quotaSearch, setQuotaSearch] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [limit, setLimit] = useState('');\n const [durationDays, setDurationDays] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const [assignQuotaFree, { loading: isAssigning }] = useAssignQuotaFreeMutation();\n\n // Scope comes from the quota template itself, not an independent toggle —\n // this mirrors the backend, which routes on quota.scope and ignores any\n // client-supplied scope, so the UI can't drift from what will actually happen.\n const selectedQuota = quotas.find((q) => q.id === quotaId);\n const isWorkspaceScoped = selectedQuota?.scope === 'WORKSPACE';\n\n const filteredQuotas = useMemo(() => filterQuotas(quotas, quotaSearch), [quotas, quotaSearch]);\n\n const defaultLimit = useMemo(() => {\n const limits = selectedQuota?.limits as { value?: number } | undefined;\n return limits?.value;\n }, [selectedQuota]);\n\n const handleQuotaSelect = (nextQuotaId: string) => {\n setQuotaId(nextQuotaId);\n setWorkspaceId('');\n setQuotaPickerOpen(false);\n setQuotaSearch('');\n };\n\n if (!permissions.canManageQuotas) {\n return (\n <AccessDenied\n message={tr(\n 'billing.quotaAssignFree.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() || !quotaId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignQuotaFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n quotaId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n ...(limit.trim() ? { limit: Number(limit) } : {}),\n ...(durationDays.trim() ? { durationDays: Number(durationDays) } : {}),\n },\n });\n\n if (data?.assignQuotaFree) {\n const assigned = data.assignQuotaFree;\n setResult({\n success: true,\n message:\n `Quota \"${assigned.quotaName}\" assigned (${assigned.scope}). Limit: ${assigned.limit ?? 'default'}` +\n (assigned.workspaceId ? ` · Workspace: ${assigned.workspaceId}` : '') +\n (assigned.endtime\n ? ` · Valid until: ${new Date(assigned.endtime).toLocaleDateString()}`\n : ''),\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setQuotaId('');\n setQuotaPickerOpen(false);\n setQuotaSearch('');\n setWorkspaceId('');\n setLimit('');\n setDurationDays('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign quota.';\n setResult({ success: false, message: msg });\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.quotaAssignFree.title', 'Assign Quota for Free')}\n description={tr(\n 'billing.quotaAssignFree.description',\n 'Internally assign a single quota to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/quota')}\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 Quotas\n </button>\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.quotaAssignFree.purpose',\n 'An internal tool to grant a single quota to a billing account without assigning a whole plan — useful for one-off comps or fixing a missing entitlement. 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 quota is granted immediately with no charge. Use\n only for enterprise deals, comped accounts, or fixing a missing entitlement.\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 Same team secret used by Assign Plan Free. 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 {/* Quota selector */}\n <div>\n <label htmlFor=\"quotaId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Quota <span className=\"text-status-error-text\">*</span>\n </label>\n {isQuotasLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <Popover\n open={quotaPickerOpen}\n onOpenChange={(open) => {\n setQuotaPickerOpen(open);\n if (!open) {\n setQuotaSearch('');\n }\n }}\n >\n <PopoverTrigger asChild>\n <button\n id=\"quotaId\"\n type=\"button\"\n role=\"combobox\"\n aria-expanded={quotaPickerOpen}\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={selectedQuota ? 'truncate' : 'truncate text-text-muted'}>\n {selectedQuota\n ? `${selectedQuota.name} (${getQuotaScopeLabel(selectedQuota)})`\n : 'Select a quota…'}\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={quotaSearch}\n onValueChange={setQuotaSearch}\n placeholder=\"Search quotas by name or ID…\"\n />\n <CommandList>\n <CommandEmpty>No matching quotas found.</CommandEmpty>\n {filteredQuotas.map((quota) => (\n <CommandItem\n key={quota.id}\n value={quota.id}\n onSelect={() => handleQuotaSelect(quota.id)}\n >\n <Check\n className={`size-4 shrink-0 ${\n quota.id === quotaId ? 'opacity-100' : 'opacity-0'\n }`}\n aria-hidden=\"true\"\n />\n <span className=\"truncate\">{quota.name}</span>\n <span className=\"ml-auto text-xs text-text-muted\">\n {getQuotaScopeLabel(quota)}\n </span>\n </CommandItem>\n ))}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )}\n {selectedQuota && (\n <p className=\"text-xs text-text-muted mt-1\">\n Scope: {getQuotaScopeLabel(selectedQuota)} · Type: {selectedQuota.quotaType}\n {defaultLimit != null ? ` · Default limit: ${defaultLimit}` : ''}\n </p>\n )}\n </div>\n\n {/* Workspace ID — required for WORKSPACE-scoped quotas, hidden otherwise */}\n {isWorkspaceScoped && (\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 This quota is workspace-scoped. If blank, the first active workspace for this org is\n used automatically.\n </p>\n </div>\n )}\n\n {/* Limit override (optional) */}\n <div>\n <label htmlFor=\"limit\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Limit <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"limit\"\n type=\"number\"\n value={limit}\n onChange={(e) => setLimit(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={\n defaultLimit != null\n ? `Leave blank to use default (${defaultLimit})`\n : 'Leave blank to use the quota default'\n }\n />\n </div>\n\n {/* Duration in days (optional, GLOBAL scope only) */}\n {!isWorkspaceScoped && selectedQuota && (\n <div>\n <label\n htmlFor=\"durationDays\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Duration (days) <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"durationDays\"\n type=\"number\"\n min={1}\n value={durationDays}\n onChange={(e) => setDurationDays(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=\"Leave blank for 365 days\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Ignored if this quota attaches to an existing subscription — it then expires with\n that subscription instead.\n </p>\n </div>\n )}\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 || !quotaId}\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 Quota for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setQuotaId('');\n setWorkspaceId('');\n setLimit('');\n setDurationDays('');\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,15 @@
|
|
|
1
|
+
//#region src/billing/modules/quota/utils/quotaSelector.ts
|
|
2
|
+
var e = (e) => e?.toLowerCase() ?? "", t = (e) => e.scope ?? "WORKSPACE";
|
|
3
|
+
function n(n, r) {
|
|
4
|
+
let i = r.trim().toLowerCase();
|
|
5
|
+
return i ? n.filter((n) => [
|
|
6
|
+
n.name,
|
|
7
|
+
n.id,
|
|
8
|
+
t(n),
|
|
9
|
+
n.quotaType
|
|
10
|
+
].some((t) => e(t).includes(i))) : [...n];
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { n as filterQuotas, t as getQuotaScopeLabel };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=quotaSelector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quotaSelector.js","names":[],"sources":["../../../../../src/billing/modules/quota/utils/quotaSelector.ts"],"sourcesContent":["import type { Quota } from '../../../shared/types';\n\nconst getSearchableValue = (value: string | null | undefined): string => value?.toLowerCase() ?? '';\n\nexport const getQuotaScopeLabel = (quota: Pick<Quota, 'scope'>): string =>\n quota.scope ?? 'WORKSPACE';\n\nexport function filterQuotas(quotas: readonly Quota[], search: string): Quota[] {\n const normalizedSearch = search.trim().toLowerCase();\n\n if (!normalizedSearch) {\n return [...quotas];\n }\n\n return quotas.filter((quota) =>\n [quota.name, quota.id, getQuotaScopeLabel(quota), quota.quotaType].some((value) =>\n getSearchableValue(value).includes(normalizedSearch)\n )\n );\n}\n"],"mappings":";AAEA,IAAM,KAAsB,MAA6C,GAAO,YAAY,KAAK,IAEpF,KAAsB,MACjC,EAAM,SAAS;AAEjB,SAAgB,EAAa,GAA0B,GAAyB;CAC9E,IAAM,IAAmB,EAAO,KAAK,EAAE,YAAY;CAMnD,OAJK,IAIE,EAAO,QAAQ,MACpB;EAAC,EAAM;EAAM,EAAM;EAAI,EAAmB,CAAK;EAAG,EAAM;CAAS,EAAE,MAAM,MACvE,EAAmB,CAAK,EAAE,SAAS,CAAgB,CACrD,CACF,IAPS,CAAC,GAAG,CAAM;AAQrB"}
|
|
@@ -5,6 +5,7 @@ var n = {}, r = t`
|
|
|
5
5
|
fragment AddonFields on Addon {
|
|
6
6
|
id
|
|
7
7
|
productID
|
|
8
|
+
nativeProductId
|
|
8
9
|
name
|
|
9
10
|
price
|
|
10
11
|
currency
|
|
@@ -422,6 +423,7 @@ var y = t`
|
|
|
422
423
|
fragment PlanFields on Plan {
|
|
423
424
|
id
|
|
424
425
|
productID
|
|
426
|
+
nativeProductId
|
|
425
427
|
name
|
|
426
428
|
price
|
|
427
429
|
currency
|