apiblaze 0.18.2 → 0.18.6
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/index.js +575 -506
- package/dist/react/index.js +377 -178
- package/dist/react/index.mjs +377 -178
- package/dist/server/index.d.mts +29 -0
- package/dist/server/index.d.ts +29 -0
- package/dist/server/index.js +38 -8
- package/dist/server/index.mjs +38 -8
- package/package.json +1 -1
package/dist/react/index.mjs
CHANGED
|
@@ -39,14 +39,79 @@ function relTime(iso) {
|
|
|
39
39
|
if (d < 30) return `${d}d ago`;
|
|
40
40
|
return new Date(iso).toLocaleDateString();
|
|
41
41
|
}
|
|
42
|
+
function Typeahead(props) {
|
|
43
|
+
const [q, setQ] = React.useState("");
|
|
44
|
+
const [results, setResults] = React.useState(null);
|
|
45
|
+
const [loading, setLoading] = React.useState(false);
|
|
46
|
+
const seq = React.useRef(0);
|
|
47
|
+
React.useEffect(() => {
|
|
48
|
+
const term = q.trim();
|
|
49
|
+
if (term.length < 2) {
|
|
50
|
+
setResults(null);
|
|
51
|
+
setLoading(false);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const my = ++seq.current;
|
|
55
|
+
setLoading(true);
|
|
56
|
+
const h = setTimeout(async () => {
|
|
57
|
+
try {
|
|
58
|
+
const r = await props.search(term);
|
|
59
|
+
if (my === seq.current) {
|
|
60
|
+
setResults(r);
|
|
61
|
+
setLoading(false);
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
if (my === seq.current) {
|
|
65
|
+
setResults([]);
|
|
66
|
+
setLoading(false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, 250);
|
|
70
|
+
return () => clearTimeout(h);
|
|
71
|
+
}, [q]);
|
|
72
|
+
return /* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, minWidth: 180 }, children: [
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
"input",
|
|
75
|
+
{
|
|
76
|
+
style: { ...props.s.input, width: "100%" },
|
|
77
|
+
placeholder: props.placeholder,
|
|
78
|
+
value: q,
|
|
79
|
+
onChange: (e) => setQ(e.target.value),
|
|
80
|
+
disabled: props.disabled,
|
|
81
|
+
"aria-label": props.placeholder
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
q.trim().length >= 2 && /* @__PURE__ */ jsxs("div", { style: { position: "absolute", zIndex: 5, left: 0, right: 0, top: "100%", marginTop: 4, background: props.s.surface, border: `1px solid ${props.s.border}`, borderRadius: 8, maxHeight: 220, overflowY: "auto", boxShadow: "0 6px 18px rgba(0,0,0,0.12)" }, children: [
|
|
85
|
+
loading && /* @__PURE__ */ jsx("div", { style: { padding: "8px 10px", fontSize: 12, color: props.s.muted }, children: "Searching\u2026" }),
|
|
86
|
+
!loading && results && results.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: "8px 10px", fontSize: 12, color: props.s.muted }, children: "No matches" }),
|
|
87
|
+
!loading && results && results.map((it) => /* @__PURE__ */ jsx(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
91
|
+
onClick: () => {
|
|
92
|
+
props.onPick(it);
|
|
93
|
+
setQ("");
|
|
94
|
+
setResults(null);
|
|
95
|
+
},
|
|
96
|
+
style: { display: "block", width: "100%", textAlign: "left", padding: "8px 10px", border: "none", borderBottom: `1px solid ${props.s.border}`, background: "transparent", cursor: "pointer", color: props.s.text, fontFamily: "inherit", fontSize: 13 },
|
|
97
|
+
children: props.itemLabel(it)
|
|
98
|
+
},
|
|
99
|
+
props.itemKey(it)
|
|
100
|
+
))
|
|
101
|
+
] })
|
|
102
|
+
] });
|
|
103
|
+
}
|
|
42
104
|
var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint = "/api/apiblaze/groups", theme, title = "Users & groups", className }, ref) {
|
|
43
105
|
const t = { ...DEFAULTS, ...theme ?? {} };
|
|
44
106
|
const headerBg = t.headerBackground || t.surface;
|
|
45
107
|
const [access, setAccess] = React.useState("ok");
|
|
46
108
|
const [myEmail, setMyEmail] = React.useState(null);
|
|
47
109
|
const [groups, setGroups] = React.useState(null);
|
|
48
|
-
const [
|
|
110
|
+
const [groupTotal, setGroupTotal] = React.useState(0);
|
|
111
|
+
const [groupQuery, setGroupQuery] = React.useState("");
|
|
49
112
|
const [observed, setObserved] = React.useState([]);
|
|
113
|
+
const [observedNew, setObservedNew] = React.useState(0);
|
|
114
|
+
const [observedLoaded, setObservedLoaded] = React.useState(false);
|
|
50
115
|
const [loadError, setLoadError] = React.useState(null);
|
|
51
116
|
const [open, setOpen] = React.useState(null);
|
|
52
117
|
const [detail, setDetail] = React.useState(null);
|
|
@@ -54,13 +119,14 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
54
119
|
const [creating, setCreating] = React.useState(false);
|
|
55
120
|
const [newName, setNewName] = React.useState("");
|
|
56
121
|
const [showCreate, setShowCreate] = React.useState(false);
|
|
122
|
+
const [showNewUser, setShowNewUser] = React.useState(false);
|
|
123
|
+
const [newUserRef, setNewUserRef] = React.useState("");
|
|
57
124
|
const [pending, setPending] = React.useState(null);
|
|
58
125
|
const [err, setErr] = React.useState(null);
|
|
59
126
|
const [confirmDelete, setConfirmDelete] = React.useState(null);
|
|
60
|
-
const [showTraffic, setShowTraffic] = React.useState(false);
|
|
61
|
-
const [addSel, setAddSel] = React.useState("");
|
|
62
127
|
const [addRole, setAddRole] = React.useState("member");
|
|
63
|
-
const [
|
|
128
|
+
const [admins, setAdmins] = React.useState([]);
|
|
129
|
+
const [newAdmin, setNewAdmin] = React.useState("");
|
|
64
130
|
const call = React.useCallback(async (body) => {
|
|
65
131
|
const res = await fetch(endpoint, body ? { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) } : { cache: "no-store" });
|
|
66
132
|
const data = await res.json().catch(() => ({}));
|
|
@@ -73,9 +139,15 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
73
139
|
const d = await call();
|
|
74
140
|
setAccess(d.access === "pending" ? "pending" : "ok");
|
|
75
141
|
setMyEmail(d.email ?? null);
|
|
76
|
-
setGroups(
|
|
77
|
-
|
|
78
|
-
|
|
142
|
+
setGroups(d.groups ?? []);
|
|
143
|
+
setGroupTotal(d.groupTotal ?? (d.groups?.length ?? 0));
|
|
144
|
+
setObservedNew(d.observedNew ?? 0);
|
|
145
|
+
setObservedLoaded(false);
|
|
146
|
+
setGroupQuery("");
|
|
147
|
+
if (d.access !== "pending") {
|
|
148
|
+
const a = await call({ action: "list-admins" });
|
|
149
|
+
setAdmins(a.admin_emails ?? []);
|
|
150
|
+
}
|
|
79
151
|
} catch (e) {
|
|
80
152
|
setLoadError(e instanceof Error ? e.message : "Could not load groups.");
|
|
81
153
|
setGroups(null);
|
|
@@ -85,12 +157,26 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
85
157
|
load();
|
|
86
158
|
}, [load]);
|
|
87
159
|
React.useImperativeHandle(ref, () => ({ refresh: load }), [load]);
|
|
160
|
+
const searchGroups = React.useCallback(async (q) => {
|
|
161
|
+
setGroupQuery(q);
|
|
162
|
+
const d = await call({ action: "list-groups", q, offset: 0 });
|
|
163
|
+
setGroups(d.groups ?? []);
|
|
164
|
+
setGroupTotal(d.total ?? (d.groups?.length ?? 0));
|
|
165
|
+
}, [call]);
|
|
166
|
+
const loadMoreGroups = React.useCallback(async () => {
|
|
167
|
+
const d = await call({ action: "list-groups", q: groupQuery, offset: groups?.length ?? 0 });
|
|
168
|
+
setGroups((prev) => [...prev ?? [], ...d.groups ?? []]);
|
|
169
|
+
if (typeof d.total === "number") setGroupTotal(d.total);
|
|
170
|
+
}, [call, groupQuery, groups]);
|
|
171
|
+
const loadObserved = React.useCallback(async () => {
|
|
172
|
+
const d = await call({ action: "list-observed", offset: 0 });
|
|
173
|
+
setObserved(d.identities ?? []);
|
|
174
|
+
setObservedLoaded(true);
|
|
175
|
+
}, [call]);
|
|
88
176
|
const loadDetail = React.useCallback(async (groupId) => {
|
|
89
177
|
setDetailLoading(true);
|
|
90
178
|
setDetail(null);
|
|
91
179
|
setErr(null);
|
|
92
|
-
setAddSel("");
|
|
93
|
-
setNestSel("");
|
|
94
180
|
try {
|
|
95
181
|
setDetail(await call({ action: "group-detail", groupId }));
|
|
96
182
|
} catch (e) {
|
|
@@ -99,6 +185,14 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
99
185
|
setDetailLoading(false);
|
|
100
186
|
}
|
|
101
187
|
}, [call]);
|
|
188
|
+
const reloadAdmins = React.useCallback(async () => {
|
|
189
|
+
const a = await call({ action: "list-admins" });
|
|
190
|
+
setAdmins(a.admin_emails ?? []);
|
|
191
|
+
}, [call]);
|
|
192
|
+
const loadMoreMembers = React.useCallback(async (groupId, offset) => {
|
|
193
|
+
const d = await call({ action: "group-members", groupId, offset });
|
|
194
|
+
setDetail((prev) => prev && prev.id === groupId ? { ...prev, members: [...prev.members, ...d.members ?? []] } : prev);
|
|
195
|
+
}, [call]);
|
|
102
196
|
function toggle(g) {
|
|
103
197
|
if (open === g.id) {
|
|
104
198
|
setOpen(null);
|
|
@@ -113,7 +207,6 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
113
207
|
setErr(null);
|
|
114
208
|
try {
|
|
115
209
|
await call(body);
|
|
116
|
-
await load();
|
|
117
210
|
if (after) await after();
|
|
118
211
|
} catch (e) {
|
|
119
212
|
setErr(e instanceof Error ? e.message : "The action failed.");
|
|
@@ -130,13 +223,29 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
130
223
|
await call({ action: "create-group", name });
|
|
131
224
|
setNewName("");
|
|
132
225
|
setShowCreate(false);
|
|
133
|
-
await
|
|
226
|
+
await searchGroups(groupQuery);
|
|
134
227
|
} catch (e) {
|
|
135
228
|
setErr(e instanceof Error ? e.message : "Could not create the group.");
|
|
136
229
|
} finally {
|
|
137
230
|
setCreating(false);
|
|
138
231
|
}
|
|
139
232
|
}
|
|
233
|
+
async function addAdmin() {
|
|
234
|
+
const email = newAdmin.trim().toLowerCase();
|
|
235
|
+
if (!email) return;
|
|
236
|
+
await act("add-admin", { action: "add-admin", email }, async () => {
|
|
237
|
+
setNewAdmin("");
|
|
238
|
+
await reloadAdmins();
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function createUser() {
|
|
242
|
+
const ref2 = newUserRef.trim();
|
|
243
|
+
if (!ref2) return;
|
|
244
|
+
await act("create-user", { action: "create-user", ref: ref2 }, async () => {
|
|
245
|
+
setNewUserRef("");
|
|
246
|
+
await loadObserved();
|
|
247
|
+
});
|
|
248
|
+
}
|
|
140
249
|
const vars = {
|
|
141
250
|
// @ts-expect-error CSS custom properties
|
|
142
251
|
"--abz-accent": t.accent,
|
|
@@ -176,7 +285,18 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
176
285
|
/* @__PURE__ */ jsxs("div", { style: card, children: [
|
|
177
286
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 12, padding: "14px 16px", borderBottom: `1px solid ${t.border}`, background: headerBg }, children: [
|
|
178
287
|
/* @__PURE__ */ jsx("strong", { style: { fontSize: 15 }, children: title }),
|
|
179
|
-
access === "ok" && /* @__PURE__ */
|
|
288
|
+
access === "ok" && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
289
|
+
/* @__PURE__ */ jsx("button", { style: btn("ghost", true), disabled: busy, onClick: () => {
|
|
290
|
+
const nv = !showNewUser;
|
|
291
|
+
setShowNewUser(nv);
|
|
292
|
+
setShowCreate(false);
|
|
293
|
+
if (nv && !observedLoaded) loadObserved();
|
|
294
|
+
}, "aria-label": "Add a new user", children: "+ New user" }),
|
|
295
|
+
/* @__PURE__ */ jsx("button", { style: btn("primary", true), disabled: busy, onClick: () => {
|
|
296
|
+
setShowCreate((v) => !v);
|
|
297
|
+
setShowNewUser(false);
|
|
298
|
+
}, "aria-label": "Create a new group", children: "+ New group" })
|
|
299
|
+
] })
|
|
180
300
|
] }),
|
|
181
301
|
access === "pending" && /* @__PURE__ */ jsxs("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted, lineHeight: 1.5 }, children: [
|
|
182
302
|
/* @__PURE__ */ jsx("div", { style: { fontWeight: 700, color: t.text, marginBottom: 6, fontSize: 15 }, children: "Admin access required" }),
|
|
@@ -225,13 +345,88 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
225
345
|
setNewName("");
|
|
226
346
|
}, children: "Cancel" })
|
|
227
347
|
] }),
|
|
348
|
+
access === "ok" && showNewUser && /* @__PURE__ */ jsxs("div", { style: { padding: "12px 16px", borderBottom: `1px solid ${t.border}` }, children: [
|
|
349
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
350
|
+
/* @__PURE__ */ jsx(
|
|
351
|
+
"input",
|
|
352
|
+
{
|
|
353
|
+
style: { ...inputStyle, flex: 1 },
|
|
354
|
+
placeholder: "Email (or a user id)",
|
|
355
|
+
value: newUserRef,
|
|
356
|
+
onChange: (e) => setNewUserRef(e.target.value),
|
|
357
|
+
onKeyDown: (e) => e.key === "Enter" && createUser(),
|
|
358
|
+
"aria-label": "New user email or id"
|
|
359
|
+
}
|
|
360
|
+
),
|
|
361
|
+
/* @__PURE__ */ jsx("button", { style: btn("primary", true), disabled: busy || !newUserRef.trim(), onClick: createUser, children: "Add" }),
|
|
362
|
+
/* @__PURE__ */ jsx("button", { style: btn("ghost", true), onClick: () => {
|
|
363
|
+
setShowNewUser(false);
|
|
364
|
+
setNewUserRef("");
|
|
365
|
+
}, children: "Cancel" })
|
|
366
|
+
] }),
|
|
367
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: t.muted, marginTop: 6 }, children: [
|
|
368
|
+
"An ",
|
|
369
|
+
/* @__PURE__ */ jsx("strong", { children: "email" }),
|
|
370
|
+
" is pre-approved \u2014 they join with their real identity the first time they sign in. A ",
|
|
371
|
+
/* @__PURE__ */ jsx("strong", { children: "user id" }),
|
|
372
|
+
" is provisioned now."
|
|
373
|
+
] }),
|
|
374
|
+
/* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
|
|
375
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: t.muted, marginBottom: 6 }, children: [
|
|
376
|
+
"\u2026or add someone already seen in your API traffic",
|
|
377
|
+
observedNew ? ` (${observedNew} new)` : "",
|
|
378
|
+
":"
|
|
379
|
+
] }),
|
|
380
|
+
!observedLoaded && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted }, children: "Loading\u2026" }),
|
|
381
|
+
observedLoaded && observed.filter((o) => !o.provisioned).length === 0 && /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted }, children: "No new identities in traffic \u2014 they appear here after their first API call." }),
|
|
382
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: observed.filter((o) => !o.provisioned).map((o) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "5px 0", flexWrap: "wrap" }, children: [
|
|
383
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
|
|
384
|
+
o.source_label && /* @__PURE__ */ jsx("span", { style: chip, children: o.source_label }),
|
|
385
|
+
o.last_seen && /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, color: t.muted }, children: [
|
|
386
|
+
"seen ",
|
|
387
|
+
relTime(o.last_seen),
|
|
388
|
+
o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
|
|
389
|
+
] }),
|
|
390
|
+
/* @__PURE__ */ jsx(
|
|
391
|
+
"button",
|
|
392
|
+
{
|
|
393
|
+
style: btn("ghost", true),
|
|
394
|
+
disabled: busy,
|
|
395
|
+
"aria-label": `Add ${o.email || o.abz_sub} as a user`,
|
|
396
|
+
onClick: () => act(`provision:${o.abz_sub}`, { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }, loadObserved),
|
|
397
|
+
children: "Add"
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
] }, o.abz_sub)) })
|
|
401
|
+
] })
|
|
402
|
+
] }),
|
|
228
403
|
access === "ok" && groups === null && !loadError && /* @__PURE__ */ jsx("div", { "aria-busy": "true", style: { padding: 24, color: t.muted }, children: "Loading groups\u2026" }),
|
|
229
404
|
access === "ok" && loadError && /* @__PURE__ */ jsxs("div", { role: "alert", style: { padding: 20, color: t.danger, fontSize: 13 }, children: [
|
|
230
405
|
loadError,
|
|
231
406
|
" ",
|
|
232
407
|
/* @__PURE__ */ jsx("button", { style: { ...btn("ghost", true), marginLeft: 6 }, onClick: load, children: "Retry" })
|
|
233
408
|
] }),
|
|
234
|
-
access === "ok" && groups !== null && groups.length
|
|
409
|
+
access === "ok" && groups !== null && (groupTotal > groups.length || groupQuery) && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", padding: "10px 16px", borderBottom: `1px solid ${t.border}` }, children: [
|
|
410
|
+
/* @__PURE__ */ jsx(
|
|
411
|
+
"input",
|
|
412
|
+
{
|
|
413
|
+
style: { ...inputStyle, flex: 1 },
|
|
414
|
+
placeholder: "Search groups\u2026",
|
|
415
|
+
defaultValue: groupQuery,
|
|
416
|
+
onChange: (e) => {
|
|
417
|
+
const v = e.target.value;
|
|
418
|
+
searchGroups(v);
|
|
419
|
+
},
|
|
420
|
+
"aria-label": "Search groups"
|
|
421
|
+
}
|
|
422
|
+
),
|
|
423
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: t.muted, whiteSpace: "nowrap" }, children: [
|
|
424
|
+
groups.length,
|
|
425
|
+
" of ",
|
|
426
|
+
groupTotal
|
|
427
|
+
] })
|
|
428
|
+
] }),
|
|
429
|
+
access === "ok" && groups !== null && groups.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: "28px 20px", textAlign: "center", color: t.muted }, children: groupQuery ? `No groups match \u201C${groupQuery}\u201D.` : "No groups yet. Create one to start organizing your users." }),
|
|
235
430
|
access === "ok" && groups !== null && groups.length > 0 && /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: groups.map((g) => {
|
|
236
431
|
const sealed = g.name === SEALED;
|
|
237
432
|
const isOpen = open === g.id;
|
|
@@ -244,9 +439,12 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
244
439
|
style: { display: "flex", width: "100%", gap: 10, alignItems: "center", padding: "12px 16px", background: "transparent", border: "none", cursor: "pointer", color: t.text, fontFamily: "inherit", fontSize: 14, textAlign: "left" },
|
|
245
440
|
children: [
|
|
246
441
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: isOpen ? "\u25BE" : "\u25B8" }),
|
|
247
|
-
/* @__PURE__ */ jsx("strong", { style: { flex: 1 }, children: g.name }),
|
|
248
|
-
sealed
|
|
249
|
-
|
|
442
|
+
/* @__PURE__ */ jsx("strong", { style: { flex: 1 }, children: sealed ? "Admins" : g.name }),
|
|
443
|
+
sealed ? /* @__PURE__ */ jsxs("span", { style: chip, children: [
|
|
444
|
+
admins.length,
|
|
445
|
+
" admin",
|
|
446
|
+
admins.length === 1 ? "" : "s"
|
|
447
|
+
] }) : /* @__PURE__ */ jsxs("span", { style: chip, children: [
|
|
250
448
|
g.member_count ?? 0,
|
|
251
449
|
" member",
|
|
252
450
|
(g.member_count ?? 0) === 1 ? "" : "s"
|
|
@@ -256,64 +454,66 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
256
454
|
),
|
|
257
455
|
isOpen && /* @__PURE__ */ jsxs("div", { style: { padding: "0 16px 14px 34px" }, children: [
|
|
258
456
|
detailLoading && /* @__PURE__ */ jsx("div", { style: { color: t.muted, fontSize: 13 }, children: "Loading\u2026" }),
|
|
259
|
-
detail && detail.id === g.id && /* @__PURE__ */
|
|
457
|
+
detail && detail.id === g.id && /* @__PURE__ */ jsx(Fragment, { children: sealed ? (
|
|
458
|
+
/* apiblaze_admins is managed HERE, by email — the seal means
|
|
459
|
+
nobody is added by group membership; adding an admin adds
|
|
460
|
+
their email (they activate on first sign-in). */
|
|
461
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
462
|
+
/* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: t.muted, marginBottom: 10 }, children: [
|
|
463
|
+
"Admins are granted by ",
|
|
464
|
+
/* @__PURE__ */ jsx("strong", { children: "email" }),
|
|
465
|
+
" \u2014 never by adding someone to a group. A new admin gets access the first time they sign in."
|
|
466
|
+
] }),
|
|
467
|
+
/* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
468
|
+
admins.map((a) => {
|
|
469
|
+
const removable = !a.pinned && admins.length > 1;
|
|
470
|
+
return /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
471
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 160 }, children: a.email }),
|
|
472
|
+
a.pinned && /* @__PURE__ */ jsx("span", { style: chip, title: "Set by the API provider \u2014 you can\u2019t remove this one", children: "provider" }),
|
|
473
|
+
/* @__PURE__ */ jsx("span", { style: { ...chip, ...a.active ? { color: t.success, borderColor: t.success } : {} }, children: a.active ? "active" : "pending sign-in" }),
|
|
474
|
+
/* @__PURE__ */ jsx(
|
|
475
|
+
"button",
|
|
476
|
+
{
|
|
477
|
+
style: { ...btn("danger", true), ...removable ? {} : { opacity: 0.4, cursor: "not-allowed" } },
|
|
478
|
+
disabled: busy || !removable,
|
|
479
|
+
title: a.pinned ? "Provider-set admins can only be removed by the API provider" : admins.length <= 1 ? "A tenant must keep at least one admin" : `Remove ${a.email}`,
|
|
480
|
+
onClick: () => act(`rm-admin:${a.email}`, { action: "remove-admin", email: a.email }, reloadAdmins),
|
|
481
|
+
children: "Remove"
|
|
482
|
+
}
|
|
483
|
+
)
|
|
484
|
+
] }, a.email);
|
|
485
|
+
}),
|
|
486
|
+
admins.length === 0 && /* @__PURE__ */ jsx("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No admins yet." })
|
|
487
|
+
] }),
|
|
488
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 10 }, children: [
|
|
489
|
+
/* @__PURE__ */ jsx(
|
|
490
|
+
"input",
|
|
491
|
+
{
|
|
492
|
+
style: { ...inputStyle, flex: 1 },
|
|
493
|
+
type: "email",
|
|
494
|
+
placeholder: "Add an admin by email",
|
|
495
|
+
value: newAdmin,
|
|
496
|
+
onChange: (e) => setNewAdmin(e.target.value),
|
|
497
|
+
onKeyDown: (e) => e.key === "Enter" && addAdmin(),
|
|
498
|
+
"aria-label": "New admin email"
|
|
499
|
+
}
|
|
500
|
+
),
|
|
501
|
+
/* @__PURE__ */ jsx("button", { style: btn("primary", true), disabled: busy || !newAdmin.trim(), onClick: addAdmin, children: "Add admin" })
|
|
502
|
+
] })
|
|
503
|
+
] })
|
|
504
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
260
505
|
/* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: [
|
|
261
506
|
detail.members.map((m) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
262
507
|
/* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 140 }, children: userLabel(m) }),
|
|
263
508
|
m.source_label && /* @__PURE__ */ jsx("span", { style: chip, children: m.source_label }),
|
|
264
|
-
sealed ? /* @__PURE__ */ jsx("span", { style: { ...chip, fontWeight: 600 }, children: "admin" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
265
|
-
/* @__PURE__ */ jsxs(
|
|
266
|
-
"select",
|
|
267
|
-
{
|
|
268
|
-
style: selectStyle,
|
|
269
|
-
value: m.role,
|
|
270
|
-
disabled: busy,
|
|
271
|
-
"aria-label": `Role of ${userLabel(m)}`,
|
|
272
|
-
onChange: (e) => act(`role:${m.abz_sub}`, { action: "change-role", groupId: g.id, abzSub: m.abz_sub, role: e.target.value }, () => loadDetail(g.id)),
|
|
273
|
-
children: [
|
|
274
|
-
/* @__PURE__ */ jsx("option", { value: "member", children: "member" }),
|
|
275
|
-
/* @__PURE__ */ jsx("option", { value: "admin", children: "group admin" })
|
|
276
|
-
]
|
|
277
|
-
}
|
|
278
|
-
),
|
|
279
|
-
/* @__PURE__ */ jsx(
|
|
280
|
-
"button",
|
|
281
|
-
{
|
|
282
|
-
style: btn("danger", true),
|
|
283
|
-
disabled: busy,
|
|
284
|
-
"aria-label": `Remove ${userLabel(m)} from ${g.name}`,
|
|
285
|
-
onClick: () => act(`rm:${m.abz_sub}`, { action: "remove-member", groupId: g.id, abzSub: m.abz_sub }, () => loadDetail(g.id)),
|
|
286
|
-
children: "Remove"
|
|
287
|
-
}
|
|
288
|
-
)
|
|
289
|
-
] })
|
|
290
|
-
] }, m.abz_sub)),
|
|
291
|
-
detail.members.length === 0 && /* @__PURE__ */ jsx("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No members yet." })
|
|
292
|
-
] }),
|
|
293
|
-
sealed ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: t.muted, marginTop: 8 }, children: "Workspace admins are granted by email on the provider\u2019s admin list \u2014 membership can\u2019t be edited here." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
294
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap" }, children: [
|
|
295
|
-
/* @__PURE__ */ jsxs(
|
|
296
|
-
"select",
|
|
297
|
-
{
|
|
298
|
-
style: { ...selectStyle, flex: 1, minWidth: 160 },
|
|
299
|
-
value: addSel,
|
|
300
|
-
disabled: busy,
|
|
301
|
-
"aria-label": "User to add",
|
|
302
|
-
onChange: (e) => setAddSel(e.target.value),
|
|
303
|
-
children: [
|
|
304
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Add a user\u2026" }),
|
|
305
|
-
users.filter((u) => !detail.members.some((m) => m.abz_sub === u.abz_sub)).map((u) => /* @__PURE__ */ jsx("option", { value: u.abz_sub, children: userLabel(u) }, u.abz_sub))
|
|
306
|
-
]
|
|
307
|
-
}
|
|
308
|
-
),
|
|
309
509
|
/* @__PURE__ */ jsxs(
|
|
310
510
|
"select",
|
|
311
511
|
{
|
|
312
512
|
style: selectStyle,
|
|
313
|
-
value:
|
|
513
|
+
value: m.role,
|
|
314
514
|
disabled: busy,
|
|
315
|
-
"aria-label":
|
|
316
|
-
onChange: (e) =>
|
|
515
|
+
"aria-label": `Role of ${userLabel(m)}`,
|
|
516
|
+
onChange: (e) => act(`role:${m.abz_sub}`, { action: "change-role", groupId: g.id, abzSub: m.abz_sub, role: e.target.value }, () => loadDetail(g.id)),
|
|
317
517
|
children: [
|
|
318
518
|
/* @__PURE__ */ jsx("option", { value: "member", children: "member" }),
|
|
319
519
|
/* @__PURE__ */ jsx("option", { value: "admin", children: "group admin" })
|
|
@@ -323,130 +523,129 @@ var UsersGroupsWidget = React.forwardRef(function UsersGroupsWidget2({ endpoint
|
|
|
323
523
|
/* @__PURE__ */ jsx(
|
|
324
524
|
"button",
|
|
325
525
|
{
|
|
326
|
-
style: btn("
|
|
327
|
-
disabled: busy || !addSel,
|
|
328
|
-
onClick: () => act("add-member", { action: "add-member", groupId: g.id, abzSub: addSel, role: addRole }, () => {
|
|
329
|
-
setAddSel("");
|
|
330
|
-
return loadDetail(g.id);
|
|
331
|
-
}),
|
|
332
|
-
children: "Add"
|
|
333
|
-
}
|
|
334
|
-
)
|
|
335
|
-
] }),
|
|
336
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 12, flexWrap: "wrap" }, children: [
|
|
337
|
-
/* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: t.muted }, children: "Subgroups:" }),
|
|
338
|
-
detail.subgroups.map((s) => /* @__PURE__ */ jsxs("span", { style: { ...chip, display: "inline-flex", gap: 6, alignItems: "center" }, children: [
|
|
339
|
-
s.name,
|
|
340
|
-
/* @__PURE__ */ jsx(
|
|
341
|
-
"button",
|
|
342
|
-
{
|
|
343
|
-
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
344
|
-
disabled: busy,
|
|
345
|
-
"aria-label": `Un-nest ${s.name} from ${g.name}`,
|
|
346
|
-
onClick: () => act("unnest", { action: "remove-subgroup", groupId: g.id, childGroupId: s.id }, () => loadDetail(g.id)),
|
|
347
|
-
children: "\u2715"
|
|
348
|
-
}
|
|
349
|
-
)
|
|
350
|
-
] }, s.id)),
|
|
351
|
-
detail.subgroups.length === 0 && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
352
|
-
/* @__PURE__ */ jsxs(
|
|
353
|
-
"select",
|
|
354
|
-
{
|
|
355
|
-
style: selectStyle,
|
|
356
|
-
value: nestSel,
|
|
526
|
+
style: btn("danger", true),
|
|
357
527
|
disabled: busy,
|
|
358
|
-
"aria-label":
|
|
359
|
-
|
|
360
|
-
children:
|
|
361
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "Nest a group\u2026" }),
|
|
362
|
-
groups.filter((x) => x.id !== g.id && x.name !== SEALED && !detail.subgroups.some((s) => s.id === x.id)).map((x) => /* @__PURE__ */ jsx("option", { value: x.id, children: x.name }, x.id))
|
|
363
|
-
]
|
|
364
|
-
}
|
|
365
|
-
),
|
|
366
|
-
/* @__PURE__ */ jsx(
|
|
367
|
-
"button",
|
|
368
|
-
{
|
|
369
|
-
style: btn("ghost", true),
|
|
370
|
-
disabled: busy || !nestSel,
|
|
371
|
-
onClick: () => act("nest", { action: "add-subgroup", groupId: g.id, childGroupId: nestSel }, () => {
|
|
372
|
-
setNestSel("");
|
|
373
|
-
return loadDetail(g.id);
|
|
374
|
-
}),
|
|
375
|
-
children: "Nest"
|
|
528
|
+
"aria-label": `Remove ${userLabel(m)} from ${g.name}`,
|
|
529
|
+
onClick: () => act(`rm:${m.abz_sub}`, { action: "remove-member", groupId: g.id, abzSub: m.abz_sub }, () => loadDetail(g.id)),
|
|
530
|
+
children: "Remove"
|
|
376
531
|
}
|
|
377
532
|
)
|
|
378
|
-
] }),
|
|
379
|
-
/* @__PURE__ */ jsx("
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
533
|
+
] }, m.abz_sub)),
|
|
534
|
+
detail.members.length === 0 && /* @__PURE__ */ jsx("li", { style: { color: t.muted, fontSize: 13, padding: "6px 0" }, children: "No members yet." })
|
|
535
|
+
] }),
|
|
536
|
+
typeof detail.member_count === "number" && detail.members.length < detail.member_count && /* @__PURE__ */ jsxs(
|
|
537
|
+
"button",
|
|
538
|
+
{
|
|
539
|
+
style: { ...btn("ghost", true), marginTop: 6 },
|
|
540
|
+
disabled: busy,
|
|
541
|
+
onClick: () => loadMoreMembers(g.id, detail.members.length),
|
|
542
|
+
children: [
|
|
543
|
+
"Load more (",
|
|
544
|
+
detail.members.length,
|
|
545
|
+
" of ",
|
|
546
|
+
detail.member_count,
|
|
547
|
+
")"
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
),
|
|
551
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 10, flexWrap: "wrap", alignItems: "flex-start" }, children: [
|
|
552
|
+
/* @__PURE__ */ jsx(
|
|
553
|
+
Typeahead,
|
|
554
|
+
{
|
|
555
|
+
placeholder: "Search users to add\u2026",
|
|
556
|
+
disabled: busy,
|
|
557
|
+
s: { input: inputStyle, surface: t.surface, border: t.border, muted: t.muted, text: t.text },
|
|
558
|
+
search: async (q) => {
|
|
559
|
+
const d = await call({ action: "search-users", q, limit: 20 });
|
|
560
|
+
return (d.users ?? []).filter((u) => !detail.members.some((m) => m.abz_sub === u.abz_sub));
|
|
561
|
+
},
|
|
562
|
+
itemKey: (u) => u.abz_sub,
|
|
563
|
+
itemLabel: (u) => userLabel(u),
|
|
564
|
+
onPick: (u) => act(`add-member:${u.abz_sub}`, { action: "add-member", groupId: g.id, abzSub: u.abz_sub, role: addRole }, () => loadDetail(g.id))
|
|
565
|
+
}
|
|
566
|
+
),
|
|
567
|
+
/* @__PURE__ */ jsxs(
|
|
568
|
+
"select",
|
|
569
|
+
{
|
|
570
|
+
style: selectStyle,
|
|
571
|
+
value: addRole,
|
|
572
|
+
disabled: busy,
|
|
573
|
+
"aria-label": "Role for the new member",
|
|
574
|
+
onChange: (e) => setAddRole(e.target.value),
|
|
575
|
+
children: [
|
|
576
|
+
/* @__PURE__ */ jsx("option", { value: "member", children: "member" }),
|
|
577
|
+
/* @__PURE__ */ jsx("option", { value: "admin", children: "group admin" })
|
|
578
|
+
]
|
|
579
|
+
}
|
|
580
|
+
)
|
|
581
|
+
] }),
|
|
582
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, alignItems: "center", marginTop: 12, flexWrap: "wrap" }, children: [
|
|
583
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: t.muted }, children: "Subgroups:" }),
|
|
584
|
+
detail.subgroups.map((s) => /* @__PURE__ */ jsxs("span", { style: { ...chip, display: "inline-flex", gap: 6, alignItems: "center" }, children: [
|
|
585
|
+
s.name,
|
|
385
586
|
/* @__PURE__ */ jsx(
|
|
386
587
|
"button",
|
|
387
588
|
{
|
|
388
|
-
style:
|
|
589
|
+
style: { background: "none", border: "none", cursor: "pointer", color: t.danger, fontSize: 12, padding: 0 },
|
|
389
590
|
disabled: busy,
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
setOpen(null);
|
|
394
|
-
setDetail(null);
|
|
395
|
-
});
|
|
396
|
-
},
|
|
397
|
-
children: "Delete group"
|
|
591
|
+
"aria-label": `Un-nest ${s.name} from ${g.name}`,
|
|
592
|
+
onClick: () => act("unnest", { action: "remove-subgroup", groupId: g.id, childGroupId: s.id }, () => loadDetail(g.id)),
|
|
593
|
+
children: "\u2715"
|
|
398
594
|
}
|
|
399
|
-
)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
595
|
+
)
|
|
596
|
+
] }, s.id)),
|
|
597
|
+
detail.subgroups.length === 0 && /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: t.muted }, children: "none" }),
|
|
598
|
+
/* @__PURE__ */ jsx(
|
|
599
|
+
Typeahead,
|
|
600
|
+
{
|
|
601
|
+
placeholder: "Nest a group\u2026",
|
|
602
|
+
disabled: busy,
|
|
603
|
+
s: { input: inputStyle, surface: t.surface, border: t.border, muted: t.muted, text: t.text },
|
|
604
|
+
search: async (q) => {
|
|
605
|
+
const d = await call({ action: "list-groups", q, offset: 0 });
|
|
606
|
+
return (d.groups ?? []).filter((x) => x.id !== g.id && x.name !== SEALED && !detail.subgroups.some((s) => s.id === x.id));
|
|
607
|
+
},
|
|
608
|
+
itemKey: (x) => x.id,
|
|
609
|
+
itemLabel: (x) => x.name,
|
|
610
|
+
onPick: (x) => act(`nest:${x.id}`, { action: "add-subgroup", groupId: g.id, childGroupId: x.id }, () => loadDetail(g.id))
|
|
611
|
+
}
|
|
612
|
+
)
|
|
613
|
+
] }),
|
|
614
|
+
/* @__PURE__ */ jsx("div", { style: { marginTop: 12, display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: confirmDelete === g.id ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
615
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: t.text }, children: [
|
|
616
|
+
"Delete \u201C",
|
|
617
|
+
g.name,
|
|
618
|
+
"\u201D? Members lose this group everywhere it\u2019s used. This can\u2019t be undone."
|
|
619
|
+
] }),
|
|
620
|
+
/* @__PURE__ */ jsx(
|
|
621
|
+
"button",
|
|
622
|
+
{
|
|
623
|
+
style: btn("danger", true),
|
|
624
|
+
disabled: busy,
|
|
625
|
+
onClick: () => {
|
|
626
|
+
setConfirmDelete(null);
|
|
627
|
+
act("del", { action: "delete-group", groupId: g.id }, () => {
|
|
628
|
+
setOpen(null);
|
|
629
|
+
setDetail(null);
|
|
630
|
+
return searchGroups(groupQuery);
|
|
631
|
+
});
|
|
632
|
+
},
|
|
633
|
+
children: "Delete group"
|
|
634
|
+
}
|
|
635
|
+
),
|
|
636
|
+
/* @__PURE__ */ jsx("button", { style: btn("ghost", true), onClick: () => setConfirmDelete(null), children: "Cancel" })
|
|
637
|
+
] }) : /* @__PURE__ */ jsx("button", { style: { ...btn("ghost", true), color: t.danger }, disabled: busy, onClick: () => setConfirmDelete(g.id), children: "Delete group" }) })
|
|
638
|
+
] }) })
|
|
404
639
|
] })
|
|
405
640
|
] }, g.id);
|
|
406
641
|
}) }),
|
|
407
|
-
access === "ok" && groups !== null && /* @__PURE__ */
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
children: [
|
|
415
|
-
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: t.muted, fontSize: 11 }, children: showTraffic ? "\u25BE" : "\u25B8" }),
|
|
416
|
-
/* @__PURE__ */ jsx("strong", { style: { flex: 1 }, children: "Seen in traffic" }),
|
|
417
|
-
/* @__PURE__ */ jsxs("span", { style: chip, children: [
|
|
418
|
-
observed.filter((o) => !o.provisioned).length,
|
|
419
|
-
" new"
|
|
420
|
-
] })
|
|
421
|
-
]
|
|
422
|
-
}
|
|
423
|
-
),
|
|
424
|
-
showTraffic && /* @__PURE__ */ jsxs("ul", { style: { listStyle: "none", margin: 0, padding: "0 16px 14px 34px" }, children: [
|
|
425
|
-
observed.length === 0 && /* @__PURE__ */ jsx("li", { style: { color: t.muted, fontSize: 13 }, children: "No identities observed yet \u2014 they appear here after their first API call." }),
|
|
426
|
-
observed.map((o) => /* @__PURE__ */ jsxs("li", { style: { display: "flex", gap: 8, alignItems: "center", padding: "6px 0", flexWrap: "wrap" }, children: [
|
|
427
|
-
/* @__PURE__ */ jsx("span", { style: { flex: 1, minWidth: 140, fontFamily: t.monoFontFamily, fontSize: 13 }, children: o.email || o.abz_sub }),
|
|
428
|
-
o.source_label && /* @__PURE__ */ jsx("span", { style: chip, children: o.source_label }),
|
|
429
|
-
o.last_seen && /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, color: t.muted }, children: [
|
|
430
|
-
"seen ",
|
|
431
|
-
relTime(o.last_seen),
|
|
432
|
-
o.seen_count ? ` \xB7 ${o.seen_count}\xD7` : ""
|
|
433
|
-
] }),
|
|
434
|
-
o.provisioned ? /* @__PURE__ */ jsxs("span", { style: { ...chip, color: t.success, borderColor: t.success }, children: [
|
|
435
|
-
"user",
|
|
436
|
-
o.groups?.length ? ` \xB7 ${o.groups.join(", ")}` : ""
|
|
437
|
-
] }) : /* @__PURE__ */ jsx(
|
|
438
|
-
"button",
|
|
439
|
-
{
|
|
440
|
-
style: btn("primary", true),
|
|
441
|
-
disabled: busy,
|
|
442
|
-
"aria-label": `Add ${o.email || o.abz_sub} as a user`,
|
|
443
|
-
onClick: () => act("provision", { action: "provision-observed", consumerUserId: o.abz_sub, email: o.email ?? void 0 }),
|
|
444
|
-
children: "Add as user"
|
|
445
|
-
}
|
|
446
|
-
)
|
|
447
|
-
] }, o.abz_sub))
|
|
448
|
-
] })
|
|
449
|
-
] }),
|
|
642
|
+
access === "ok" && groups !== null && groups.length > 0 && groups.length < groupTotal && /* @__PURE__ */ jsx("div", { style: { padding: "10px 16px", borderTop: `1px solid ${t.border}` }, children: /* @__PURE__ */ jsxs("button", { style: btn("ghost", true), disabled: busy, onClick: loadMoreGroups, children: [
|
|
643
|
+
"Load more groups (",
|
|
644
|
+
groups.length,
|
|
645
|
+
" of ",
|
|
646
|
+
groupTotal,
|
|
647
|
+
")"
|
|
648
|
+
] }) }),
|
|
450
649
|
err && /* @__PURE__ */ jsx("div", { role: "alert", style: { padding: "0 16px 14px", fontSize: 12, color: t.danger }, children: err })
|
|
451
650
|
] })
|
|
452
651
|
] });
|