@burdenoff/microfe-billing 2026.623.3 → 2026.624.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingUserRoutes.js +3 -3
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +34 -34
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +94 -94
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +162 -187
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +146 -153
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +10 -10
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +62 -62
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +164 -179
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +8 -8
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +243 -305
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +73 -78
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +173 -179
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +4 -4
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +181 -173
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +111 -99
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +68 -83
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/shared/components/AccessDenied.js +4 -4
- package/dist/billing/shared/components/AccessDenied.js.map +1 -1
- package/dist/billing/shared/components/ActorIdentity.js +8 -8
- package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +11 -11
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
- package/dist/billing/shared/ui/MetricCard.js +1 -1
- package/dist/billing/shared/ui/MetricCard.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,78 +12,77 @@ import { useActorProfiles as c } from "../../../shared/hooks/useActorProfiles.js
|
|
|
12
12
|
import "../../../shared/hooks/index.js";
|
|
13
13
|
import { useCallback as l, useEffect as u, useMemo as d, useState as f } from "react";
|
|
14
14
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
15
|
+
import { AlertDialog as h, AlertDialogAction as g, AlertDialogCancel as _, AlertDialogContent as v, AlertDialogDescription as y, AlertDialogFooter as b, AlertDialogHeader as x, AlertDialogTitle as S } from "@burdenoff/fe-libs/ui";
|
|
15
16
|
//#region src/billing/modules/settings/pages/TeamPermissionsPage.tsx
|
|
16
|
-
var
|
|
17
|
-
let { apiGatewayUrl:
|
|
18
|
-
orgId:
|
|
19
|
-
billingAccounts:
|
|
17
|
+
var C = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, w = "\n query BillingScopeRoles($scopeId: String!) {\n roles(scopeId: $scopeId) {\n id\n name\n description\n priority\n isSystem\n }\n }\n", T = "\n query BillingScopeActors($scopeId: String!) {\n actors(scopeId: $scopeId) {\n actorId\n actorType\n roleAssignments {\n id\n actorId\n actorType\n roleId\n scopeId\n isActive\n expiresAt\n role { id name priority }\n }\n }\n }\n", E = "\n mutation AssignBillingRole($input: AssignRoleInput!) {\n assignRoleToActor(input: $input) {\n id\n actorId\n roleId\n scopeId\n }\n }\n", D = "\n mutation RemoveBillingRole($assignmentId: String!) {\n removeRoleFromActor(assignmentId: $assignmentId)\n }\n", O = () => {
|
|
18
|
+
let { apiGatewayUrl: O, authToken: k, orgId: A } = e(), { billingAccounts: j } = s(), { selectedAccountId: M } = i({
|
|
19
|
+
orgId: A,
|
|
20
|
+
billingAccounts: j,
|
|
20
21
|
syncFromUrl: !0,
|
|
21
22
|
urlParamName: "billingAccountId"
|
|
22
|
-
}),
|
|
23
|
-
apiGatewayUrl:
|
|
24
|
-
authToken:
|
|
25
|
-
orgId:
|
|
23
|
+
}), N = M ?? void 0, P = o({ billingAccountIdOverride: N }), [F, I] = f([]), [L, R] = f([]), [z, B] = f(!1), [V, H] = f(null), [U, W] = f(""), [G, K] = f(""), [q, J] = f(!1), [Y, X] = f(null), Z = l(async (e, t) => a({
|
|
24
|
+
apiGatewayUrl: O,
|
|
25
|
+
authToken: k,
|
|
26
|
+
orgId: A,
|
|
26
27
|
query: e,
|
|
27
28
|
variables: t
|
|
28
29
|
}), [
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
]),
|
|
33
|
-
if (
|
|
34
|
-
|
|
30
|
+
O,
|
|
31
|
+
k,
|
|
32
|
+
A
|
|
33
|
+
]), Q = l(async () => {
|
|
34
|
+
if (N) {
|
|
35
|
+
B(!0), H(null);
|
|
35
36
|
try {
|
|
36
|
-
let [e, t] = await Promise.all([
|
|
37
|
-
|
|
37
|
+
let [e, t] = await Promise.all([Z(w, { scopeId: N }), Z(T, { scopeId: N })]);
|
|
38
|
+
I(e.roles ?? []), R(t.actors ?? []), K((e.roles ?? []).find((e) => /viewer/i.test(e.name))?.id ?? e.roles?.[0]?.id ?? "");
|
|
38
39
|
} catch (e) {
|
|
39
|
-
|
|
40
|
+
H(e instanceof Error ? e.message : "Failed to load team data");
|
|
40
41
|
} finally {
|
|
41
|
-
|
|
42
|
+
B(!1);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
}, [
|
|
45
|
+
}, [N, Z]);
|
|
45
46
|
u(() => {
|
|
46
|
-
|
|
47
|
-
}, [
|
|
48
|
-
let
|
|
47
|
+
Q();
|
|
48
|
+
}, [Q]);
|
|
49
|
+
let $ = d(() => [...L].sort((e, t) => {
|
|
49
50
|
let n = Math.max(...e.roleAssignments.map((e) => e.role?.priority ?? 0), 0);
|
|
50
51
|
return Math.max(...t.roleAssignments.map((e) => e.role?.priority ?? 0), 0) - n;
|
|
51
|
-
}), [
|
|
52
|
+
}), [L]), ee = c(d(() => $.map((e) => ({
|
|
52
53
|
actorId: e.actorId,
|
|
53
54
|
actorType: e.actorType
|
|
54
|
-
})), [
|
|
55
|
-
if (
|
|
56
|
-
}, [
|
|
57
|
-
if (e.preventDefault(), !
|
|
58
|
-
let t =
|
|
59
|
-
if (!
|
|
60
|
-
|
|
55
|
+
})), [$])), te = d(() => {
|
|
56
|
+
if (N) return j.find((e) => e.id === N)?.name ?? void 0;
|
|
57
|
+
}, [N, j]), ne = async (e) => {
|
|
58
|
+
if (e.preventDefault(), !N || !U.trim() || !G) return;
|
|
59
|
+
let t = U.trim();
|
|
60
|
+
if (!C.test(t)) {
|
|
61
|
+
H("User ID must be a UUID (e.g. 1a2b3c4d-...). Get it from the user profile page.");
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
+
J(!0), H(null);
|
|
64
65
|
try {
|
|
65
|
-
await
|
|
66
|
+
await Z(E, { input: {
|
|
66
67
|
actorId: t,
|
|
67
68
|
actorType: "USER",
|
|
68
|
-
roleId:
|
|
69
|
-
scopeId:
|
|
70
|
-
} }),
|
|
69
|
+
roleId: G,
|
|
70
|
+
scopeId: N
|
|
71
|
+
} }), W(""), await Q();
|
|
71
72
|
} catch (e) {
|
|
72
|
-
|
|
73
|
+
H(e instanceof Error ? e.message : "Failed to assign role");
|
|
73
74
|
} finally {
|
|
74
|
-
|
|
75
|
+
J(!1);
|
|
75
76
|
}
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
P(e instanceof Error ? e.message : "Failed to remove role");
|
|
83
|
-
}
|
|
77
|
+
}, re = async (e) => {
|
|
78
|
+
H(null);
|
|
79
|
+
try {
|
|
80
|
+
await Z(D, { assignmentId: e }), await Q();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
H(e instanceof Error ? e.message : "Failed to remove role");
|
|
84
83
|
}
|
|
85
84
|
};
|
|
86
|
-
return
|
|
85
|
+
return P.canManageBillingAccount ? N ? /* @__PURE__ */ m("div", {
|
|
87
86
|
className: "space-y-6",
|
|
88
87
|
children: [
|
|
89
88
|
/* @__PURE__ */ p(n, {
|
|
@@ -91,39 +90,39 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
91
90
|
description: "Control who can manage this billing account and what they can do."
|
|
92
91
|
}),
|
|
93
92
|
/* @__PURE__ */ m("div", {
|
|
94
|
-
className: "rounded-lg border border-border bg-
|
|
93
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm",
|
|
95
94
|
children: [
|
|
96
95
|
/* @__PURE__ */ p("span", {
|
|
97
|
-
className: "font-medium text-
|
|
96
|
+
className: "font-medium text-text-primary",
|
|
98
97
|
children: "Billing account:"
|
|
99
98
|
}),
|
|
100
99
|
" ",
|
|
101
100
|
/* @__PURE__ */ p("span", {
|
|
102
|
-
className: "text-
|
|
103
|
-
children:
|
|
101
|
+
className: "text-text-primary",
|
|
102
|
+
children: te ?? "Unnamed account"
|
|
104
103
|
}),
|
|
105
|
-
|
|
106
|
-
className: "mt-2 text-xs text-
|
|
104
|
+
N ? /* @__PURE__ */ m("details", {
|
|
105
|
+
className: "mt-2 text-xs text-text-secondary",
|
|
107
106
|
children: [/* @__PURE__ */ p("summary", {
|
|
108
107
|
className: "cursor-pointer",
|
|
109
108
|
children: "Account ID"
|
|
110
109
|
}), /* @__PURE__ */ p("span", {
|
|
111
110
|
className: "mt-1 inline-block font-mono",
|
|
112
|
-
children:
|
|
111
|
+
children: N
|
|
113
112
|
})]
|
|
114
113
|
}) : null
|
|
115
114
|
]
|
|
116
115
|
}),
|
|
117
|
-
|
|
118
|
-
className: "rounded-lg border border-
|
|
119
|
-
children:
|
|
116
|
+
V && /* @__PURE__ */ p("div", {
|
|
117
|
+
className: "rounded-lg border border-status-error-border/30 bg-status-error-bg/5 p-4 text-sm text-status-error-text",
|
|
118
|
+
children: V
|
|
120
119
|
}),
|
|
121
120
|
/* @__PURE__ */ m("form", {
|
|
122
|
-
onSubmit: (e) => void
|
|
123
|
-
className: "rounded-lg border border-border bg-
|
|
121
|
+
onSubmit: (e) => void ne(e),
|
|
122
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4 space-y-3",
|
|
124
123
|
children: [
|
|
125
124
|
/* @__PURE__ */ p("h3", {
|
|
126
|
-
className: "text-sm font-semibold text-
|
|
125
|
+
className: "text-sm font-semibold text-text-primary",
|
|
127
126
|
children: "Add member"
|
|
128
127
|
}),
|
|
129
128
|
/* @__PURE__ */ m("div", {
|
|
@@ -133,34 +132,34 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
133
132
|
children: [
|
|
134
133
|
/* @__PURE__ */ p("label", {
|
|
135
134
|
htmlFor: "newActorId",
|
|
136
|
-
className: "block text-xs font-medium text-
|
|
135
|
+
className: "block text-xs font-medium text-text-secondary",
|
|
137
136
|
children: "User ID"
|
|
138
137
|
}),
|
|
139
138
|
/* @__PURE__ */ p("input", {
|
|
140
139
|
id: "newActorId",
|
|
141
140
|
type: "text",
|
|
142
|
-
value:
|
|
143
|
-
onChange: (e) =>
|
|
141
|
+
value: U,
|
|
142
|
+
onChange: (e) => W(e.target.value),
|
|
144
143
|
placeholder: "Paste the user ID from their profile page",
|
|
145
|
-
className: "mt-1 w-full rounded-md border border-border bg-
|
|
144
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm",
|
|
146
145
|
required: !0
|
|
147
146
|
}),
|
|
148
147
|
/* @__PURE__ */ p("p", {
|
|
149
|
-
className: "mt-1 text-[11px] text-
|
|
148
|
+
className: "mt-1 text-[11px] text-text-secondary",
|
|
150
149
|
children: "Find this on the user's profile page."
|
|
151
150
|
})
|
|
152
151
|
]
|
|
153
152
|
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
|
|
154
153
|
htmlFor: "newRoleId",
|
|
155
|
-
className: "block text-xs font-medium text-
|
|
154
|
+
className: "block text-xs font-medium text-text-secondary",
|
|
156
155
|
children: "Role"
|
|
157
156
|
}), /* @__PURE__ */ p("select", {
|
|
158
157
|
id: "newRoleId",
|
|
159
|
-
value:
|
|
160
|
-
onChange: (e) =>
|
|
161
|
-
className: "mt-1 w-full rounded-md border border-border bg-
|
|
158
|
+
value: G,
|
|
159
|
+
onChange: (e) => K(e.target.value),
|
|
160
|
+
className: "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm",
|
|
162
161
|
required: !0,
|
|
163
|
-
children:
|
|
162
|
+
children: F.map((e) => /* @__PURE__ */ p("option", {
|
|
164
163
|
value: e.id,
|
|
165
164
|
children: e.name
|
|
166
165
|
}, e.id))
|
|
@@ -168,33 +167,33 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
168
167
|
}),
|
|
169
168
|
/* @__PURE__ */ p("button", {
|
|
170
169
|
type: "submit",
|
|
171
|
-
disabled:
|
|
172
|
-
className: "rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-
|
|
173
|
-
children:
|
|
170
|
+
disabled: q || !U.trim() || !G,
|
|
171
|
+
className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text disabled:opacity-50",
|
|
172
|
+
children: q ? "Adding…" : "Add member"
|
|
174
173
|
})
|
|
175
174
|
]
|
|
176
175
|
}),
|
|
177
176
|
/* @__PURE__ */ m("div", {
|
|
178
|
-
className: "rounded-lg border border-border bg-
|
|
177
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface",
|
|
179
178
|
children: [/* @__PURE__ */ m("div", {
|
|
180
|
-
className: "border-b border-border px-4 py-3",
|
|
179
|
+
className: "border-b border-border-subtle px-4 py-3",
|
|
181
180
|
children: [/* @__PURE__ */ p("h3", {
|
|
182
|
-
className: "text-sm font-semibold text-
|
|
181
|
+
className: "text-sm font-semibold text-text-primary",
|
|
183
182
|
children: "Members"
|
|
184
183
|
}), /* @__PURE__ */ p("p", {
|
|
185
|
-
className: "text-xs text-
|
|
184
|
+
className: "text-xs text-text-secondary",
|
|
186
185
|
children: "Each member's role applies only to this billing account."
|
|
187
186
|
})]
|
|
188
|
-
}),
|
|
189
|
-
className: "p-6 text-sm text-
|
|
187
|
+
}), z ? /* @__PURE__ */ p("div", {
|
|
188
|
+
className: "p-6 text-sm text-text-secondary",
|
|
190
189
|
children: "Loading…"
|
|
191
|
-
}) :
|
|
192
|
-
className: "p-6 text-sm text-
|
|
190
|
+
}) : $.length === 0 ? /* @__PURE__ */ p("div", {
|
|
191
|
+
className: "p-6 text-sm text-text-secondary",
|
|
193
192
|
children: "No members assigned yet."
|
|
194
193
|
}) : /* @__PURE__ */ m("table", {
|
|
195
194
|
className: "w-full text-sm",
|
|
196
195
|
children: [/* @__PURE__ */ p("thead", { children: /* @__PURE__ */ m("tr", {
|
|
197
|
-
className: "border-b border-border text-left text-xs text-
|
|
196
|
+
className: "border-b border-border-subtle text-left text-xs text-text-secondary",
|
|
198
197
|
children: [
|
|
199
198
|
/* @__PURE__ */ p("th", {
|
|
200
199
|
className: "px-4 py-2",
|
|
@@ -213,13 +212,13 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
213
212
|
children: "Actions"
|
|
214
213
|
})
|
|
215
214
|
]
|
|
216
|
-
}) }), /* @__PURE__ */ p("tbody", { children:
|
|
217
|
-
className: "border-b border-border last:border-0",
|
|
215
|
+
}) }), /* @__PURE__ */ p("tbody", { children: $.map((e) => /* @__PURE__ */ m("tr", {
|
|
216
|
+
className: "border-b border-border-subtle last:border-0",
|
|
218
217
|
children: [
|
|
219
218
|
/* @__PURE__ */ p("td", {
|
|
220
219
|
className: "px-4 py-3",
|
|
221
220
|
children: /* @__PURE__ */ p(r, {
|
|
222
|
-
profile:
|
|
221
|
+
profile: ee[e.actorId] ?? {
|
|
223
222
|
actorId: e.actorId,
|
|
224
223
|
actorType: e.actorType,
|
|
225
224
|
displayName: e.actorType.toLowerCase() === "user" ? `User ${e.actorId.slice(0, 8)}…` : e.actorType,
|
|
@@ -230,7 +229,7 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
230
229
|
})
|
|
231
230
|
}),
|
|
232
231
|
/* @__PURE__ */ p("td", {
|
|
233
|
-
className: "px-4 py-3 text-xs capitalize text-
|
|
232
|
+
className: "px-4 py-3 text-xs capitalize text-text-secondary",
|
|
234
233
|
children: e.actorType.toLowerCase()
|
|
235
234
|
}),
|
|
236
235
|
/* @__PURE__ */ p("td", {
|
|
@@ -238,11 +237,11 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
238
237
|
children: /* @__PURE__ */ p("div", {
|
|
239
238
|
className: "flex flex-wrap gap-1",
|
|
240
239
|
children: e.roleAssignments.filter((e) => e.isActive).map((e) => /* @__PURE__ */ m("span", {
|
|
241
|
-
className: "inline-flex items-center gap-1 rounded-full border border-border bg-
|
|
240
|
+
className: "inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-sunken px-2 py-0.5 text-xs",
|
|
242
241
|
children: [e.role?.name ?? e.roleId, /* @__PURE__ */ p("button", {
|
|
243
242
|
type: "button",
|
|
244
|
-
onClick: () =>
|
|
245
|
-
className: "text-
|
|
243
|
+
onClick: () => X(e.id),
|
|
244
|
+
className: "text-text-secondary hover:text-status-error-text",
|
|
246
245
|
"aria-label": `Remove ${e.role?.name ?? "role"}`,
|
|
247
246
|
children: "×"
|
|
248
247
|
})]
|
|
@@ -252,7 +251,7 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
252
251
|
/* @__PURE__ */ p("td", {
|
|
253
252
|
className: "px-4 py-3 text-right",
|
|
254
253
|
children: /* @__PURE__ */ m("span", {
|
|
255
|
-
className: "text-xs text-
|
|
254
|
+
className: "text-xs text-text-secondary",
|
|
256
255
|
children: [e.effectivePermissionCount ?? "—", " permissions"]
|
|
257
256
|
})
|
|
258
257
|
})
|
|
@@ -261,36 +260,49 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
261
260
|
})]
|
|
262
261
|
}),
|
|
263
262
|
/* @__PURE__ */ m("div", {
|
|
264
|
-
className: "rounded-lg border border-border bg-
|
|
263
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
265
264
|
children: [
|
|
266
265
|
/* @__PURE__ */ p("h3", {
|
|
267
|
-
className: "text-sm font-semibold text-
|
|
266
|
+
className: "text-sm font-semibold text-text-primary",
|
|
268
267
|
children: "Available roles"
|
|
269
268
|
}),
|
|
270
269
|
/* @__PURE__ */ p("p", {
|
|
271
|
-
className: "mt-1 text-xs text-
|
|
270
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
272
271
|
children: "Roles are cloned from the system templates when a billing account is created."
|
|
273
272
|
}),
|
|
274
273
|
/* @__PURE__ */ p("ul", {
|
|
275
274
|
className: "mt-3 space-y-2",
|
|
276
|
-
children:
|
|
277
|
-
className: "flex items-start gap-3 rounded-md border border-border bg-
|
|
275
|
+
children: F.map((e) => /* @__PURE__ */ m("li", {
|
|
276
|
+
className: "flex items-start gap-3 rounded-md border border-border-subtle bg-bg-surface p-3",
|
|
278
277
|
children: [/* @__PURE__ */ m("div", {
|
|
279
278
|
className: "flex-1",
|
|
280
279
|
children: [/* @__PURE__ */ p("div", {
|
|
281
|
-
className: "text-sm font-medium text-
|
|
280
|
+
className: "text-sm font-medium text-text-primary",
|
|
282
281
|
children: e.name
|
|
283
282
|
}), e.description && /* @__PURE__ */ p("p", {
|
|
284
|
-
className: "mt-1 text-xs text-
|
|
283
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
285
284
|
children: e.description
|
|
286
285
|
})]
|
|
287
286
|
}), /* @__PURE__ */ m("span", {
|
|
288
|
-
className: "text-xs text-
|
|
287
|
+
className: "text-xs text-text-secondary",
|
|
289
288
|
children: ["priority ", e.priority]
|
|
290
289
|
})]
|
|
291
290
|
}, e.id))
|
|
292
291
|
})
|
|
293
292
|
]
|
|
293
|
+
}),
|
|
294
|
+
/* @__PURE__ */ p(h, {
|
|
295
|
+
open: Y !== null,
|
|
296
|
+
onOpenChange: (e) => {
|
|
297
|
+
e || X(null);
|
|
298
|
+
},
|
|
299
|
+
children: /* @__PURE__ */ m(v, { children: [/* @__PURE__ */ m(x, { children: [/* @__PURE__ */ p(S, { children: "Remove role assignment?" }), /* @__PURE__ */ p(y, { children: "Remove this role assignment? The member will lose the access this role grants for this billing account." })] }), /* @__PURE__ */ m(b, { children: [/* @__PURE__ */ p(_, { children: "Cancel" }), /* @__PURE__ */ p(g, {
|
|
300
|
+
onClick: () => {
|
|
301
|
+
let e = Y;
|
|
302
|
+
X(null), e && re(e);
|
|
303
|
+
},
|
|
304
|
+
children: "Remove"
|
|
305
|
+
})] })] })
|
|
294
306
|
})
|
|
295
307
|
]
|
|
296
308
|
}) : /* @__PURE__ */ m("div", {
|
|
@@ -299,12 +311,12 @@ var h = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, g = "
|
|
|
299
311
|
title: "Team & Permissions",
|
|
300
312
|
description: "Select a billing account to manage who can access it."
|
|
301
313
|
}), /* @__PURE__ */ p("div", {
|
|
302
|
-
className: "rounded-lg border border-border bg-
|
|
314
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-6 text-sm text-text-secondary",
|
|
303
315
|
children: "No billing account selected. Pick one from the billing account selector to manage its members and roles."
|
|
304
316
|
})]
|
|
305
317
|
}) : /* @__PURE__ */ p(t, { message: "You don't have permission to manage team and permissions for this billing account." });
|
|
306
318
|
};
|
|
307
319
|
//#endregion
|
|
308
|
-
export {
|
|
320
|
+
export { O as TeamPermissionsPage };
|
|
309
321
|
|
|
310
322
|
//# sourceMappingURL=TeamPermissionsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamPermissionsPage.js","names":[],"sources":["../../../../../src/billing/modules/settings/pages/TeamPermissionsPage.tsx"],"sourcesContent":["/**\n * Settings Module - Team & Permissions Page\n *\n * Lets a Billing Admin manage billing-scope role assignments for a specific\n * billing account. Calls global-rbac-svc directly via the same gateway URL\n * used by the rest of the billing module.\n *\n * Scope: `scopeType: \"billing\"`, `scopeId: <billingAccountId>`\n * Roles cloned per billing account at creation time:\n * - Billing Admin (full access to this billing account)\n * - Billing Viewer (read-only access to this billing account)\n */\nimport { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, ActorIdentity, PageHeader } from '../../../shared/components';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useBillingAccounts } from '../../dashboard/hooks/useDashboard';\nimport { directBillingGraphqlRequest } from '../../../shared/utils';\nimport { useActorProfiles } from '../../../shared/hooks';\n\n// Basic UUID-v1-through-v5 shape — accept any 8-4-4-4-12 hex pattern.\n// We intentionally don't strictly validate the version nibble; the rbac\n// service is the authoritative ID validator.\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ninterface Role {\n id: string;\n name: string;\n description?: string | null;\n priority: number;\n isSystem: boolean;\n}\n\ninterface ActorRoleAssignment {\n id: string;\n actorId: string;\n actorType: string;\n roleId: string;\n scopeId: string;\n isActive: boolean;\n expiresAt?: string | null;\n role?: Role;\n}\n\ninterface ActorRbacSummary {\n actorId: string;\n actorType: string;\n roleAssignments: ActorRoleAssignment[];\n effectivePermissionCount?: number;\n}\n\nconst ROLES_QUERY = `\n query BillingScopeRoles($scopeId: String!) {\n roles(scopeId: $scopeId) {\n id\n name\n description\n priority\n isSystem\n }\n }\n`;\n\nconst ACTORS_QUERY = `\n query BillingScopeActors($scopeId: String!) {\n actors(scopeId: $scopeId) {\n actorId\n actorType\n roleAssignments {\n id\n actorId\n actorType\n roleId\n scopeId\n isActive\n expiresAt\n role { id name priority }\n }\n }\n }\n`;\n\nconst ASSIGN_ROLE_MUTATION = `\n mutation AssignBillingRole($input: AssignRoleInput!) {\n assignRoleToActor(input: $input) {\n id\n actorId\n roleId\n scopeId\n }\n }\n`;\n\nconst REMOVE_ROLE_MUTATION = `\n mutation RemoveBillingRole($assignmentId: String!) {\n removeRoleFromActor(assignmentId: $assignmentId)\n }\n`;\n\nexport const TeamPermissionsPage: FC = () => {\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n // Resolution priority: URL `?billingAccountId=` → persisted localStorage →\n // default/first available account. Pass `billingAccounts` so first-time\n // users with empty localStorage still land on the default account\n // (without it the hook's auto-select effect can't fire and the page\n // dead-ends on \"No billing account selected\").\n const { billingAccounts } = useBillingAccounts();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const billingAccountId = selectedAccountId ?? undefined;\n\n // Share the resolved account with `useBillingPermissions` so its\n // billing-scope permissions fetch observes the same ID. Without this,\n // a first-time user (no URL param, empty localStorage) would see the\n // page resolve to the default account here while the permissions hook\n // still sat at null — billing-scope-only admins would see AccessDenied\n // until a navigation/reload propagated the localStorage write.\n const permissions = useBillingPermissions({ billingAccountIdOverride: billingAccountId });\n\n const [roles, setRoles] = useState<Role[]>([]);\n const [actors, setActors] = useState<ActorRbacSummary[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Add-member form\n const [newActorId, setNewActorId] = useState('');\n const [newRoleId, setNewRoleId] = useState('');\n const [isAdding, setIsAdding] = useState(false);\n\n const gqlPost = useCallback(\n async <T,>(query: string, variables: Record<string, unknown>): Promise<T> => {\n return directBillingGraphqlRequest<T>({\n apiGatewayUrl,\n authToken,\n orgId,\n query,\n variables,\n });\n },\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = useCallback(async () => {\n if (!billingAccountId) return;\n setIsLoading(true);\n setError(null);\n try {\n const [rolesResp, actorsResp] = await Promise.all([\n gqlPost<{ roles: Role[] }>(ROLES_QUERY, { scopeId: billingAccountId }),\n gqlPost<{ actors: ActorRbacSummary[] }>(ACTORS_QUERY, {\n scopeId: billingAccountId,\n }),\n ]);\n setRoles(rolesResp.roles ?? []);\n setActors(actorsResp.actors ?? []);\n // Default the role-picker to \"Billing Viewer\" if available, else first role\n const viewer = (rolesResp.roles ?? []).find((r) => /viewer/i.test(r.name));\n setNewRoleId(viewer?.id ?? rolesResp.roles?.[0]?.id ?? '');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load team data');\n } finally {\n setIsLoading(false);\n }\n }, [billingAccountId, gqlPost]);\n\n useEffect(() => {\n void loadData();\n }, [loadData]);\n\n const sortedActors = useMemo(\n () =>\n [...actors].sort((a, b) => {\n const priA = Math.max(...a.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n const priB = Math.max(...b.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n return priB - priA;\n }),\n [actors]\n );\n\n const profileInputs = useMemo(\n () =>\n sortedActors.map((a) => ({\n actorId: a.actorId,\n actorType: a.actorType,\n })),\n [sortedActors]\n );\n const profileMap = useActorProfiles(profileInputs);\n\n const billingAccountName = useMemo(() => {\n if (!billingAccountId) return undefined;\n return billingAccounts.find((acct) => acct.id === billingAccountId)?.name ?? undefined;\n }, [billingAccountId, billingAccounts]);\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!billingAccountId || !newActorId.trim() || !newRoleId) return;\n const trimmedActorId = newActorId.trim();\n if (!UUID_RE.test(trimmedActorId)) {\n setError('User ID must be a UUID (e.g. 1a2b3c4d-...). Get it from the user profile page.');\n return;\n }\n setIsAdding(true);\n setError(null);\n try {\n await gqlPost(ASSIGN_ROLE_MUTATION, {\n input: {\n actorId: trimmedActorId,\n actorType: 'USER',\n roleId: newRoleId,\n scopeId: billingAccountId,\n },\n });\n setNewActorId('');\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to assign role');\n } finally {\n setIsAdding(false);\n }\n };\n\n const handleRemoveAssignment = async (assignmentId: string) => {\n if (!confirm('Remove this role assignment?')) return;\n setError(null);\n try {\n await gqlPost(REMOVE_ROLE_MUTATION, { assignmentId });\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to remove role');\n }\n };\n\n if (!permissions.canManageBillingAccount) {\n return (\n <AccessDenied message=\"You don't have permission to manage team and permissions for this billing account.\" />\n );\n }\n\n if (!billingAccountId) {\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Select a billing account to manage who can access it.\"\n />\n <div className=\"rounded-lg border border-border bg-card p-6 text-sm text-muted-foreground\">\n No billing account selected. Pick one from the billing account selector to manage its\n members and roles.\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Control who can manage this billing account and what they can do.\"\n />\n\n <div className=\"rounded-lg border border-border bg-card p-4 text-sm\">\n <span className=\"font-medium text-foreground\">Billing account:</span>{' '}\n <span className=\"text-foreground\">{billingAccountName ?? 'Unnamed account'}</span>\n {billingAccountId ? (\n <details className=\"mt-2 text-xs text-muted-foreground\">\n <summary className=\"cursor-pointer\">Account ID</summary>\n <span className=\"mt-1 inline-block font-mono\">{billingAccountId}</span>\n </details>\n ) : null}\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive\">\n {error}\n </div>\n )}\n\n {/* Add member form */}\n <form\n onSubmit={(e) => void handleAddMember(e)}\n className=\"rounded-lg border border-border bg-card p-4 space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-foreground\">Add member</h3>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-3\">\n <div className=\"md:col-span-2\">\n <label htmlFor=\"newActorId\" className=\"block text-xs font-medium text-muted-foreground\">\n User ID\n </label>\n <input\n id=\"newActorId\"\n type=\"text\"\n value={newActorId}\n onChange={(e) => setNewActorId(e.target.value)}\n placeholder=\"Paste the user ID from their profile page\"\n className=\"mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm\"\n required\n />\n <p className=\"mt-1 text-[11px] text-muted-foreground\">\n Find this on the user's profile page.\n </p>\n </div>\n <div>\n <label htmlFor=\"newRoleId\" className=\"block text-xs font-medium text-muted-foreground\">\n Role\n </label>\n <select\n id=\"newRoleId\"\n value={newRoleId}\n onChange={(e) => setNewRoleId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm\"\n required\n >\n {roles.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n <button\n type=\"submit\"\n disabled={isAdding || !newActorId.trim() || !newRoleId}\n className=\"rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground disabled:opacity-50\"\n >\n {isAdding ? 'Adding…' : 'Add member'}\n </button>\n </form>\n\n {/* Members table */}\n <div className=\"rounded-lg border border-border bg-card\">\n <div className=\"border-b border-border px-4 py-3\">\n <h3 className=\"text-sm font-semibold text-foreground\">Members</h3>\n <p className=\"text-xs text-muted-foreground\">\n Each member's role applies only to this billing account.\n </p>\n </div>\n {isLoading ? (\n <div className=\"p-6 text-sm text-muted-foreground\">Loading…</div>\n ) : sortedActors.length === 0 ? (\n <div className=\"p-6 text-sm text-muted-foreground\">No members assigned yet.</div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-xs text-muted-foreground\">\n <th className=\"px-4 py-2\">Member</th>\n <th className=\"px-4 py-2\">Type</th>\n <th className=\"px-4 py-2\">Roles</th>\n <th className=\"px-4 py-2 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {sortedActors.map((actor) => (\n <tr key={actor.actorId} className=\"border-b border-border last:border-0\">\n <td className=\"px-4 py-3\">\n <ActorIdentity\n profile={\n profileMap[actor.actorId] ?? {\n actorId: actor.actorId,\n actorType: actor.actorType,\n displayName:\n actor.actorType.toLowerCase() === 'user'\n ? `User ${actor.actorId.slice(0, 8)}…`\n : actor.actorType,\n secondaryLabel: actor.actorId.slice(0, 12),\n resolved: false,\n }\n }\n variant=\"cell\"\n />\n </td>\n <td className=\"px-4 py-3 text-xs capitalize text-muted-foreground\">\n {actor.actorType.toLowerCase()}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex flex-wrap gap-1\">\n {actor.roleAssignments\n .filter((ra) => ra.isActive)\n .map((ra) => (\n <span\n key={ra.id}\n className=\"inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-xs\"\n >\n {ra.role?.name ?? ra.roleId}\n <button\n type=\"button\"\n onClick={() => void handleRemoveAssignment(ra.id)}\n className=\"text-muted-foreground hover:text-destructive\"\n aria-label={`Remove ${ra.role?.name ?? 'role'}`}\n >\n ×\n </button>\n </span>\n ))}\n </div>\n </td>\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-muted-foreground\">\n {actor.effectivePermissionCount ?? '—'} permissions\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n\n {/* Roles reference */}\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <h3 className=\"text-sm font-semibold text-foreground\">Available roles</h3>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Roles are cloned from the system templates when a billing account is created.\n </p>\n <ul className=\"mt-3 space-y-2\">\n {roles.map((r) => (\n <li\n key={r.id}\n className=\"flex items-start gap-3 rounded-md border border-border bg-background p-3\"\n >\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-foreground\">{r.name}</div>\n {r.description && (\n <p className=\"mt-1 text-xs text-muted-foreground\">{r.description}</p>\n )}\n </div>\n <span className=\"text-xs text-muted-foreground\">priority {r.priority}</span>\n </li>\n ))}\n </ul>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TeamPermissionsPage.js","names":[],"sources":["../../../../../src/billing/modules/settings/pages/TeamPermissionsPage.tsx"],"sourcesContent":["/**\n * Settings Module - Team & Permissions Page\n *\n * Lets a Billing Admin manage billing-scope role assignments for a specific\n * billing account. Calls global-rbac-svc directly via the same gateway URL\n * used by the rest of the billing module.\n *\n * Scope: `scopeType: \"billing\"`, `scopeId: <billingAccountId>`\n * Roles cloned per billing account at creation time:\n * - Billing Admin (full access to this billing account)\n * - Billing Viewer (read-only access to this billing account)\n */\nimport { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, ActorIdentity, PageHeader } from '../../../shared/components';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useBillingAccounts } from '../../dashboard/hooks/useDashboard';\nimport { directBillingGraphqlRequest } from '../../../shared/utils';\nimport { useActorProfiles } from '../../../shared/hooks';\n\n// Basic UUID-v1-through-v5 shape — accept any 8-4-4-4-12 hex pattern.\n// We intentionally don't strictly validate the version nibble; the rbac\n// service is the authoritative ID validator.\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ninterface Role {\n id: string;\n name: string;\n description?: string | null;\n priority: number;\n isSystem: boolean;\n}\n\ninterface ActorRoleAssignment {\n id: string;\n actorId: string;\n actorType: string;\n roleId: string;\n scopeId: string;\n isActive: boolean;\n expiresAt?: string | null;\n role?: Role;\n}\n\ninterface ActorRbacSummary {\n actorId: string;\n actorType: string;\n roleAssignments: ActorRoleAssignment[];\n effectivePermissionCount?: number;\n}\n\nconst ROLES_QUERY = `\n query BillingScopeRoles($scopeId: String!) {\n roles(scopeId: $scopeId) {\n id\n name\n description\n priority\n isSystem\n }\n }\n`;\n\nconst ACTORS_QUERY = `\n query BillingScopeActors($scopeId: String!) {\n actors(scopeId: $scopeId) {\n actorId\n actorType\n roleAssignments {\n id\n actorId\n actorType\n roleId\n scopeId\n isActive\n expiresAt\n role { id name priority }\n }\n }\n }\n`;\n\nconst ASSIGN_ROLE_MUTATION = `\n mutation AssignBillingRole($input: AssignRoleInput!) {\n assignRoleToActor(input: $input) {\n id\n actorId\n roleId\n scopeId\n }\n }\n`;\n\nconst REMOVE_ROLE_MUTATION = `\n mutation RemoveBillingRole($assignmentId: String!) {\n removeRoleFromActor(assignmentId: $assignmentId)\n }\n`;\n\nexport const TeamPermissionsPage: FC = () => {\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n // Resolution priority: URL `?billingAccountId=` → persisted localStorage →\n // default/first available account. Pass `billingAccounts` so first-time\n // users with empty localStorage still land on the default account\n // (without it the hook's auto-select effect can't fire and the page\n // dead-ends on \"No billing account selected\").\n const { billingAccounts } = useBillingAccounts();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const billingAccountId = selectedAccountId ?? undefined;\n\n // Share the resolved account with `useBillingPermissions` so its\n // billing-scope permissions fetch observes the same ID. Without this,\n // a first-time user (no URL param, empty localStorage) would see the\n // page resolve to the default account here while the permissions hook\n // still sat at null — billing-scope-only admins would see AccessDenied\n // until a navigation/reload propagated the localStorage write.\n const permissions = useBillingPermissions({ billingAccountIdOverride: billingAccountId });\n\n const [roles, setRoles] = useState<Role[]>([]);\n const [actors, setActors] = useState<ActorRbacSummary[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Add-member form\n const [newActorId, setNewActorId] = useState('');\n const [newRoleId, setNewRoleId] = useState('');\n const [isAdding, setIsAdding] = useState(false);\n const [removeAssignmentId, setRemoveAssignmentId] = useState<string | null>(null);\n\n const gqlPost = useCallback(\n async <T,>(query: string, variables: Record<string, unknown>): Promise<T> => {\n return directBillingGraphqlRequest<T>({\n apiGatewayUrl,\n authToken,\n orgId,\n query,\n variables,\n });\n },\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = useCallback(async () => {\n if (!billingAccountId) return;\n setIsLoading(true);\n setError(null);\n try {\n const [rolesResp, actorsResp] = await Promise.all([\n gqlPost<{ roles: Role[] }>(ROLES_QUERY, { scopeId: billingAccountId }),\n gqlPost<{ actors: ActorRbacSummary[] }>(ACTORS_QUERY, {\n scopeId: billingAccountId,\n }),\n ]);\n setRoles(rolesResp.roles ?? []);\n setActors(actorsResp.actors ?? []);\n // Default the role-picker to \"Billing Viewer\" if available, else first role\n const viewer = (rolesResp.roles ?? []).find((r) => /viewer/i.test(r.name));\n setNewRoleId(viewer?.id ?? rolesResp.roles?.[0]?.id ?? '');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load team data');\n } finally {\n setIsLoading(false);\n }\n }, [billingAccountId, gqlPost]);\n\n useEffect(() => {\n void loadData();\n }, [loadData]);\n\n const sortedActors = useMemo(\n () =>\n [...actors].sort((a, b) => {\n const priA = Math.max(...a.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n const priB = Math.max(...b.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n return priB - priA;\n }),\n [actors]\n );\n\n const profileInputs = useMemo(\n () =>\n sortedActors.map((a) => ({\n actorId: a.actorId,\n actorType: a.actorType,\n })),\n [sortedActors]\n );\n const profileMap = useActorProfiles(profileInputs);\n\n const billingAccountName = useMemo(() => {\n if (!billingAccountId) return undefined;\n return billingAccounts.find((acct) => acct.id === billingAccountId)?.name ?? undefined;\n }, [billingAccountId, billingAccounts]);\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!billingAccountId || !newActorId.trim() || !newRoleId) return;\n const trimmedActorId = newActorId.trim();\n if (!UUID_RE.test(trimmedActorId)) {\n setError('User ID must be a UUID (e.g. 1a2b3c4d-...). Get it from the user profile page.');\n return;\n }\n setIsAdding(true);\n setError(null);\n try {\n await gqlPost(ASSIGN_ROLE_MUTATION, {\n input: {\n actorId: trimmedActorId,\n actorType: 'USER',\n roleId: newRoleId,\n scopeId: billingAccountId,\n },\n });\n setNewActorId('');\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to assign role');\n } finally {\n setIsAdding(false);\n }\n };\n\n const handleRemoveAssignment = async (assignmentId: string) => {\n setError(null);\n try {\n await gqlPost(REMOVE_ROLE_MUTATION, { assignmentId });\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to remove role');\n }\n };\n\n if (!permissions.canManageBillingAccount) {\n return (\n <AccessDenied message=\"You don't have permission to manage team and permissions for this billing account.\" />\n );\n }\n\n if (!billingAccountId) {\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Select a billing account to manage who can access it.\"\n />\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-6 text-sm text-text-secondary\">\n No billing account selected. Pick one from the billing account selector to manage its\n members and roles.\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Control who can manage this billing account and what they can do.\"\n />\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm\">\n <span className=\"font-medium text-text-primary\">Billing account:</span>{' '}\n <span className=\"text-text-primary\">{billingAccountName ?? 'Unnamed account'}</span>\n {billingAccountId ? (\n <details className=\"mt-2 text-xs text-text-secondary\">\n <summary className=\"cursor-pointer\">Account ID</summary>\n <span className=\"mt-1 inline-block font-mono\">{billingAccountId}</span>\n </details>\n ) : null}\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-status-error-border/30 bg-status-error-bg/5 p-4 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n\n {/* Add member form */}\n <form\n onSubmit={(e) => void handleAddMember(e)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">Add member</h3>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-3\">\n <div className=\"md:col-span-2\">\n <label htmlFor=\"newActorId\" className=\"block text-xs font-medium text-text-secondary\">\n User ID\n </label>\n <input\n id=\"newActorId\"\n type=\"text\"\n value={newActorId}\n onChange={(e) => setNewActorId(e.target.value)}\n placeholder=\"Paste the user ID from their profile page\"\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n required\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n Find this on the user's profile page.\n </p>\n </div>\n <div>\n <label htmlFor=\"newRoleId\" className=\"block text-xs font-medium text-text-secondary\">\n Role\n </label>\n <select\n id=\"newRoleId\"\n value={newRoleId}\n onChange={(e) => setNewRoleId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n required\n >\n {roles.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n <button\n type=\"submit\"\n disabled={isAdding || !newActorId.trim() || !newRoleId}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text disabled:opacity-50\"\n >\n {isAdding ? 'Adding…' : 'Add member'}\n </button>\n </form>\n\n {/* Members table */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"border-b border-border-subtle px-4 py-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Members</h3>\n <p className=\"text-xs text-text-secondary\">\n Each member's role applies only to this billing account.\n </p>\n </div>\n {isLoading ? (\n <div className=\"p-6 text-sm text-text-secondary\">Loading…</div>\n ) : sortedActors.length === 0 ? (\n <div className=\"p-6 text-sm text-text-secondary\">No members assigned yet.</div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-2\">Member</th>\n <th className=\"px-4 py-2\">Type</th>\n <th className=\"px-4 py-2\">Roles</th>\n <th className=\"px-4 py-2 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {sortedActors.map((actor) => (\n <tr key={actor.actorId} className=\"border-b border-border-subtle last:border-0\">\n <td className=\"px-4 py-3\">\n <ActorIdentity\n profile={\n profileMap[actor.actorId] ?? {\n actorId: actor.actorId,\n actorType: actor.actorType,\n displayName:\n actor.actorType.toLowerCase() === 'user'\n ? `User ${actor.actorId.slice(0, 8)}…`\n : actor.actorType,\n secondaryLabel: actor.actorId.slice(0, 12),\n resolved: false,\n }\n }\n variant=\"cell\"\n />\n </td>\n <td className=\"px-4 py-3 text-xs capitalize text-text-secondary\">\n {actor.actorType.toLowerCase()}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex flex-wrap gap-1\">\n {actor.roleAssignments\n .filter((ra) => ra.isActive)\n .map((ra) => (\n <span\n key={ra.id}\n className=\"inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-sunken px-2 py-0.5 text-xs\"\n >\n {ra.role?.name ?? ra.roleId}\n <button\n type=\"button\"\n onClick={() => setRemoveAssignmentId(ra.id)}\n className=\"text-text-secondary hover:text-status-error-text\"\n aria-label={`Remove ${ra.role?.name ?? 'role'}`}\n >\n ×\n </button>\n </span>\n ))}\n </div>\n </td>\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-text-secondary\">\n {actor.effectivePermissionCount ?? '—'} permissions\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n\n {/* Roles reference */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Available roles</h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Roles are cloned from the system templates when a billing account is created.\n </p>\n <ul className=\"mt-3 space-y-2\">\n {roles.map((r) => (\n <li\n key={r.id}\n className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-text-primary\">{r.name}</div>\n {r.description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{r.description}</p>\n )}\n </div>\n <span className=\"text-xs text-text-secondary\">priority {r.priority}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <AlertDialog\n open={removeAssignmentId !== null}\n onOpenChange={(open) => {\n if (!open) setRemoveAssignmentId(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Remove role assignment?</AlertDialogTitle>\n <AlertDialogDescription>\n Remove this role assignment? The member will lose the access this role grants for\n this billing account.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() => {\n const id = removeAssignmentId;\n setRemoveAssignmentId(null);\n if (id) void handleRemoveAssignment(id);\n }}\n >\n Remove\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}
|