@burdenoff/microfe-billing 2026.1005.2 → 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.
@@ -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 { useQuotaTemplates as a } from "../hooks/useQuotaTemplates.js";
8
- import "../hooks/index.js";
9
- import { useMemo as o, useState as s } from "react";
10
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
11
- import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
- import { PagePurpose as d } from "@burdenoff/fe-libs/ui";
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 f = () => {
15
- let { t: f } = u(), p = (e, t) => {
16
- let n = f(e);
16
+ var w = () => {
17
+ let { t: w } = m(), T = (e, t) => {
18
+ let n = w(e);
17
19
  return n === e ? t : n;
18
- }, m = i(), h = n(), [g, _] = s(""), [v, y] = s(""), [b, x] = s(""), [S, C] = s(""), [w, T] = s(""), [E, D] = s(""), [O, k] = s(null), { quotas: A, isLoading: j } = a({ onlyActive: !0 }), [M, { loading: N }] = r(), P = A.find((e) => e.id === b), F = P?.scope === "WORKSPACE", I = o(() => P?.limits?.value, [P]);
19
- return h.canManageQuotas ? /* @__PURE__ */ l("div", {
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__ */ c(t, {
23
- title: p("billing.quotaAssignFree.title", "Assign Quota for Free"),
24
- description: p("billing.quotaAssignFree.description", "Internally assign a single quota to a billing account at no charge. Requires the team secret."),
25
- actions: /* @__PURE__ */ c("button", {
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: () => m("/quota"),
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__ */ c(d, { children: p("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.") }),
33
- /* @__PURE__ */ l("div", {
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__ */ c("svg", {
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__ */ c("path", {
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__ */ l("div", { children: [/* @__PURE__ */ c("p", {
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__ */ c("p", {
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__ */ l("form", {
58
+ /* @__PURE__ */ p("form", {
55
59
  onSubmit: async (e) => {
56
- if (e.preventDefault(), k(null), !g.trim() || !v.trim() || !b) {
57
- k({
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 M({ variables: {
65
- secret: g.trim(),
66
- billingAccountId: v.trim(),
67
- quotaId: b,
68
- ...S.trim() ? { workspaceId: S.trim() } : {},
69
- ...w.trim() ? { limit: Number(w) } : {},
70
- ...E.trim() ? { durationDays: Number(E) } : {}
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
- k({
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
- }), _(""), y(""), x(""), C(""), T(""), D("");
81
+ }), k(""), j(""), N(""), F(!1), L(""), z(""), V(""), U("");
78
82
  }
79
83
  } catch (e) {
80
- k({
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__ */ c("div", {
92
+ /* @__PURE__ */ f("div", {
89
93
  className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
90
- children: /* @__PURE__ */ c("h2", {
94
+ children: /* @__PURE__ */ f("h2", {
91
95
  className: "font-semibold text-text-primary",
92
96
  children: "Assignment Details"
93
97
  })
94
98
  }),
95
- /* @__PURE__ */ l("div", {
99
+ /* @__PURE__ */ p("div", {
96
100
  className: "p-4 space-y-4",
97
101
  children: [
98
- /* @__PURE__ */ l("div", { children: [
99
- /* @__PURE__ */ l("label", {
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__ */ c("span", {
106
+ children: ["Team Secret ", /* @__PURE__ */ f("span", {
103
107
  className: "text-status-error-text",
104
108
  children: "*"
105
109
  })]
106
110
  }),
107
- /* @__PURE__ */ c("input", {
111
+ /* @__PURE__ */ f("input", {
108
112
  id: "secret",
109
113
  type: "password",
110
- value: g,
111
- onChange: (e) => _(e.target.value),
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__ */ c("p", {
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__ */ l("div", { children: [
123
- /* @__PURE__ */ l("label", {
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__ */ c("span", {
130
+ children: ["Billing Account ID ", /* @__PURE__ */ f("span", {
127
131
  className: "text-status-error-text",
128
132
  children: "*"
129
133
  })]
130
134
  }),
131
- /* @__PURE__ */ c("input", {
135
+ /* @__PURE__ */ f("input", {
132
136
  id: "billingAccountId",
133
137
  type: "text",
134
- value: v,
135
- onChange: (e) => y(e.target.value),
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__ */ c("p", {
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__ */ l("div", { children: [
146
- /* @__PURE__ */ l("label", {
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__ */ c("span", {
153
+ children: ["Quota ", /* @__PURE__ */ f("span", {
150
154
  className: "text-status-error-text",
151
155
  children: "*"
152
156
  })]
153
157
  }),
154
- j ? /* @__PURE__ */ c("div", { className: "h-10 bg-bg-sunken animate-pulse rounded-md" }) : /* @__PURE__ */ l("select", {
155
- id: "quotaId",
156
- value: b,
157
- onChange: (e) => {
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
- 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)]",
161
- required: !0,
162
- children: [/* @__PURE__ */ c("option", {
163
- value: "",
164
- disabled: !0,
165
- children: "Select a quota…"
166
- }), A.map((e) => /* @__PURE__ */ l("option", {
167
- value: e.id,
168
- children: [
169
- e.name,
170
- " (",
171
- e.scope ?? "WORKSPACE",
172
- ")"
173
- ]
174
- }, e.id))]
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
- P && /* @__PURE__ */ l("p", {
210
+ X && /* @__PURE__ */ p("p", {
177
211
  className: "text-xs text-text-muted mt-1",
178
212
  children: [
179
213
  "Scope: ",
180
- P.scope ?? "WORKSPACE",
214
+ s(X),
181
215
  " · Type: ",
182
- P.quotaType,
183
- I == null ? "" : ` · Default limit: ${I}`
216
+ X.quotaType,
217
+ $ == null ? "" : ` · Default limit: ${$}`
184
218
  ]
185
219
  })
186
220
  ] }),
187
- F && /* @__PURE__ */ l("div", { children: [
188
- /* @__PURE__ */ l("label", {
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__ */ c("span", {
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__ */ c("input", {
230
+ /* @__PURE__ */ f("input", {
197
231
  id: "workspaceId",
198
232
  type: "text",
199
- value: S,
200
- onChange: (e) => C(e.target.value),
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__ */ c("p", {
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__ */ l("div", { children: [/* @__PURE__ */ l("label", {
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__ */ c("span", {
246
+ children: ["Limit ", /* @__PURE__ */ f("span", {
213
247
  className: "text-text-secondary font-normal",
214
248
  children: "(optional)"
215
249
  })]
216
- }), /* @__PURE__ */ c("input", {
250
+ }), /* @__PURE__ */ f("input", {
217
251
  id: "limit",
218
252
  type: "number",
219
- value: w,
220
- onChange: (e) => T(e.target.value),
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: I == null ? "Leave blank to use the quota default" : `Leave blank to use default (${I})`
256
+ placeholder: $ == null ? "Leave blank to use the quota default" : `Leave blank to use default (${$})`
223
257
  })] }),
224
- !F && P && /* @__PURE__ */ l("div", { children: [
225
- /* @__PURE__ */ l("label", {
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__ */ c("span", {
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__ */ c("input", {
267
+ /* @__PURE__ */ f("input", {
234
268
  id: "durationDays",
235
269
  type: "number",
236
270
  min: 1,
237
- value: E,
238
- onChange: (e) => D(e.target.value),
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__ */ c("p", {
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
- O && /* @__PURE__ */ c("div", {
250
- className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${O.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"}`,
251
- children: O.message
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__ */ l("div", {
287
+ /* @__PURE__ */ p("div", {
254
288
  className: "px-4 pb-4 flex items-center gap-3",
255
- children: [/* @__PURE__ */ c("button", {
289
+ children: [/* @__PURE__ */ f("button", {
256
290
  type: "submit",
257
- disabled: N || !g || !v || !b,
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: N ? "Assigning…" : "Assign Quota for Free"
260
- }), /* @__PURE__ */ c("button", {
293
+ children: Y ? "Assigning…" : "Assign Quota for Free"
294
+ }), /* @__PURE__ */ f("button", {
261
295
  type: "button",
262
296
  onClick: () => {
263
- _(""), y(""), x(""), C(""), T(""), D(""), k(null);
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__ */ c(e, { message: p("billing.quotaAssignFree.noPermission", "You don't have permission to access this page.") });
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 { f as QuotaAssignFreePage };
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.1005.2",
3
+ "version": "2026.1005.3",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [