@burdenoff/microfe-billing 2026.1005.4 → 2026.1005.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,214 +6,255 @@ import { useAssignPlanFreeMutation as r } from "../../../../generated/global-ope
6
6
  import { useBillingNavigate as i } from "../../../hooks/useBillingNavigate.js";
7
7
  import { usePlans as a } from "../hooks/usePlans.js";
8
8
  import "../hooks/index.js";
9
- import { useState as o } from "react";
10
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
11
- import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
- import { PagePurpose as u } from "@burdenoff/fe-libs/ui";
9
+ import { filterPlans as o } from "../utils/planSelector.js";
10
+ import { useMemo as s, useState as c } from "react";
11
+ import { Check as l, ChevronsUpDown as u } from "lucide-react";
12
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
13
+ import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
14
+ import { Command as m, CommandEmpty as h, CommandInput as g, CommandItem as _, CommandList as v, PagePurpose as y, Popover as b, PopoverContent as x, PopoverTrigger as S } from "@burdenoff/fe-libs/ui";
13
15
  //#region src/billing/modules/plans/pages/PlanAssignFreePage.tsx
14
- var d = () => {
15
- let { t: d } = l(), f = (e, t) => {
16
- let n = d(e);
16
+ var C = () => {
17
+ let { t: C } = p(), w = (e, t) => {
18
+ let n = C(e);
17
19
  return n === e ? t : n;
18
- }, p = i(), m = n(), [h, g] = o(""), [_, v] = o(""), [y, b] = o(""), [x, S] = o(""), [C, w] = o(null), { plans: T, isLoading: E } = a({ includeFree: !0 }), [D, { loading: O }] = r();
19
- if (!m.canManagePlans) return /* @__PURE__ */ s(e, { message: f("billing.assignFree.noPermission", "You don't have permission to access this page.") });
20
- let k = async (e) => {
21
- if (e.preventDefault(), w(null), !h.trim() || !_.trim() || !y) {
22
- w({
20
+ }, T = i(), E = n(), [D, O] = c(""), [k, A] = c(""), [j, M] = c(""), [N, P] = c(!1), [F, I] = c(""), [L, R] = c(""), [z, B] = c(null), { plans: V, isLoading: H } = a({ includeFree: !0 }), U = s(() => o(V, F), [V, F]), [W, { loading: G }] = r();
21
+ if (!E.canManagePlans) return /* @__PURE__ */ d(e, { message: w("billing.assignFree.noPermission", "You don't have permission to access this page.") });
22
+ let K = async (e) => {
23
+ if (e.preventDefault(), B(null), !D.trim() || !k.trim() || !j) {
24
+ B({
23
25
  success: !1,
24
26
  message: "All fields are required."
25
27
  });
26
28
  return;
27
29
  }
28
30
  try {
29
- let { data: e } = await D({ variables: {
30
- secret: h.trim(),
31
- billingAccountId: _.trim(),
32
- planId: y,
33
- ...x.trim() ? { workspaceId: x.trim() } : {}
31
+ let { data: e } = await W({ variables: {
32
+ secret: D.trim(),
33
+ billingAccountId: k.trim(),
34
+ planId: j,
35
+ ...L.trim() ? { workspaceId: L.trim() } : {}
34
36
  } });
35
37
  if (e?.assignPlanFree) {
36
38
  let t = e.assignPlanFree;
37
- w({
39
+ B({
38
40
  success: !0,
39
41
  message: `Plan assigned. Subscription ID: ${t.id} · Status: ${t.status} · Valid: ${new Date(t.startDate).toLocaleDateString()} → ${new Date(t.endDate).toLocaleDateString()}`
40
- }), g(""), v(""), b(""), S("");
42
+ }), O(""), A(""), M(""), P(!1), I(""), R("");
41
43
  }
42
44
  } catch (e) {
43
- w({
45
+ B({
44
46
  success: !1,
45
47
  message: e instanceof Error ? e.message : "Failed to assign plan."
46
48
  });
47
49
  }
48
- }, A = T.find((e) => e.id === y);
49
- return /* @__PURE__ */ c("div", {
50
+ }, q = V.find((e) => e.id === j), J = (e) => {
51
+ M(e), P(!1), I("");
52
+ };
53
+ return /* @__PURE__ */ f("div", {
50
54
  className: "space-y-6 p-6 max-w-2xl",
51
55
  children: [
52
- /* @__PURE__ */ s(t, {
53
- title: f("billing.assignFree.title", "Assign Plan for Free"),
54
- description: f("billing.assignFree.description", "Internally assign any plan to a billing account at no charge. Requires the team secret."),
55
- actions: /* @__PURE__ */ s("button", {
56
+ /* @__PURE__ */ d(t, {
57
+ title: w("billing.assignFree.title", "Assign Plan for Free"),
58
+ description: w("billing.assignFree.description", "Internally assign any plan to a billing account at no charge. Requires the team secret."),
59
+ actions: /* @__PURE__ */ d("button", {
56
60
  type: "button",
57
- onClick: () => p("/plans"),
61
+ onClick: () => T("/plans"),
58
62
  className: "px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors",
59
63
  children: "← Back to Plans"
60
64
  })
61
65
  }),
62
- /* @__PURE__ */ s(u, { children: f("billing.assignFree.purpose", "An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.") }),
63
- /* @__PURE__ */ c("div", {
66
+ /* @__PURE__ */ d(y, { children: w("billing.assignFree.purpose", "An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.") }),
67
+ /* @__PURE__ */ f("div", {
64
68
  className: "flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10",
65
- children: [/* @__PURE__ */ s("svg", {
69
+ children: [/* @__PURE__ */ d("svg", {
66
70
  className: "size-5 text-status-warning-text shrink-0 mt-0.5",
67
71
  fill: "none",
68
72
  viewBox: "0 0 24 24",
69
73
  stroke: "currentColor",
70
- children: /* @__PURE__ */ s("path", {
74
+ children: /* @__PURE__ */ d("path", {
71
75
  strokeLinecap: "round",
72
76
  strokeLinejoin: "round",
73
77
  strokeWidth: 2,
74
78
  d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z"
75
79
  })
76
- }), /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("p", {
80
+ }), /* @__PURE__ */ f("div", { children: [/* @__PURE__ */ d("p", {
77
81
  className: "text-sm font-medium text-status-warning-text",
78
82
  children: "Internal admin tool"
79
- }), /* @__PURE__ */ s("p", {
83
+ }), /* @__PURE__ */ d("p", {
80
84
  className: "text-xs text-status-warning-text/80 mt-0.5",
81
85
  children: "This bypasses all payment flows. The plan is assigned immediately with no charge. Use only for enterprise deals or internal accounts."
82
86
  })] })]
83
87
  }),
84
- /* @__PURE__ */ c("form", {
85
- onSubmit: k,
88
+ /* @__PURE__ */ f("form", {
89
+ onSubmit: K,
86
90
  className: "border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]",
87
91
  children: [
88
- /* @__PURE__ */ s("div", {
92
+ /* @__PURE__ */ d("div", {
89
93
  className: "p-4 border-b border-border-subtle bg-bg-sunken/30",
90
- children: /* @__PURE__ */ s("h2", {
94
+ children: /* @__PURE__ */ d("h2", {
91
95
  className: "font-semibold text-text-primary",
92
96
  children: "Assignment Details"
93
97
  })
94
98
  }),
95
- /* @__PURE__ */ c("div", {
99
+ /* @__PURE__ */ f("div", {
96
100
  className: "p-4 space-y-4",
97
101
  children: [
98
- /* @__PURE__ */ c("div", { children: [
99
- /* @__PURE__ */ c("label", {
102
+ /* @__PURE__ */ f("div", { children: [
103
+ /* @__PURE__ */ f("label", {
100
104
  htmlFor: "secret",
101
105
  className: "block text-sm font-medium text-text-primary mb-1",
102
- children: ["Team Secret ", /* @__PURE__ */ s("span", {
106
+ children: ["Team Secret ", /* @__PURE__ */ d("span", {
103
107
  className: "text-status-error-text",
104
108
  children: "*"
105
109
  })]
106
110
  }),
107
- /* @__PURE__ */ s("input", {
111
+ /* @__PURE__ */ d("input", {
108
112
  id: "secret",
109
113
  type: "password",
110
- value: h,
111
- onChange: (e) => g(e.target.value),
114
+ value: D,
115
+ onChange: (e) => O(e.target.value),
112
116
  autoComplete: "off",
113
117
  className: "w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono",
114
118
  placeholder: "Enter PLAN_ASSIGN_SECRET",
115
119
  required: !0
116
120
  }),
117
- /* @__PURE__ */ s("p", {
121
+ /* @__PURE__ */ d("p", {
118
122
  className: "text-xs text-text-muted mt-1",
119
123
  children: "The secret key shared within the team. Never expose this to users."
120
124
  })
121
125
  ] }),
122
- /* @__PURE__ */ c("div", { children: [
123
- /* @__PURE__ */ c("label", {
126
+ /* @__PURE__ */ f("div", { children: [
127
+ /* @__PURE__ */ f("label", {
124
128
  htmlFor: "billingAccountId",
125
129
  className: "block text-sm font-medium text-text-primary mb-1",
126
- children: ["Billing Account ID ", /* @__PURE__ */ s("span", {
130
+ children: ["Billing Account ID ", /* @__PURE__ */ d("span", {
127
131
  className: "text-status-error-text",
128
132
  children: "*"
129
133
  })]
130
134
  }),
131
- /* @__PURE__ */ s("input", {
135
+ /* @__PURE__ */ d("input", {
132
136
  id: "billingAccountId",
133
137
  type: "text",
134
- value: _,
135
- onChange: (e) => v(e.target.value),
138
+ value: k,
139
+ onChange: (e) => A(e.target.value),
136
140
  className: "w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm",
137
141
  placeholder: "e.g. ba_01jk3n2...",
138
142
  required: !0
139
143
  }),
140
- /* @__PURE__ */ s("p", {
144
+ /* @__PURE__ */ d("p", {
141
145
  className: "text-xs text-text-muted mt-1",
142
146
  children: "Find this in the user's billing account settings or via the billing accounts list."
143
147
  })
144
148
  ] }),
145
- /* @__PURE__ */ c("div", { children: [
146
- /* @__PURE__ */ c("label", {
149
+ /* @__PURE__ */ f("div", { children: [
150
+ /* @__PURE__ */ f("label", {
147
151
  htmlFor: "workspaceId",
148
152
  className: "block text-sm font-medium text-text-primary mb-1",
149
- children: ["Workspace ID ", /* @__PURE__ */ s("span", {
153
+ children: ["Workspace ID ", /* @__PURE__ */ d("span", {
150
154
  className: "text-text-secondary font-normal",
151
155
  children: "(optional)"
152
156
  })]
153
157
  }),
154
- /* @__PURE__ */ s("input", {
158
+ /* @__PURE__ */ d("input", {
155
159
  id: "workspaceId",
156
160
  type: "text",
157
- value: x,
158
- onChange: (e) => S(e.target.value),
161
+ value: L,
162
+ onChange: (e) => R(e.target.value),
159
163
  className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm",
160
164
  placeholder: "e.g. ws_01jk3n2… (leave blank to auto-resolve)"
161
165
  }),
162
- /* @__PURE__ */ s("p", {
166
+ /* @__PURE__ */ d("p", {
163
167
  className: "text-xs text-text-secondary mt-1",
164
168
  children: "If blank, the first active workspace for this org is used automatically. Set explicitly when the org has multiple workspaces."
165
169
  })
166
170
  ] }),
167
- /* @__PURE__ */ c("div", { children: [
168
- /* @__PURE__ */ c("label", {
171
+ /* @__PURE__ */ f("div", { children: [
172
+ /* @__PURE__ */ f("label", {
169
173
  htmlFor: "planId",
170
174
  className: "block text-sm font-medium text-text-primary mb-1",
171
- children: ["Plan ", /* @__PURE__ */ s("span", {
175
+ children: ["Plan ", /* @__PURE__ */ d("span", {
172
176
  className: "text-status-error-text",
173
177
  children: "*"
174
178
  })]
175
179
  }),
176
- E ? /* @__PURE__ */ s("div", { className: "h-10 bg-bg-sunken animate-pulse rounded-md" }) : /* @__PURE__ */ c("select", {
177
- id: "planId",
178
- value: y,
179
- onChange: (e) => b(e.target.value),
180
- 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)]",
181
- required: !0,
182
- children: [/* @__PURE__ */ s("option", {
183
- value: "",
184
- disabled: !0,
185
- children: "Select a plan…"
186
- }), T.map((e) => /* @__PURE__ */ c("option", {
187
- value: e.id,
188
- children: [
189
- e.name,
190
- e.isPurchasable ? "" : " (internal)",
191
- e.price != null && e.price > 0 ? ` — ${e.currency ?? "USD"} ${e.price}` : ""
192
- ]
193
- }, e.id))]
180
+ H ? /* @__PURE__ */ d("div", { className: "h-10 bg-bg-sunken animate-pulse rounded-md" }) : /* @__PURE__ */ f(b, {
181
+ open: N,
182
+ onOpenChange: (e) => {
183
+ P(e), e || I("");
184
+ },
185
+ children: [/* @__PURE__ */ d(S, {
186
+ asChild: !0,
187
+ children: /* @__PURE__ */ f("button", {
188
+ id: "planId",
189
+ type: "button",
190
+ role: "combobox",
191
+ "aria-expanded": N,
192
+ "aria-required": "true",
193
+ className: "w-full flex items-center justify-between gap-2 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-left text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
194
+ children: [/* @__PURE__ */ d("span", {
195
+ className: q ? "truncate" : "truncate text-text-muted",
196
+ children: q ? `${q.name}${q.productID ? ` · ${q.productID}` : ""}` : "Select a plan…"
197
+ }), /* @__PURE__ */ d(u, {
198
+ className: "size-4 shrink-0 text-text-muted",
199
+ "aria-hidden": "true"
200
+ })]
201
+ })
202
+ }), /* @__PURE__ */ d(x, {
203
+ align: "start",
204
+ className: "w-[var(--radix-popover-trigger-width)] p-0",
205
+ children: /* @__PURE__ */ f(m, {
206
+ shouldFilter: !1,
207
+ children: [/* @__PURE__ */ d(g, {
208
+ value: F,
209
+ onValueChange: I,
210
+ placeholder: "Search plans by name, product, or ID…"
211
+ }), /* @__PURE__ */ f(v, { children: [/* @__PURE__ */ d(h, { children: "No matching plans found." }), U.map((e) => /* @__PURE__ */ f(_, {
212
+ value: e.id,
213
+ onSelect: () => J(e.id),
214
+ children: [
215
+ /* @__PURE__ */ d(l, {
216
+ className: `size-4 shrink-0 ${e.id === j ? "opacity-100" : "opacity-0"}`,
217
+ "aria-hidden": "true"
218
+ }),
219
+ /* @__PURE__ */ f("span", {
220
+ className: "truncate",
221
+ children: [e.name, e.isPurchasable ? "" : " (internal)"]
222
+ }),
223
+ /* @__PURE__ */ f("span", {
224
+ className: "ml-auto text-xs text-text-muted",
225
+ children: [
226
+ e.productID,
227
+ " · ",
228
+ e.duration
229
+ ]
230
+ })
231
+ ]
232
+ }, e.id))] })]
233
+ })
234
+ })]
194
235
  }),
195
- A && /* @__PURE__ */ c("p", {
236
+ q && /* @__PURE__ */ f("p", {
196
237
  className: "text-xs text-text-muted mt-1",
197
- children: [A.isPurchasable ? "Public plan" : "Internal / non-purchasable plan", A.productID ? ` · Product: ${A.productID}` : ""]
238
+ children: [q.isPurchasable ? "Public plan" : "Internal / non-purchasable plan", q.productID ? ` · Product: ${q.productID}` : ""]
198
239
  })
199
240
  ] })
200
241
  ]
201
242
  }),
202
- C && /* @__PURE__ */ s("div", {
203
- className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${C.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"}`,
204
- children: C.message
243
+ z && /* @__PURE__ */ d("div", {
244
+ className: `mx-4 mb-4 p-3 rounded-lg border text-sm ${z.success ? "border-status-success-border bg-status-success-bg/10 text-status-success-text" : "border-status-error-border bg-status-error-bg/10 text-status-error-text"}`,
245
+ children: z.message
205
246
  }),
206
- /* @__PURE__ */ c("div", {
247
+ /* @__PURE__ */ f("div", {
207
248
  className: "px-4 pb-4 flex items-center gap-3",
208
- children: [/* @__PURE__ */ s("button", {
249
+ children: [/* @__PURE__ */ d("button", {
209
250
  type: "submit",
210
- disabled: O || !h || !_ || !y,
251
+ disabled: G || !D || !k || !j,
211
252
  className: "px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
212
- children: O ? "Assigning…" : "Assign Plan for Free"
213
- }), /* @__PURE__ */ s("button", {
253
+ children: G ? "Assigning…" : "Assign Plan for Free"
254
+ }), /* @__PURE__ */ d("button", {
214
255
  type: "button",
215
256
  onClick: () => {
216
- g(""), v(""), b(""), S(""), w(null);
257
+ O(""), A(""), M(""), P(!1), I(""), R(""), B(null);
217
258
  },
218
259
  className: "px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors",
219
260
  children: "Clear"
@@ -225,6 +266,6 @@ var d = () => {
225
266
  });
226
267
  };
227
268
  //#endregion
228
- export { d as PlanAssignFreePage };
269
+ export { C as PlanAssignFreePage };
229
270
 
230
271
  //# sourceMappingURL=PlanAssignFreePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { usePlans } from '../hooks';\n\nexport const PlanAssignFreePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.assignFree.purpose',\n 'An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.'\n )}\n </PagePurpose>\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label\n htmlFor=\"workspaceId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"planId\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n required\n >\n <option value=\"\" disabled>\n Select a plan…\n </option>\n {plans.map((plan) => (\n <option key={plan.id} value={plan.id}>\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n {plan.price != null && plan.price > 0\n ? ` — ${plan.currency ?? 'USD'} ${plan.price}`\n : ''}\n </option>\n ))}\n </select>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { Check, ChevronsUpDown } from 'lucide-react';\nimport { useMemo, useState, type FC, type FormEvent } from 'react';\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n PagePurpose,\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { usePlans } from '../hooks';\nimport { filterPlans } from '../utils/planSelector';\n\nexport const PlanAssignFreePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [planPickerOpen, setPlanPickerOpen] = useState(false);\n const [planSearch, setPlanSearch] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n const filteredPlans = useMemo(() => filterPlans(plans, planSearch), [plans, planSearch]);\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setPlanPickerOpen(false);\n setPlanSearch('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n const handlePlanSelect = (nextPlanId: string) => {\n setPlanId(nextPlanId);\n setPlanPickerOpen(false);\n setPlanSearch('');\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.assignFree.purpose',\n 'An internal tool to grant any plan to a billing account at no charge — useful for partners, internal teams or comped accounts. It bypasses payment, so it requires the team secret and should be used deliberately.'\n )}\n </PagePurpose>\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-seam rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label\n htmlFor=\"workspaceId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <Popover\n open={planPickerOpen}\n onOpenChange={(open) => {\n setPlanPickerOpen(open);\n if (!open) {\n setPlanSearch('');\n }\n }}\n >\n <PopoverTrigger asChild>\n <button\n id=\"planId\"\n type=\"button\"\n role=\"combobox\"\n aria-expanded={planPickerOpen}\n aria-required=\"true\"\n className=\"w-full flex items-center justify-between gap-2 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-left text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <span className={selectedPlan ? 'truncate' : 'truncate text-text-muted'}>\n {selectedPlan\n ? `${selectedPlan.name}${selectedPlan.productID ? ` · ${selectedPlan.productID}` : ''}`\n : 'Select a plan…'}\n </span>\n <ChevronsUpDown\n className=\"size-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-[var(--radix-popover-trigger-width)] p-0\"\n >\n <Command shouldFilter={false}>\n <CommandInput\n value={planSearch}\n onValueChange={setPlanSearch}\n placeholder=\"Search plans by name, product, or ID…\"\n />\n <CommandList>\n <CommandEmpty>No matching plans found.</CommandEmpty>\n {filteredPlans.map((plan) => (\n <CommandItem\n key={plan.id}\n value={plan.id}\n onSelect={() => handlePlanSelect(plan.id)}\n >\n <Check\n className={`size-4 shrink-0 ${\n plan.id === planId ? 'opacity-100' : 'opacity-0'\n }`}\n aria-hidden=\"true\"\n />\n <span className=\"truncate\">\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n </span>\n <span className=\"ml-auto text-xs text-text-muted\">\n {plan.productID} · {plan.duration}\n </span>\n </CommandItem>\n ))}\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\n className=\"px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setPlanPickerOpen(false);\n setPlanSearch('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;AA+BA,IAAa,UAA+B;CAC1C,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GAEM,IAAa,EAAmB,GAChC,IAAc,EAAsB,GAEpC,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,CAAC,GAAkB,KAAuB,EAAS,EAAE,GACrD,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,CAAC,GAAgB,KAAqB,EAAS,EAAK,GACpD,CAAC,GAAY,KAAiB,EAAS,EAAE,GACzC,CAAC,GAAa,KAAkB,EAAS,EAAE,GAC3C,CAAC,GAAQ,KAAa,EAAuD,IAAI,GAGjF,EAAE,UAAO,WAAW,MAAmB,EAAS,EAAE,aAAa,GAAK,CAAC,GACrE,IAAgB,QAAc,EAAY,GAAO,CAAU,GAAG,CAAC,GAAO,CAAU,CAAC,GAEjF,CAAC,GAAgB,EAAE,SAAS,OAAiB,EAA0B;CAE7E,IAAI,CAAC,EAAY,gBACf,OACE,kBAAC,GAAD,EACE,SAAS,EACP,mCACA,gDACF,EACD,CAAA;CAIL,IAAM,IAAe,OAAO,MAAiB;EAI3C,IAHA,EAAE,eAAe,GACjB,EAAU,IAAI,GAEV,CAAC,EAAO,KAAK,KAAK,CAAC,EAAiB,KAAK,KAAK,CAAC,GAAQ;GACzD,EAAU;IAAE,SAAS;IAAO,SAAS;GAA2B,CAAC;GACjE;EACF;EAEA,IAAI;GACF,IAAM,EAAE,YAAS,MAAM,EAAe,EACpC,WAAW;IACT,QAAQ,EAAO,KAAK;IACpB,kBAAkB,EAAiB,KAAK;IACxC;IACA,GAAI,EAAY,KAAK,IAAI,EAAE,aAAa,EAAY,KAAK,EAAE,IAAI,CAAC;GAClE,EACF,CAAC;GAED,IAAI,GAAM,gBAAgB;IACxB,IAAM,IAAM,EAAK;IAWjB,AAVA,EAAU;KACR,SAAS;KACT,SAAS,mCAAmC,EAAI,GAAG,aAAa,EAAI,OAAO,YAAY,IAAI,KAAK,EAAI,SAAS,EAAE,mBAAmB,EAAE,KAAK,IAAI,KAAK,EAAI,OAAO,EAAE,mBAAmB;IACpL,CAAC,GAED,EAAU,EAAE,GACZ,EAAoB,EAAE,GACtB,EAAU,EAAE,GACZ,EAAkB,EAAK,GACvB,EAAc,EAAE,GAChB,EAAe,EAAE;GACnB;EACF,SAAS,GAAK;GAEZ,EAAU;IAAE,SAAS;IAAO,SADhB,aAAe,QAAQ,EAAI,UAAU;GACR,CAAC;EAC5C;CACF,GAEM,IAAe,EAAM,MAAM,MAAM,EAAE,OAAO,CAAM,GAEhD,KAAoB,MAAuB;EAG/C,AAFA,EAAU,CAAU,GACpB,EAAkB,EAAK,GACvB,EAAc,EAAE;CAClB;CAEA,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,OAAO,EAAG,4BAA4B,sBAAsB;IAC5D,aAAa,EACX,kCACA,yFACF;IACA,SACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,QAAQ;KAClC,WAAU;eACX;IAEO,CAAA;GAEX,CAAA;GAED,kBAAC,GAAD,EAAA,UACG,EACC,8BACA,qNACF,EACW,CAAA;GAGb,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,SAAQ;KACR,QAAO;eAEP,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;KACH,CAAA;IACE,CAAA,GACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;KAAG,WAAU;eAA+C;IAAsB,CAAA,GAClF,kBAAC,KAAD;KAAG,WAAU;eAA6C;IAGvD,CAAA,CACA,EAAA,CAAA,CACF;;GAEL,kBAAC,QAAD;IACE,UAAU;IACV,WAAU;cAFZ;KAIE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAkC;MAAsB,CAAA;KACnE,CAAA;KAEL,kBAAC,OAAD;MAAK,WAAU;gBAAf;OAEE,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,SAAQ;SAAS,WAAU;mBAAlC,CAAqF,gBACvE,kBAAC,QAAD;UAAM,WAAU;oBAAyB;SAAO,CAAA,CACvD;;QACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAU,EAAE,OAAO,KAAK;SACzC,cAAa;SACb,WAAU;SACV,aAAY;SACZ,UAAA;QACD,CAAA;QACD,kBAAC,KAAD;SAAG,WAAU;mBAA+B;QAEzC,CAAA;OACA,EAAA,CAAA;OAGL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAFZ,CAGC,uBACoB,kBAAC,QAAD;UAAM,WAAU;oBAAyB;SAAO,CAAA,CAC9D;;QACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAoB,EAAE,OAAO,KAAK;SACnD,WAAU;SACV,aAAY;SACZ,UAAA;QACD,CAAA;QACD,kBAAC,KAAD;SAAG,WAAU;mBAA+B;QAEzC,CAAA;OACA,EAAA,CAAA;OAGL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBAFZ,CAGC,iBACc,kBAAC,QAAD;UAAM,WAAU;oBAAkC;SAAgB,CAAA,CAC1E;;QACP,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,KAAK;SAC9C,WAAU;SACV,aAAY;QACb,CAAA;QACD,kBAAC,KAAD;SAAG,WAAU;mBAAmC;QAG7C,CAAA;OACA,EAAA,CAAA;OAGL,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,SAAQ;SAAS,WAAU;mBAAlC,CAAqF,SAC9E,kBAAC,QAAD;UAAM,WAAU;oBAAyB;SAAO,CAAA,CAChD;;QACN,IACC,kBAAC,OAAD,EAAK,WAAU,6CAA8C,CAAA,IAE7D,kBAAC,GAAD;SACE,MAAM;SACN,eAAe,MAAS;UAEtB,AADA,EAAkB,CAAI,GACjB,KACH,EAAc,EAAE;SAEpB;mBAPF,CASE,kBAAC,GAAD;UAAgB,SAAA;oBACd,kBAAC,UAAD;WACE,IAAG;WACH,MAAK;WACL,MAAK;WACL,iBAAe;WACf,iBAAc;WACd,WAAU;qBANZ,CAQE,kBAAC,QAAD;YAAM,WAAW,IAAe,aAAa;sBAC1C,IACG,GAAG,EAAa,OAAO,EAAa,YAAY,MAAM,EAAa,cAAc,OACjF;WACA,CAAA,GACN,kBAAC,GAAD;YACE,WAAU;YACV,eAAY;WACb,CAAA,CACK;;SACM,CAAA,GAChB,kBAAC,GAAD;UACE,OAAM;UACN,WAAU;oBAEV,kBAAC,GAAD;WAAS,cAAc;qBAAvB,CACE,kBAAC,GAAD;YACE,OAAO;YACP,eAAe;YACf,aAAY;WACb,CAAA,GACD,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAc,2BAAsC,CAAA,GACnD,EAAc,KAAK,MAClB,kBAAC,GAAD;YAEE,OAAO,EAAK;YACZ,gBAAgB,EAAiB,EAAK,EAAE;sBAH1C;aAKE,kBAAC,GAAD;cACE,WAAW,mBACT,EAAK,OAAO,IAAS,gBAAgB;cAEvC,eAAY;aACb,CAAA;aACD,kBAAC,QAAD;cAAM,WAAU;wBAAhB,CACG,EAAK,MACJ,EAAK,gBAAgC,KAAhB,aACnB;;aACN,kBAAC,QAAD;cAAM,WAAU;wBAAhB;eACG,EAAK;eAAU;eAAI,EAAK;cACrB;;YACK;cAjBN,EAAK,EAiBC,CACd,CACU,EAAA,CAAA,CACN;;SACK,CAAA,CACT;;QAEV,KACC,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACG,EAAa,gBAAgB,gBAAgB,mCAC7C,EAAa,YAAY,eAAe,EAAa,cAAc,EACnE;;OAEF,EAAA,CAAA;MACF;;KAGJ,KACC,kBAAC,OAAD;MACE,WAAW,2CACT,EAAO,UACH,kFACA;gBAGL,EAAO;KACL,CAAA;KAGP,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,KAAe,CAAC,KAAU,CAAC,KAAoB,CAAC;OAC1D,WAAU;iBAET,IAAc,eAAe;MACxB,CAAA,GACR,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;QAOb,AANA,EAAU,EAAE,GACZ,EAAoB,EAAE,GACtB,EAAU,EAAE,GACZ,EAAkB,EAAK,GACvB,EAAc,EAAE,GAChB,EAAe,EAAE,GACjB,EAAU,IAAI;OAChB;OACA,WAAU;iBACX;MAEO,CAAA,CACL;;IACD;;EACH;;AAET"}
@@ -0,0 +1,16 @@
1
+ //#region src/billing/modules/plans/utils/planSelector.ts
2
+ var e = (e) => e?.toLowerCase() ?? "";
3
+ function t(t, n) {
4
+ let r = n.trim().toLowerCase();
5
+ return r ? t.filter((t) => [
6
+ t.name,
7
+ t.id,
8
+ t.productID,
9
+ t.product?.name,
10
+ t.duration
11
+ ].some((t) => e(t).includes(r))) : [...t];
12
+ }
13
+ //#endregion
14
+ export { t as filterPlans };
15
+
16
+ //# sourceMappingURL=planSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planSelector.js","names":[],"sources":["../../../../../src/billing/modules/plans/utils/planSelector.ts"],"sourcesContent":["import type { Plan } from '../../../shared/types';\n\nconst getSearchableValue = (value: string | null | undefined): string => value?.toLowerCase() ?? '';\n\nexport function filterPlans(plans: readonly Plan[], search: string): Plan[] {\n const normalizedSearch = search.trim().toLowerCase();\n\n if (!normalizedSearch) {\n return [...plans];\n }\n\n return plans.filter((plan) =>\n [plan.name, plan.id, plan.productID, plan.product?.name, plan.duration].some((value) =>\n getSearchableValue(value).includes(normalizedSearch)\n )\n );\n}\n"],"mappings":";AAEA,IAAM,KAAsB,MAA6C,GAAO,YAAY,KAAK;AAEjG,SAAgB,EAAY,GAAwB,GAAwB;CAC1E,IAAM,IAAmB,EAAO,KAAK,EAAE,YAAY;CAMnD,OAJK,IAIE,EAAM,QAAQ,MACnB;EAAC,EAAK;EAAM,EAAK;EAAI,EAAK;EAAW,EAAK,SAAS;EAAM,EAAK;CAAQ,EAAE,MAAM,MAC5E,EAAmB,CAAK,EAAE,SAAS,CAAgB,CACrD,CACF,IAPS,CAAC,GAAG,CAAK;AAQpB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.1005.4",
3
+ "version": "2026.1005.5",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [