@strapi-community/plugin-better-auth-dashboard 1.0.0-beta.1 → 1.0.0

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.
Files changed (46) hide show
  1. package/README.md +0 -3
  2. package/dist/admin/Avatar-CdT7eBCh.js +39 -0
  3. package/dist/admin/Avatar-zlMTAZh3.mjs +40 -0
  4. package/dist/admin/{Root-2FMy-qQg.mjs → Root-CgVBJuoM.mjs} +12 -4
  5. package/dist/admin/{Root-DDi0mtve.js → Root-CsiOnzlB.js} +12 -4
  6. package/dist/admin/cs-C_jSgeON.js +416 -0
  7. package/dist/admin/cs-wtnknvBC.mjs +416 -0
  8. package/dist/admin/de-Cg5HyoCY.js +416 -0
  9. package/dist/admin/de-DnnVdUxk.mjs +416 -0
  10. package/dist/admin/en-B3utEgMT.mjs +416 -0
  11. package/dist/admin/en-Cz1IJEvp.js +416 -0
  12. package/dist/admin/es-CGA0Tslt.js +416 -0
  13. package/dist/admin/es-RoCEriJL.mjs +416 -0
  14. package/dist/admin/fr-Ca74t21t.js +416 -0
  15. package/dist/admin/fr-CtmuSfiQ.mjs +416 -0
  16. package/dist/admin/{index-BHfYu0uA.js → index-BCBu-0nr.js} +65 -10
  17. package/dist/admin/{index-D9CiI8Yr.mjs → index-BI2WIsds.mjs} +73 -18
  18. package/dist/admin/index-CPSoAbZt.js +2321 -0
  19. package/dist/admin/index-D9ttdrT3.js +2716 -0
  20. package/dist/admin/index-DQdn5s8q.js +1184 -0
  21. package/dist/admin/index-DvfwvCj0.mjs +1182 -0
  22. package/dist/admin/index-VyHXoQ6p.mjs +2321 -0
  23. package/dist/admin/index-gFqHlMLP.mjs +2716 -0
  24. package/dist/admin/index.js +1 -1
  25. package/dist/admin/index.mjs +1 -1
  26. package/dist/admin/it-BAB9hDdp.mjs +416 -0
  27. package/dist/admin/it-dYT2_ERV.js +416 -0
  28. package/dist/admin/nl-CVqSbUU9.js +416 -0
  29. package/dist/admin/nl-CYv26PHv.mjs +416 -0
  30. package/dist/admin/pl-Bc0cS2mD.mjs +416 -0
  31. package/dist/admin/pl-HYF4BxRv.js +416 -0
  32. package/dist/admin/pt-DDUGyLtb.js +416 -0
  33. package/dist/admin/pt-DZyyK3EG.mjs +416 -0
  34. package/dist/admin/{useModelSchema-CqOTI7P1.js → useModelSchema-BogQah8f.js} +309 -287
  35. package/dist/admin/{useModelSchema-B2tmzlwQ.mjs → useModelSchema-C6bxNjW4.mjs} +320 -298
  36. package/dist/server/index.js +15 -15
  37. package/dist/server/index.mjs +15 -15
  38. package/package.json +4 -4
  39. package/dist/admin/Avatar-Cvk89aMx.js +0 -67
  40. package/dist/admin/Avatar-DNczJU0i.mjs +0 -66
  41. package/dist/admin/index-BQi_BUi6.mjs +0 -1968
  42. package/dist/admin/index-BV-4M_Fp.js +0 -1970
  43. package/dist/admin/index-CANDPUGD.mjs +0 -1215
  44. package/dist/admin/index-CqR8WpOa.js +0 -1217
  45. package/dist/admin/index-D6ktNZcj.js +0 -1685
  46. package/dist/admin/index-DszX3ibA.mjs +0 -1683
@@ -1,1685 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const designSystem = require("@strapi/design-system");
5
- const icons = require("@strapi/icons");
6
- const admin = require("@strapi/strapi/admin");
7
- const react = require("react");
8
- const reactQuery = require("react-query");
9
- const styled = require("styled-components");
10
- const index = require("./index-BHfYu0uA.js");
11
- const useModelSchema = require("./useModelSchema-CqOTI7P1.js");
12
- const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
13
- const styled__default = /* @__PURE__ */ _interopDefault(styled);
14
- function UserCombobox({
15
- label = "User",
16
- hint,
17
- placeholder = "Search users by email or name…",
18
- value,
19
- onChange,
20
- error,
21
- required
22
- }) {
23
- const [search, setSearch] = react.useState("");
24
- const { data, isLoading } = reactQuery.useQuery({
25
- queryKey: ["user-combobox-search", search],
26
- queryFn: async () => {
27
- const whereClause = search ? JSON.stringify([
28
- {
29
- field: "email",
30
- operator: "contains",
31
- value: search,
32
- connector: "OR"
33
- },
34
- {
35
- field: "name",
36
- operator: "contains",
37
- value: search,
38
- connector: "OR"
39
- }
40
- ]) : void 0;
41
- const result = await index.client.dash.listUsers({
42
- query: {
43
- limit: search ? 100 : 20,
44
- offset: 0,
45
- sortBy: "createdAt",
46
- sortOrder: "desc",
47
- ...whereClause ? { where: whereClause } : {}
48
- }
49
- });
50
- return result.data?.users ?? [];
51
- },
52
- keepPreviousData: true
53
- });
54
- const users = data ?? [];
55
- return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { hint, error, style: { width: "100%" }, children: [
56
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: label }),
57
- /* @__PURE__ */ jsxRuntime.jsx(
58
- designSystem.Combobox,
59
- {
60
- placeholder,
61
- value,
62
- onChange: (val) => onChange(val),
63
- onInputChange: (e) => setSearch(e.target.value),
64
- loading: isLoading,
65
- required,
66
- children: users.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ComboboxOption, { value: user.id, children: [
67
- user.name,
68
- " — ",
69
- user.email
70
- ] }, user.id))
71
- }
72
- ),
73
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
74
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
75
- ] });
76
- }
77
- function CreateOrganizationDialog({ teamsEnabled, onClose }) {
78
- const qc = reactQuery.useQueryClient();
79
- const { toggleNotification } = admin.useNotification();
80
- const [name, setName] = react.useState("");
81
- const [slug, setSlug] = react.useState("");
82
- const [logo, setLogo] = react.useState("");
83
- const [ownerId, setOwnerId] = react.useState("");
84
- const [skipDefaultTeam, setSkipDefaultTeam] = react.useState(false);
85
- const [defaultTeamName, setDefaultTeamName] = react.useState("");
86
- const createMutation = reactQuery.useMutation({
87
- mutationFn: async () => {
88
- const result = await index.client.dash.organization.create(
89
- {
90
- name,
91
- slug,
92
- ...logo ? { logo } : {},
93
- ...teamsEnabled && !skipDefaultTeam && defaultTeamName ? { defaultTeamName } : {}
94
- },
95
- useModelSchema.withContext({ userId: ownerId, skipDefaultTeam }, index.getAuthHeaders())
96
- );
97
- if (result.error)
98
- throw new Error(result.error.message ?? "Create failed");
99
- return result.data;
100
- },
101
- onSuccess: () => {
102
- qc.invalidateQueries({ queryKey: ["dash-organizations"] });
103
- toggleNotification({
104
- type: "success",
105
- message: "Organization created successfully"
106
- });
107
- onClose();
108
- },
109
- onError: (err) => {
110
- toggleNotification({
111
- type: "danger",
112
- message: err.message ?? "Failed to create organization"
113
- });
114
- }
115
- });
116
- const handleNameChange = (e) => {
117
- const val = e.target.value;
118
- setName(val);
119
- if (!slug || slug === slugify(name)) {
120
- setSlug(slugify(val));
121
- }
122
- };
123
- const footer = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
124
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: onClose, children: "Cancel" }),
125
- /* @__PURE__ */ jsxRuntime.jsx(
126
- designSystem.Button,
127
- {
128
- loading: createMutation.isLoading,
129
- disabled: !name || !slug || !ownerId,
130
- onClick: () => createMutation.mutate(),
131
- "data-testid": "create-org-submit",
132
- children: "Create organization"
133
- }
134
- )
135
- ] });
136
- return /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.Drawer, { title: "Create organization", footer, onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, children: [
137
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
138
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Details" }),
139
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, required: true, children: [
140
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Name" }),
141
- /* @__PURE__ */ jsxRuntime.jsx(
142
- designSystem.TextInput,
143
- {
144
- name: "name",
145
- value: name,
146
- onChange: handleNameChange,
147
- placeholder: "Acme Corp"
148
- }
149
- )
150
- ] }),
151
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, hint: "Used in URLs", required: true, children: [
152
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Slug" }),
153
- /* @__PURE__ */ jsxRuntime.jsx(
154
- designSystem.TextInput,
155
- {
156
- name: "slug",
157
- value: slug,
158
- onChange: (e) => setSlug(e.target.value),
159
- placeholder: "acme-corp"
160
- }
161
- ),
162
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
163
- ] }),
164
- /* @__PURE__ */ jsxRuntime.jsx(
165
- useModelSchema.MediaPickerField,
166
- {
167
- label: "Logo URL",
168
- name: "logo",
169
- value: logo,
170
- onChange: setLogo,
171
- placeholder: "https://example.com/logo.png",
172
- hint: "Optional — publicly accessible image URL"
173
- }
174
- )
175
- ] }),
176
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
177
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Owner" }),
178
- /* @__PURE__ */ jsxRuntime.jsx(
179
- UserCombobox,
180
- {
181
- label: "Owner",
182
- hint: "The user who will own and manage this organization",
183
- value: ownerId,
184
- onChange: setOwnerId,
185
- required: true
186
- }
187
- )
188
- ] }),
189
- teamsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
190
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Default team" }),
191
- /* @__PURE__ */ jsxRuntime.jsx(
192
- designSystem.Checkbox,
193
- {
194
- name: "skipDefaultTeam",
195
- checked: skipDefaultTeam,
196
- onCheckedChange: (checked) => setSkipDefaultTeam(checked),
197
- children: "Don't create a default team"
198
- }
199
- ),
200
- !skipDefaultTeam && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, children: [
201
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Default team name" }),
202
- /* @__PURE__ */ jsxRuntime.jsx(
203
- designSystem.TextInput,
204
- {
205
- name: "defaultTeamName",
206
- value: defaultTeamName,
207
- onChange: (e) => setDefaultTeamName(e.target.value),
208
- placeholder: "Leave blank to use the system default"
209
- }
210
- )
211
- ] }),
212
- skipDefaultTeam && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "Members can be added to teams manually after the organization is created." }) })
213
- ] })
214
- ] }) });
215
- }
216
- function slugify(str) {
217
- return str.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
218
- }
219
- const ExpandableRow = styled__default.default.div`
220
- width: 100%;
221
- background: white;
222
- border: 1px solid #dcdce4;
223
- border-radius: 8px;
224
- overflow: hidden;
225
- `;
226
- const ExpandableRowHeader = styled__default.default.div`
227
- display: flex;
228
- align-items: center;
229
- justify-content: space-between;
230
- padding: 10px 14px;
231
- gap: 12px;
232
- cursor: pointer;
233
- &:hover {
234
- background: #fafafe;
235
- }
236
- `;
237
- const ExpandableRowBody = styled__default.default.div`
238
- padding: 12px 14px;
239
- border-top: 1px solid #eaeaef;
240
- background: #fafafe;
241
- display: flex;
242
- flex-direction: column;
243
- gap: 10px;
244
- `;
245
- const STANDARD_ORG_FIELDS = /* @__PURE__ */ new Set([
246
- "id",
247
- "name",
248
- "slug",
249
- "logo",
250
- "metadata",
251
- "memberCount",
252
- "createdAt",
253
- "updatedAt"
254
- ]);
255
- function OrganizationDetail({
256
- organizationId,
257
- teamsEnabled,
258
- onClose
259
- }) {
260
- const schemaQuery = useModelSchema.useModelSchema("organization");
261
- const qc = reactQuery.useQueryClient();
262
- const { toggleNotification } = admin.useNotification();
263
- const { get, put } = admin.useFetchClient();
264
- const orgQuery = reactQuery.useQuery({
265
- queryKey: ["dash-org", organizationId],
266
- queryFn: async () => {
267
- const result = await index.client.dash.organization[organizationId](
268
- {},
269
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
270
- );
271
- if (result.error)
272
- throw new Error(result.error.message ?? "Failed to load org");
273
- return result.data;
274
- }
275
- });
276
- const membersQuery = reactQuery.useQuery({
277
- queryKey: ["dash-org-members", organizationId],
278
- queryFn: async () => {
279
- const result = await index.client.dash.organization[organizationId].members({}, useModelSchema.withContext({ organizationId }, index.getAuthHeaders()));
280
- if (result.error) throw new Error(result.error.message ?? "Failed");
281
- return result.data ?? [];
282
- }
283
- });
284
- const teamsQuery = reactQuery.useQuery({
285
- queryKey: ["dash-org-teams", organizationId],
286
- queryFn: async () => {
287
- if (!teamsEnabled) return [];
288
- const result = await index.client.dash.organization[organizationId].teams({}, useModelSchema.withContext({ organizationId }, index.getAuthHeaders()));
289
- if (result.error) throw new Error(result.error.message ?? "Failed");
290
- return result.data ?? [];
291
- },
292
- enabled: teamsEnabled
293
- });
294
- const ssoQuery = reactQuery.useQuery({
295
- queryKey: ["dash-org-sso", organizationId],
296
- queryFn: async () => {
297
- const result = await index.client.dash.organization[organizationId].ssoProviders({}, useModelSchema.withContext({ organizationId }, index.getAuthHeaders()));
298
- if (result.error) throw new Error(result.error.message ?? "Failed");
299
- return result.data ?? [];
300
- }
301
- });
302
- const invitationsQuery = reactQuery.useQuery({
303
- queryKey: ["dash-org-invitations", organizationId],
304
- queryFn: async () => {
305
- const result = await index.client.dash.organization[organizationId].invitations({}, useModelSchema.withContext({ organizationId }, index.getAuthHeaders()));
306
- if (result.error) throw new Error(result.error.message ?? "Failed");
307
- return result.data ?? [];
308
- }
309
- });
310
- const strapiOrgQuery = reactQuery.useQuery({
311
- queryKey: ["dash-strapi-org", organizationId],
312
- enabled: !!orgQuery.data,
313
- queryFn: async () => {
314
- const { data } = await get(
315
- `/better-auth-dashboard/db?uid=plugin::better-auth.organization&filters[id][$eq]=${organizationId}&pagination[pageSize]=1`
316
- );
317
- return data.results?.[0] ?? null;
318
- }
319
- });
320
- const [activeTab, setActiveTab] = react.useState("details");
321
- const [editName, setEditName] = react.useState(void 0);
322
- const [editSlug, setEditSlug] = react.useState(void 0);
323
- const [editLogo, setEditLogo] = react.useState(void 0);
324
- const [editExtra, setEditExtra] = react.useState({});
325
- const [confirmRemoveMemberId, setConfirmRemoveMemberId] = react.useState(null);
326
- const [confirmDeleteTeamId, setConfirmDeleteTeamId] = react.useState(
327
- null
328
- );
329
- const [confirmDeleteSsoId, setConfirmDeleteSsoId] = react.useState(
330
- null
331
- );
332
- const [inviteEmail, setInviteEmail] = react.useState("");
333
- const [inviteRole, setInviteRole] = react.useState("member");
334
- const [confirmCancelInvitationId, setConfirmCancelInvitationId] = react.useState(null);
335
- const handleExtraChange = (name, value) => {
336
- setEditExtra((prev) => ({ ...prev, [name]: value }));
337
- };
338
- const org = orgQuery.data;
339
- const extraData = {
340
- ...strapiOrgQuery.data ?? {},
341
- ...editExtra
342
- };
343
- const updateOrgMutation = reactQuery.useMutation({
344
- mutationFn: async () => {
345
- const body = { ...editExtra };
346
- if (editName !== void 0) body.name = editName;
347
- if (editSlug !== void 0) body.slug = editSlug;
348
- if (editLogo !== void 0) body.logo = editLogo;
349
- const documentId = strapiOrgQuery.data?.documentId;
350
- if (!documentId)
351
- throw new Error("Could not resolve documentId for organization");
352
- await put(
353
- `/better-auth-dashboard/db/${documentId}?uid=plugin::better-auth.organization`,
354
- body
355
- );
356
- },
357
- onSuccess: async () => {
358
- await qc.invalidateQueries({
359
- queryKey: ["dash-strapi-org", organizationId]
360
- });
361
- qc.invalidateQueries({ queryKey: ["dash-org", organizationId] });
362
- qc.invalidateQueries({ queryKey: ["dash-organizations"] });
363
- qc.invalidateQueries({ queryKey: ["dash-strapi-org", organizationId] });
364
- setEditName(void 0);
365
- setEditSlug(void 0);
366
- setEditLogo(void 0);
367
- setEditExtra({});
368
- toggleNotification({
369
- type: "success",
370
- message: "Organization updated successfully"
371
- });
372
- onClose();
373
- },
374
- onError: (err) => {
375
- toggleNotification({
376
- type: "danger",
377
- message: err.message ?? "Update failed"
378
- });
379
- }
380
- });
381
- const [addUserId, setAddUserId] = react.useState("");
382
- const [addRole, setAddRole] = react.useState("member");
383
- const addMemberMutation = reactQuery.useMutation({
384
- mutationFn: async () => {
385
- const result = await index.client.dash.organization.addMember(
386
- { userId: addUserId, role: addRole },
387
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
388
- );
389
- if (result.error)
390
- throw new Error(result.error.message ?? "Add member failed");
391
- return result.data;
392
- },
393
- onSuccess: () => {
394
- qc.invalidateQueries({ queryKey: ["dash-org-members", organizationId] });
395
- setAddUserId("");
396
- setAddRole("member");
397
- toggleNotification({ type: "success", message: "Member added" });
398
- },
399
- onError: (err) => {
400
- toggleNotification({
401
- type: "danger",
402
- message: err.message ?? "Failed to add member"
403
- });
404
- }
405
- });
406
- const removeMemberMutation = reactQuery.useMutation({
407
- mutationFn: async (memberId) => {
408
- const result = await index.client.dash.organization.removeMember(
409
- { memberId },
410
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
411
- );
412
- if (result.error) throw new Error(result.error.message ?? "Failed");
413
- },
414
- onSuccess: () => {
415
- setConfirmRemoveMemberId(null);
416
- qc.invalidateQueries({ queryKey: ["dash-org-members", organizationId] });
417
- toggleNotification({ type: "success", message: "Member removed" });
418
- },
419
- onError: (err) => {
420
- toggleNotification({
421
- type: "danger",
422
- message: err.message ?? "Failed to remove member"
423
- });
424
- }
425
- });
426
- const updateRoleMutation = reactQuery.useMutation({
427
- mutationFn: async ({
428
- memberId,
429
- role
430
- }) => {
431
- const result = await index.client.dash.organization.updateMemberRole(
432
- { memberId, role },
433
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
434
- );
435
- if (result.error) throw new Error(result.error.message ?? "Failed");
436
- return result.data;
437
- },
438
- onSuccess: () => {
439
- qc.invalidateQueries({ queryKey: ["dash-org-members", organizationId] });
440
- toggleNotification({ type: "success", message: "Role updated" });
441
- },
442
- onError: (err) => {
443
- toggleNotification({
444
- type: "danger",
445
- message: err.message ?? "Failed to update role"
446
- });
447
- }
448
- });
449
- const [newTeamName, setNewTeamName] = react.useState("");
450
- const createTeamMutation = reactQuery.useMutation({
451
- mutationFn: async () => {
452
- const result = await index.client.dash.organization.createTeam(
453
- { name: newTeamName },
454
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
455
- );
456
- if (result.error)
457
- throw new Error(result.error.message ?? "Create team failed");
458
- return result.data;
459
- },
460
- onSuccess: () => {
461
- qc.invalidateQueries({ queryKey: ["dash-org-teams", organizationId] });
462
- setNewTeamName("");
463
- toggleNotification({ type: "success", message: "Team created" });
464
- },
465
- onError: (err) => {
466
- toggleNotification({
467
- type: "danger",
468
- message: err.message ?? "Failed to create team"
469
- });
470
- }
471
- });
472
- const deleteTeamMutation = reactQuery.useMutation({
473
- mutationFn: async (teamId) => {
474
- const result = await index.client.dash.organization.deleteTeam(
475
- { teamId },
476
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
477
- );
478
- if (result.error) throw new Error(result.error.message ?? "Failed");
479
- },
480
- onSuccess: () => {
481
- setConfirmDeleteTeamId(null);
482
- qc.invalidateQueries({ queryKey: ["dash-org-teams", organizationId] });
483
- toggleNotification({ type: "success", message: "Team deleted" });
484
- },
485
- onError: (err) => {
486
- toggleNotification({
487
- type: "danger",
488
- message: err.message ?? "Failed to delete team"
489
- });
490
- }
491
- });
492
- const deleteSsoMutation = reactQuery.useMutation({
493
- mutationFn: async (providerId) => {
494
- const result = await index.client.dash.organization[organizationId].ssoProvider.delete(
495
- { providerId },
496
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
497
- );
498
- if (result.error) throw new Error(result.error.message ?? "Failed");
499
- },
500
- onSuccess: () => {
501
- setConfirmDeleteSsoId(null);
502
- qc.invalidateQueries({ queryKey: ["dash-org-sso", organizationId] });
503
- toggleNotification({ type: "success", message: "SSO provider deleted" });
504
- },
505
- onError: (err) => {
506
- toggleNotification({
507
- type: "danger",
508
- message: err.message ?? "Failed to delete SSO provider"
509
- });
510
- }
511
- });
512
- const inviteMemberMutation = reactQuery.useMutation({
513
- mutationFn: async () => {
514
- const result = await index.client.dash.organization.inviteMember(
515
- { email: inviteEmail, role: inviteRole, invitedBy: "" },
516
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
517
- );
518
- if (result.error) throw new Error(result.error.message ?? "Failed");
519
- return result.data;
520
- },
521
- onSuccess: () => {
522
- qc.invalidateQueries({
523
- queryKey: ["dash-org-invitations", organizationId]
524
- });
525
- setInviteEmail("");
526
- setInviteRole("member");
527
- toggleNotification({ type: "success", message: "Invitation sent" });
528
- },
529
- onError: (err) => {
530
- toggleNotification({
531
- type: "danger",
532
- message: err.message ?? "Failed to invite"
533
- });
534
- }
535
- });
536
- const cancelInvitationMutation = reactQuery.useMutation({
537
- mutationFn: async (invitationId) => {
538
- const result = await index.client.dash.organization.cancelInvitation(
539
- { invitationId },
540
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
541
- );
542
- if (result.error) throw new Error(result.error.message ?? "Failed");
543
- },
544
- onSuccess: () => {
545
- setConfirmCancelInvitationId(null);
546
- qc.invalidateQueries({
547
- queryKey: ["dash-org-invitations", organizationId]
548
- });
549
- toggleNotification({ type: "success", message: "Invitation cancelled" });
550
- },
551
- onError: (err) => {
552
- toggleNotification({
553
- type: "danger",
554
- message: err.message ?? "Failed to cancel"
555
- });
556
- }
557
- });
558
- const resendInvitationMutation = reactQuery.useMutation({
559
- mutationFn: async (invitationId) => {
560
- const result = await index.client.dash.organization.resendInvitation(
561
- { invitationId },
562
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
563
- );
564
- if (result.error) throw new Error(result.error.message ?? "Failed");
565
- },
566
- onSuccess: () => {
567
- toggleNotification({ type: "success", message: "Invitation resent" });
568
- },
569
- onError: (err) => {
570
- toggleNotification({
571
- type: "danger",
572
- message: err.message ?? "Failed to resend"
573
- });
574
- }
575
- });
576
- const members = membersQuery.data ?? [];
577
- const teams = teamsQuery.data ?? [];
578
- const ssoProviders = ssoQuery.data ?? [];
579
- const invitations = invitationsQuery.data ?? [];
580
- const hasOrgEdits = editName !== void 0 || editSlug !== void 0 || editLogo !== void 0 || Object.keys(editExtra).length > 0;
581
- const customFields = Object.entries(schemaQuery.data ?? {}).filter(([name]) => !STANDARD_ORG_FIELDS.has(name)).map(([name, attribute]) => ({ name, attribute }));
582
- const detailsFooter = activeTab === "details" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
583
- /* @__PURE__ */ jsxRuntime.jsx(
584
- designSystem.Button,
585
- {
586
- variant: "tertiary",
587
- onClick: () => {
588
- setEditName(void 0);
589
- setEditSlug(void 0);
590
- setEditLogo(void 0);
591
- setEditExtra({});
592
- },
593
- children: "Discard"
594
- }
595
- ),
596
- /* @__PURE__ */ jsxRuntime.jsx(
597
- designSystem.Button,
598
- {
599
- disabled: !hasOrgEdits,
600
- loading: updateOrgMutation.isLoading,
601
- onClick: () => updateOrgMutation.mutate(),
602
- "data-testid": "save-org-btn",
603
- children: "Save changes"
604
- }
605
- )
606
- ] }) : void 0;
607
- return /* @__PURE__ */ jsxRuntime.jsxs(
608
- useModelSchema.Drawer,
609
- {
610
- title: org?.name ?? "Organization",
611
- footer: detailsFooter,
612
- onClose,
613
- "data-testid": "org-detail-drawer",
614
- children: [
615
- orgQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : orgQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx(
616
- designSystem.Alert,
617
- {
618
- closeLabel: "Close",
619
- title: "Failed to load organization",
620
- variant: "danger",
621
- children: orgQuery.error instanceof Error ? orgQuery.error.message : "An error occurred"
622
- }
623
- ) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Root, { defaultValue: "details", onValueChange: setActiveTab, children: [
624
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.List, { children: [
625
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Trigger, { value: "details", children: "Details" }),
626
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Trigger, { value: "members", "data-testid": "org-members-tab", children: [
627
- "Members (",
628
- members.length,
629
- ")"
630
- ] }),
631
- teamsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Trigger, { value: "teams", children: [
632
- "Teams (",
633
- teams.length,
634
- ")"
635
- ] }),
636
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Trigger, { value: "invitations", children: [
637
- "Invitations (",
638
- invitations.length,
639
- ")"
640
- ] }),
641
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tabs.Trigger, { value: "sso", children: [
642
- "SSO (",
643
- ssoProviders.length,
644
- ")"
645
- ] })
646
- ] }),
647
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "details", children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.EditLayout, { children: [
648
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, children: [
649
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
650
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Details" }),
651
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { style: { width: "100%" }, children: [
652
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Name" }),
653
- /* @__PURE__ */ jsxRuntime.jsx(
654
- designSystem.TextInput,
655
- {
656
- value: editName ?? org?.name ?? "",
657
- onChange: (e) => setEditName(e.target.value),
658
- "data-testid": "org-name-input"
659
- }
660
- )
661
- ] }),
662
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { hint: "Used in URLs", style: { width: "100%" }, children: [
663
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Slug" }),
664
- /* @__PURE__ */ jsxRuntime.jsx(
665
- designSystem.TextInput,
666
- {
667
- value: editSlug ?? org?.slug ?? "",
668
- onChange: (e) => setEditSlug(e.target.value),
669
- "data-testid": "org-slug-input"
670
- }
671
- ),
672
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
673
- ] }),
674
- /* @__PURE__ */ jsxRuntime.jsx(
675
- useModelSchema.MediaPickerField,
676
- {
677
- label: "Logo URL",
678
- name: "logo",
679
- value: editLogo ?? org?.logo ?? "",
680
- onChange: setEditLogo,
681
- placeholder: "https://example.com/logo.png",
682
- hint: "Optional — publicly accessible image URL"
683
- }
684
- )
685
- ] }),
686
- /* @__PURE__ */ jsxRuntime.jsx(
687
- useModelSchema.CustomFieldsSection,
688
- {
689
- fields: customFields,
690
- data: extraData,
691
- onChange: handleExtraChange
692
- }
693
- )
694
- ] }),
695
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.EditSidebar, { children: [
696
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
697
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Details" }),
698
- /* @__PURE__ */ jsxRuntime.jsxs(
699
- "div",
700
- {
701
- style: {
702
- display: "grid",
703
- gridTemplateColumns: "1fr 1fr",
704
- gap: 12
705
- },
706
- children: [
707
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.MetaItem, { style: { gridColumn: "1 / -1" }, children: [
708
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaKey, { children: "Organization ID" }),
709
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MonoChip, { children: org?.id })
710
- ] }),
711
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.MetaItem, { children: [
712
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaKey, { children: "Members" }),
713
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaVal, { children: org?.memberCount ?? 0 })
714
- ] }),
715
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.MetaItem, { children: [
716
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaKey, { children: "Created" }),
717
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.MetaVal, { children: org?.createdAt ? new Date(org.createdAt).toLocaleDateString() : "—" })
718
- ] })
719
- ]
720
- }
721
- )
722
- ] }),
723
- /* @__PURE__ */ jsxRuntime.jsx(
724
- useModelSchema.EditViewSidePanels,
725
- {
726
- model: "plugin::better-auth.organization",
727
- documentId: strapiOrgQuery.data?.documentId,
728
- document: strapiOrgQuery.data ?? void 0
729
- }
730
- )
731
- ] })
732
- ] }) }),
733
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "members", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
734
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
735
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Add member" }),
736
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 3, children: [
737
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
738
- UserCombobox,
739
- {
740
- label: "User",
741
- value: addUserId,
742
- onChange: setAddUserId
743
- }
744
- ) }),
745
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
746
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Role" }),
747
- /* @__PURE__ */ jsxRuntime.jsxs(
748
- designSystem.SingleSelect,
749
- {
750
- value: addRole,
751
- onChange: (role) => setAddRole(String(role)),
752
- "aria-label": "Member role",
753
- children: [
754
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "member", children: "Member" }),
755
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "admin", children: "Admin" }),
756
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "owner", children: "Owner" })
757
- ]
758
- }
759
- )
760
- ] }) })
761
- ] }),
762
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
763
- designSystem.Button,
764
- {
765
- size: "S",
766
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
767
- disabled: !addUserId,
768
- loading: addMemberMutation.isLoading,
769
- onClick: () => addMemberMutation.mutate(),
770
- "data-testid": "add-member-btn",
771
- children: "Add member"
772
- }
773
- ) })
774
- ] }),
775
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
776
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.SectionLabel, { children: [
777
- "Members (",
778
- members.length,
779
- ")"
780
- ] }),
781
- membersQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : members.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No members yet." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: members.map((member) => /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { "data-testid": "member-row", children: [
782
- /* @__PURE__ */ jsxRuntime.jsxs(
783
- designSystem.Flex,
784
- {
785
- direction: "column",
786
- gap: 1,
787
- alignItems: "flex-start",
788
- style: { flex: 1 },
789
- children: [
790
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: member.user?.name ?? "Unknown" }),
791
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: member.user?.email })
792
- ]
793
- }
794
- ),
795
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
796
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { style: { width: 110 }, children: /* @__PURE__ */ jsxRuntime.jsxs(
797
- designSystem.SingleSelect,
798
- {
799
- value: member.role,
800
- onChange: (role) => updateRoleMutation.mutate({
801
- memberId: member.id,
802
- role: String(role)
803
- }),
804
- size: "S",
805
- "aria-label": "Member role",
806
- children: [
807
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "member", children: "Member" }),
808
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "admin", children: "Admin" }),
809
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "owner", children: "Owner" })
810
- ]
811
- }
812
- ) }),
813
- /* @__PURE__ */ jsxRuntime.jsx(
814
- designSystem.IconButton,
815
- {
816
- label: "Remove member",
817
- onClick: () => setConfirmRemoveMemberId(member.id),
818
- "data-testid": "remove-member-btn",
819
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
820
- }
821
- )
822
- ] })
823
- ] }, member.id)) })
824
- ] })
825
- ] }) }),
826
- teamsEnabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "teams", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
827
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
828
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Create team" }),
829
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-end", children: [
830
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
831
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Team name" }),
832
- /* @__PURE__ */ jsxRuntime.jsx(
833
- designSystem.TextInput,
834
- {
835
- value: newTeamName,
836
- onChange: (e) => setNewTeamName(e.target.value),
837
- placeholder: "e.g. Engineering"
838
- }
839
- )
840
- ] }) }),
841
- /* @__PURE__ */ jsxRuntime.jsx(
842
- designSystem.Button,
843
- {
844
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
845
- disabled: !newTeamName,
846
- loading: createTeamMutation.isLoading,
847
- onClick: () => createTeamMutation.mutate(),
848
- children: "Create"
849
- }
850
- )
851
- ] })
852
- ] }),
853
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
854
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.SectionLabel, { children: [
855
- "Teams (",
856
- teams.length,
857
- ")"
858
- ] }),
859
- teamsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : teams.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No teams yet." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: teams.map((team) => /* @__PURE__ */ jsxRuntime.jsx(
860
- TeamRow,
861
- {
862
- team,
863
- organizationId,
864
- onDelete: () => setConfirmDeleteTeamId(team.id)
865
- },
866
- team.id
867
- )) })
868
- ] })
869
- ] }) }),
870
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "sso", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
871
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "SSO providers" }),
872
- ssoQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : ssoProviders.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No SSO providers configured." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: ssoProviders.map((provider) => /* @__PURE__ */ jsxRuntime.jsxs(
873
- useModelSchema.AccountRow,
874
- {
875
- "data-testid": "sso-provider-row",
876
- children: [
877
- /* @__PURE__ */ jsxRuntime.jsxs(
878
- designSystem.Flex,
879
- {
880
- direction: "column",
881
- gap: 1,
882
- alignItems: "flex-start",
883
- style: { flex: 1 },
884
- children: [
885
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.ProviderBadge, { children: provider.providerId }),
886
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: provider.domain }),
887
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
888
- "Issuer: ",
889
- provider.issuer
890
- ] })
891
- ]
892
- }
893
- ),
894
- /* @__PURE__ */ jsxRuntime.jsx(
895
- designSystem.IconButton,
896
- {
897
- label: "Delete SSO provider",
898
- onClick: () => setConfirmDeleteSsoId(provider.providerId),
899
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
900
- }
901
- )
902
- ]
903
- },
904
- provider.id
905
- )) })
906
- ] }) }) }),
907
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "invitations", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, paddingTop: 6, children: [
908
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
909
- /* @__PURE__ */ jsxRuntime.jsx(useModelSchema.SectionLabel, { children: "Invite member by email" }),
910
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 3, children: [
911
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 8, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
912
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Email address" }),
913
- /* @__PURE__ */ jsxRuntime.jsx(
914
- designSystem.TextInput,
915
- {
916
- type: "email",
917
- value: inviteEmail,
918
- onChange: (e) => setInviteEmail(e.target.value),
919
- placeholder: "user@example.com"
920
- }
921
- )
922
- ] }) }),
923
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
924
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Role" }),
925
- /* @__PURE__ */ jsxRuntime.jsxs(
926
- designSystem.SingleSelect,
927
- {
928
- value: inviteRole,
929
- onChange: (v) => setInviteRole(String(v)),
930
- "aria-label": "Invite role",
931
- children: [
932
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "member", children: "Member" }),
933
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "admin", children: "Admin" }),
934
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "owner", children: "Owner" })
935
- ]
936
- }
937
- )
938
- ] }) })
939
- ] }),
940
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { children: /* @__PURE__ */ jsxRuntime.jsx(
941
- designSystem.Button,
942
- {
943
- size: "S",
944
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
945
- disabled: !inviteEmail,
946
- loading: inviteMemberMutation.isLoading,
947
- onClick: () => inviteMemberMutation.mutate(),
948
- children: "Send invitation"
949
- }
950
- ) })
951
- ] }),
952
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.FormSection, { children: [
953
- /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.SectionLabel, { children: [
954
- "Invitations (",
955
- invitations.length,
956
- ")"
957
- ] }),
958
- invitationsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral500", children: "Loading…" }) : invitations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No invitations yet." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: invitations.map((inv) => {
959
- const statusColor = {
960
- pending: "#f59e0b",
961
- accepted: "#5cb176",
962
- rejected: "#d02b20",
963
- canceled: "#8e8ea9"
964
- };
965
- const color = statusColor[inv.status] ?? "#8e8ea9";
966
- const isPending = inv.status === "pending";
967
- return /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
968
- /* @__PURE__ */ jsxRuntime.jsxs(
969
- designSystem.Flex,
970
- {
971
- direction: "column",
972
- gap: 1,
973
- alignItems: "flex-start",
974
- style: { flex: 1 },
975
- children: [
976
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: inv.user?.name ?? inv.email }),
977
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
978
- inv.email,
979
- " · role: ",
980
- inv.role
981
- ] }),
982
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
983
- "Expires",
984
- " ",
985
- new Date(inv.expiresAt).toLocaleDateString()
986
- ] })
987
- ]
988
- }
989
- ),
990
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
991
- /* @__PURE__ */ jsxRuntime.jsx(
992
- useModelSchema.MonoChip,
993
- {
994
- style: {
995
- color,
996
- borderColor: color,
997
- background: `${color}18`
998
- },
999
- children: inv.status
1000
- }
1001
- ),
1002
- isPending && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1003
- /* @__PURE__ */ jsxRuntime.jsx(
1004
- designSystem.Button,
1005
- {
1006
- size: "S",
1007
- variant: "secondary",
1008
- loading: resendInvitationMutation.isLoading,
1009
- onClick: () => resendInvitationMutation.mutate(inv.id),
1010
- children: "Resend"
1011
- }
1012
- ),
1013
- /* @__PURE__ */ jsxRuntime.jsx(
1014
- designSystem.IconButton,
1015
- {
1016
- label: "Cancel invitation",
1017
- onClick: () => setConfirmCancelInvitationId(inv.id),
1018
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1019
- }
1020
- )
1021
- ] })
1022
- ] })
1023
- ] }, inv.id);
1024
- }) })
1025
- ] })
1026
- ] }) })
1027
- ] }),
1028
- confirmRemoveMemberId && /* @__PURE__ */ jsxRuntime.jsx(
1029
- useModelSchema.ConfirmDialog,
1030
- {
1031
- title: "Remove member",
1032
- message: "Are you sure you want to remove this member from the organization?",
1033
- confirmLabel: "Remove",
1034
- loading: removeMemberMutation.isLoading,
1035
- onConfirm: () => removeMemberMutation.mutate(confirmRemoveMemberId),
1036
- onCancel: () => setConfirmRemoveMemberId(null)
1037
- }
1038
- ),
1039
- confirmDeleteTeamId && /* @__PURE__ */ jsxRuntime.jsx(
1040
- useModelSchema.ConfirmDialog,
1041
- {
1042
- title: "Delete team",
1043
- message: "Are you sure you want to delete this team? All team members will be removed. This action cannot be undone.",
1044
- confirmLabel: "Delete",
1045
- loading: deleteTeamMutation.isLoading,
1046
- onConfirm: () => deleteTeamMutation.mutate(confirmDeleteTeamId),
1047
- onCancel: () => setConfirmDeleteTeamId(null)
1048
- }
1049
- ),
1050
- confirmDeleteSsoId && /* @__PURE__ */ jsxRuntime.jsx(
1051
- useModelSchema.ConfirmDialog,
1052
- {
1053
- title: "Delete SSO provider",
1054
- message: "Are you sure you want to delete this SSO provider? Members using this provider will need to authenticate differently.",
1055
- confirmLabel: "Delete",
1056
- loading: deleteSsoMutation.isLoading,
1057
- onConfirm: () => deleteSsoMutation.mutate(confirmDeleteSsoId),
1058
- onCancel: () => setConfirmDeleteSsoId(null)
1059
- }
1060
- ),
1061
- confirmCancelInvitationId && /* @__PURE__ */ jsxRuntime.jsx(
1062
- useModelSchema.ConfirmDialog,
1063
- {
1064
- title: "Cancel invitation",
1065
- message: "Are you sure you want to cancel this invitation? The invite link will no longer work.",
1066
- confirmLabel: "Cancel invitation",
1067
- loading: cancelInvitationMutation.isLoading,
1068
- onConfirm: () => cancelInvitationMutation.mutate(confirmCancelInvitationId),
1069
- onCancel: () => setConfirmCancelInvitationId(null)
1070
- }
1071
- )
1072
- ]
1073
- }
1074
- );
1075
- }
1076
- function TeamRow({
1077
- team,
1078
- organizationId,
1079
- onDelete
1080
- }) {
1081
- const qc = reactQuery.useQueryClient();
1082
- const [expanded, setExpanded] = react.useState(false);
1083
- const [confirmRemoveTeamMemberId, setConfirmRemoveTeamMemberId] = react.useState(null);
1084
- const teamMembersQuery = reactQuery.useQuery({
1085
- queryKey: ["dash-team-members", organizationId, team.id],
1086
- queryFn: async () => {
1087
- const result = await index.client.dash.organization[
1088
- // @ts-expect-error
1089
- organizationId
1090
- ].teams[team.id].members(
1091
- { params: { orgId: organizationId, teamId: team.id } },
1092
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
1093
- );
1094
- if (result.error) throw new Error(result.error.message ?? "Failed");
1095
- return result.data ?? [];
1096
- },
1097
- enabled: expanded
1098
- });
1099
- const [addUserId, setAddUserId] = react.useState("");
1100
- const addTeamMemberMutation = reactQuery.useMutation({
1101
- mutationFn: async () => {
1102
- const result = await index.client.dash.organization.addTeamMember(
1103
- { teamId: team.id, userId: addUserId },
1104
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
1105
- );
1106
- if (result.error) throw new Error(result.error.message ?? "Failed");
1107
- return result.data;
1108
- },
1109
- onSuccess: () => {
1110
- qc.invalidateQueries({
1111
- queryKey: ["dash-team-members", organizationId, team.id]
1112
- });
1113
- setAddUserId("");
1114
- }
1115
- });
1116
- const removeTeamMemberMutation = reactQuery.useMutation({
1117
- mutationFn: async (userId) => {
1118
- const result = await index.client.dash.organization.removeTeamMember(
1119
- { teamId: team.id, userId },
1120
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
1121
- );
1122
- if (result.error) throw new Error(result.error.message ?? "Failed");
1123
- },
1124
- onSuccess: () => {
1125
- setConfirmRemoveTeamMemberId(null);
1126
- qc.invalidateQueries({
1127
- queryKey: ["dash-team-members", organizationId, team.id]
1128
- });
1129
- }
1130
- });
1131
- return /* @__PURE__ */ jsxRuntime.jsxs(ExpandableRow, { "data-testid": "team-row", children: [
1132
- /* @__PURE__ */ jsxRuntime.jsxs(ExpandableRowHeader, { onClick: () => setExpanded((e) => !e), children: [
1133
- /* @__PURE__ */ jsxRuntime.jsxs(
1134
- designSystem.Flex,
1135
- {
1136
- direction: "column",
1137
- gap: 1,
1138
- alignItems: "flex-start",
1139
- style: { flex: 1 },
1140
- children: [
1141
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: team.name }),
1142
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
1143
- expanded ? "Collapse" : "Expand",
1144
- " ·",
1145
- " ",
1146
- teamMembersQuery.data?.length ?? "—",
1147
- " members"
1148
- ] })
1149
- ]
1150
- }
1151
- ),
1152
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
1153
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral400", children: expanded ? "▲" : "▼" }),
1154
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { label: "Delete team", onClick: onDelete, children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) }) })
1155
- ] })
1156
- ] }),
1157
- expanded && /* @__PURE__ */ jsxRuntime.jsxs(ExpandableRowBody, { children: [
1158
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "flex-end", children: [
1159
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1160
- UserCombobox,
1161
- {
1162
- label: "Add member to team",
1163
- value: addUserId,
1164
- onChange: setAddUserId
1165
- }
1166
- ) }),
1167
- /* @__PURE__ */ jsxRuntime.jsx(
1168
- designSystem.Button,
1169
- {
1170
- size: "S",
1171
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1172
- disabled: !addUserId,
1173
- loading: addTeamMemberMutation.isLoading,
1174
- onClick: () => addTeamMemberMutation.mutate(),
1175
- children: "Add"
1176
- }
1177
- )
1178
- ] }),
1179
- teamMembersQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "Loading…" }) : (teamMembersQuery.data ?? []).length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No members in this team." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: (teamMembersQuery.data ?? []).map((tm) => /* @__PURE__ */ jsxRuntime.jsxs(useModelSchema.AccountRow, { children: [
1180
- /* @__PURE__ */ jsxRuntime.jsxs(
1181
- designSystem.Flex,
1182
- {
1183
- direction: "column",
1184
- gap: 1,
1185
- alignItems: "flex-start",
1186
- style: { flex: 1 },
1187
- children: [
1188
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: tm.user?.name ?? tm.userId }),
1189
- tm.user?.email && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: tm.user.email })
1190
- ]
1191
- }
1192
- ),
1193
- /* @__PURE__ */ jsxRuntime.jsx(
1194
- designSystem.IconButton,
1195
- {
1196
- label: "Remove from team",
1197
- onClick: () => setConfirmRemoveTeamMemberId(tm.userId),
1198
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1199
- }
1200
- )
1201
- ] }, tm.id)) })
1202
- ] }),
1203
- confirmRemoveTeamMemberId && /* @__PURE__ */ jsxRuntime.jsx(
1204
- useModelSchema.ConfirmDialog,
1205
- {
1206
- title: "Remove from team",
1207
- message: "Are you sure you want to remove this member from the team?",
1208
- confirmLabel: "Remove",
1209
- loading: removeTeamMemberMutation.isLoading,
1210
- onConfirm: () => removeTeamMemberMutation.mutate(confirmRemoveTeamMemberId),
1211
- onCancel: () => setConfirmRemoveTeamMemberId(null)
1212
- }
1213
- )
1214
- ] });
1215
- }
1216
- const PAGE_SIZE = 25;
1217
- const fadeUp = styled.keyframes`
1218
- from { opacity: 0; transform: translateY(6px); }
1219
- to { opacity: 1; transform: translateY(0); }
1220
- `;
1221
- const Wrap = styled__default.default.div`
1222
- padding: 28px 32px;
1223
- background: #f6f6f9;
1224
- min-height: 100%;
1225
- display: flex;
1226
- flex-direction: column;
1227
- gap: 20px;
1228
- `;
1229
- const PageHeader = styled__default.default.div`
1230
- display: flex;
1231
- justify-content: space-between;
1232
- align-items: flex-start;
1233
- `;
1234
- const TitleBlock = styled__default.default.div`
1235
- display: flex;
1236
- flex-direction: column;
1237
- gap: 4px;
1238
- `;
1239
- const PageTitle = styled__default.default.h1`
1240
- margin: 0;
1241
- font-size: 22px;
1242
- font-weight: 800;
1243
- color: #32324d;
1244
- letter-spacing: -0.03em;
1245
- `;
1246
- const PageSubtitle = styled__default.default.p`
1247
- margin: 0;
1248
- font-size: 12px;
1249
- color: #8e8ea9;
1250
- `;
1251
- const TableCard = styled__default.default.div`
1252
- background: #ffffff;
1253
- border: 1px solid #eaeaef;
1254
- border-radius: 10px;
1255
- overflow: hidden;
1256
- `;
1257
- const Table = styled__default.default.table`
1258
- width: 100%;
1259
- border-collapse: collapse;
1260
- `;
1261
- const TH = styled__default.default.th`
1262
- text-align: left;
1263
- padding: 10px 14px;
1264
- font-size: 10px;
1265
- font-weight: 700;
1266
- color: #8e8ea9;
1267
- text-transform: uppercase;
1268
- letter-spacing: 0.08em;
1269
- border-bottom: 1px solid #eaeaef;
1270
- background: #fafafa;
1271
- white-space: nowrap;
1272
- &:first-child { padding-left: 20px; }
1273
- &:last-child { padding-right: 20px; }
1274
- `;
1275
- const THCheck = styled__default.default(TH)`
1276
- width: 44px;
1277
- `;
1278
- const THActions = styled__default.default(TH)`
1279
- width: 80px;
1280
- `;
1281
- const TD = styled__default.default.td`
1282
- padding: 11px 14px;
1283
- font-size: 12px;
1284
- color: #32324d;
1285
- border-bottom: 1px solid #f5f5f9;
1286
- vertical-align: middle;
1287
- &:first-child { padding-left: 20px; }
1288
- &:last-child { padding-right: 20px; }
1289
- `;
1290
- const TDCheck = styled__default.default(TD)`
1291
- width: 44px;
1292
- `;
1293
- const TDActions = styled__default.default(TD)`
1294
- width: 80px;
1295
- `;
1296
- const TR = styled__default.default.tr`
1297
- animation: ${fadeUp} 280ms ease both;
1298
- animation-delay: ${(p) => (p.$i ?? 0) * 25}ms;
1299
- background: ${(p) => p.$selected ? "#f0f0ff" : "transparent"};
1300
- transition: background 120ms ease;
1301
- &:hover td { background: ${(p) => p.$selected ? "#e8e8ff" : "#fafafe"}; }
1302
- &:last-child td { border-bottom: none; }
1303
- `;
1304
- const SlugChip = styled__default.default.span`
1305
- display: inline-block;
1306
- background: #f5f5f9;
1307
- color: #666687;
1308
- border: 1px solid #eaeaef;
1309
- border-radius: 6px;
1310
- padding: 2px 6px;
1311
- font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
1312
- font-size: 11px;
1313
- `;
1314
- const CountChip = styled__default.default.span`
1315
- display: inline-flex;
1316
- align-items: center;
1317
- padding: 2px 8px;
1318
- border-radius: 20px;
1319
- font-size: 10px;
1320
- font-weight: 700;
1321
- background: #f0f0ff;
1322
- color: #4945ff;
1323
- `;
1324
- const OrgName = styled__default.default.span`
1325
- font-size: 12px;
1326
- font-weight: 600;
1327
- color: #32324d;
1328
- `;
1329
- const DateText = styled__default.default.span`
1330
- font-size: 11px;
1331
- color: #b8b8c7;
1332
- font-variant-numeric: tabular-nums;
1333
- `;
1334
- const Toolbar = styled__default.default.div`
1335
- display: flex;
1336
- align-items: flex-end;
1337
- gap: 12px;
1338
- `;
1339
- const OrgLogoFallback = styled__default.default.div`
1340
- width: 30px;
1341
- height: 30px;
1342
- border-radius: 7px;
1343
- background: ${(p) => p.$bg};
1344
- color: ${(p) => p.$fg};
1345
- display: inline-flex;
1346
- align-items: center;
1347
- justify-content: center;
1348
- font-size: 11px;
1349
- font-weight: 700;
1350
- flex-shrink: 0;
1351
- user-select: none;
1352
- `;
1353
- const ORG_COLORS = [
1354
- { bg: "#EAF5FF", fg: "#0C75AF" },
1355
- { bg: "#F0F0FF", fg: "#4945FF" },
1356
- { bg: "#EAFBE7", fg: "#328048" },
1357
- { bg: "#FFF3D3", fg: "#8E6A00" },
1358
- { bg: "#FCECEA", fg: "#D02B20" },
1359
- { bg: "#F6ECFC", fg: "#8312D1" }
1360
- ];
1361
- function OrgAvatar({ name, logo }) {
1362
- if (logo) {
1363
- return /* @__PURE__ */ jsxRuntime.jsx(
1364
- designSystem.Box,
1365
- {
1366
- tag: "img",
1367
- src: logo,
1368
- alt: "",
1369
- style: {
1370
- width: 30,
1371
- height: 30,
1372
- borderRadius: 7,
1373
- objectFit: "cover",
1374
- flexShrink: 0
1375
- }
1376
- }
1377
- );
1378
- }
1379
- const color = ORG_COLORS[(name.charCodeAt(0) || 0) % ORG_COLORS.length];
1380
- const initials = name.slice(0, 2).toUpperCase();
1381
- return /* @__PURE__ */ jsxRuntime.jsx(OrgLogoFallback, { $bg: color.bg, $fg: color.fg, children: initials });
1382
- }
1383
- function OrganizationsPage({ teamsEnabled }) {
1384
- const rbac = admin.useRBAC(index.PERMISSIONS.organization);
1385
- const qc = reactQuery.useQueryClient();
1386
- const { toggleNotification } = admin.useNotification();
1387
- const [page, setPage] = react.useState(1);
1388
- const [search, setSearch] = react.useState("");
1389
- const [searchInput, setSearchInput] = react.useState("");
1390
- const [selected, setSelected] = react.useState(/* @__PURE__ */ new Set());
1391
- const [showCreate, setShowCreate] = react.useState(false);
1392
- const [detailOrgId, setDetailOrgId] = react.useState(null);
1393
- const [confirmDelete, setConfirmDelete] = react.useState(null);
1394
- const [confirmDeleteMany, setConfirmDeleteMany] = react.useState(false);
1395
- const offset = (page - 1) * PAGE_SIZE;
1396
- const orgsQuery = reactQuery.useQuery({
1397
- queryKey: ["dash-organizations", page, search],
1398
- queryFn: async () => {
1399
- const result = await index.client.dash.listOrganizations({
1400
- query: {
1401
- limit: PAGE_SIZE,
1402
- offset,
1403
- sortBy: "createdAt",
1404
- sortOrder: "desc",
1405
- ...search ? { search } : {}
1406
- }
1407
- });
1408
- if (result.error)
1409
- throw new Error(result.error.message ?? "Failed to load organizations");
1410
- return result.data;
1411
- },
1412
- keepPreviousData: true
1413
- });
1414
- const deleteMutation = reactQuery.useMutation({
1415
- mutationFn: async (organizationId) => {
1416
- const result = await index.client.dash.organization.delete(
1417
- { organizationId },
1418
- useModelSchema.withContext({ organizationId }, index.getAuthHeaders())
1419
- );
1420
- if (result.error)
1421
- throw new Error(result.error.message ?? "Delete failed");
1422
- },
1423
- onSuccess: () => {
1424
- setConfirmDelete(null);
1425
- qc.invalidateQueries({ queryKey: ["dash-organizations"] });
1426
- toggleNotification({ type: "success", message: "Organization deleted" });
1427
- },
1428
- onError: (err) => {
1429
- toggleNotification({
1430
- type: "danger",
1431
- message: err.message ?? "Failed to delete organization"
1432
- });
1433
- }
1434
- });
1435
- const deleteManyMutation = reactQuery.useMutation({
1436
- mutationFn: async (organizationIds) => {
1437
- const result = await index.client.dash.organization.deleteMany(
1438
- {},
1439
- useModelSchema.withContext({ organizationIds }, index.getAuthHeaders())
1440
- );
1441
- if (result.error)
1442
- throw new Error(result.error.message ?? "Delete failed");
1443
- return result.data;
1444
- },
1445
- onSuccess: (_data, organizationIds) => {
1446
- setConfirmDeleteMany(false);
1447
- setSelected(/* @__PURE__ */ new Set());
1448
- qc.invalidateQueries({ queryKey: ["dash-organizations"] });
1449
- toggleNotification({
1450
- type: "success",
1451
- message: `${organizationIds.length} organization${organizationIds.length !== 1 ? "s" : ""} deleted`
1452
- });
1453
- },
1454
- onError: (err) => {
1455
- toggleNotification({
1456
- type: "danger",
1457
- message: err.message ?? "Failed to delete organizations"
1458
- });
1459
- }
1460
- });
1461
- const orgs = orgsQuery.data && "organizations" in orgsQuery.data ? orgsQuery.data.organizations : [];
1462
- const total = orgsQuery.data && "total" in orgsQuery.data ? orgsQuery.data.total : 0;
1463
- const pageCount = Math.ceil(total / PAGE_SIZE);
1464
- const allSelected = orgs.length > 0 && orgs.every((o) => selected.has(o.id));
1465
- const someSelected = selected.size > 0;
1466
- const toggleSelect = (id) => {
1467
- setSelected((prev) => {
1468
- const next = new Set(prev);
1469
- if (next.has(id)) next.delete(id);
1470
- else next.add(id);
1471
- return next;
1472
- });
1473
- };
1474
- const handleSelectAll = () => {
1475
- if (allSelected) setSelected(/* @__PURE__ */ new Set());
1476
- else setSelected(new Set(orgs.map((o) => o.id)));
1477
- };
1478
- const handleSearch = (e) => {
1479
- e.preventDefault();
1480
- setSearch(searchInput);
1481
- setPage(1);
1482
- };
1483
- return /* @__PURE__ */ jsxRuntime.jsxs(Wrap, { "data-testid": "organizations-page", children: [
1484
- /* @__PURE__ */ jsxRuntime.jsxs(PageHeader, { children: [
1485
- /* @__PURE__ */ jsxRuntime.jsxs(TitleBlock, { children: [
1486
- /* @__PURE__ */ jsxRuntime.jsx(PageTitle, { children: "Organizations" }),
1487
- /* @__PURE__ */ jsxRuntime.jsxs(PageSubtitle, { children: [
1488
- total.toLocaleString(),
1489
- " total"
1490
- ] })
1491
- ] }),
1492
- rbac.allowedActions.canCreate && /* @__PURE__ */ jsxRuntime.jsx(
1493
- designSystem.Button,
1494
- {
1495
- startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
1496
- onClick: () => setShowCreate(true),
1497
- "data-testid": "create-org-btn",
1498
- children: "Create organization"
1499
- }
1500
- )
1501
- ] }),
1502
- /* @__PURE__ */ jsxRuntime.jsxs(Toolbar, { children: [
1503
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { onSubmit: handleSearch, children: /* @__PURE__ */ jsxRuntime.jsx(
1504
- designSystem.Searchbar,
1505
- {
1506
- clearLabel: "Clear",
1507
- name: "search",
1508
- placeholder: "Search organizations…",
1509
- value: searchInput,
1510
- onChange: (e) => setSearchInput(e.target.value),
1511
- onClear: () => {
1512
- setSearchInput("");
1513
- setSearch("");
1514
- setPage(1);
1515
- },
1516
- children: "Search organizations"
1517
- }
1518
- ) }),
1519
- someSelected && rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsxs(
1520
- designSystem.Button,
1521
- {
1522
- variant: "danger-light",
1523
- size: "S",
1524
- onClick: () => setConfirmDeleteMany(true),
1525
- "data-testid": "delete-selected-orgs-btn",
1526
- children: [
1527
- "Delete ",
1528
- selected.size,
1529
- " selected"
1530
- ]
1531
- }
1532
- )
1533
- ] }),
1534
- orgsQuery.isError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "#d02b20", fontSize: 12, padding: "8px 0" }, children: orgsQuery.error instanceof Error ? orgsQuery.error.message : "An error occurred" }),
1535
- /* @__PURE__ */ jsxRuntime.jsx(TableCard, { children: orgsQuery.isLoading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Loading organizations…" }) }) : /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
1536
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1537
- rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(THCheck, { children: /* @__PURE__ */ jsxRuntime.jsx(
1538
- designSystem.Checkbox,
1539
- {
1540
- checked: allSelected,
1541
- onCheckedChange: handleSelectAll,
1542
- "aria-label": "Select all"
1543
- }
1544
- ) }),
1545
- /* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Name" }),
1546
- /* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Slug" }),
1547
- /* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Members" }),
1548
- /* @__PURE__ */ jsxRuntime.jsx(TH, { children: "Created" }),
1549
- /* @__PURE__ */ jsxRuntime.jsx(THActions, {})
1550
- ] }) }),
1551
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: orgs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
1552
- TD,
1553
- {
1554
- colSpan: 6,
1555
- style: {
1556
- textAlign: "center",
1557
- padding: "40px",
1558
- color: "#8e8ea9"
1559
- },
1560
- "data-testid": "orgs-empty",
1561
- children: search ? `No organizations matching "${search}"` : "No organizations found"
1562
- }
1563
- ) }) : orgs.map((org, i) => /* @__PURE__ */ jsxRuntime.jsxs(
1564
- TR,
1565
- {
1566
- $selected: selected.has(org.id),
1567
- $i: i,
1568
- "data-testid": "org-row",
1569
- children: [
1570
- rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(TDCheck, { children: /* @__PURE__ */ jsxRuntime.jsx(
1571
- designSystem.Checkbox,
1572
- {
1573
- checked: selected.has(org.id),
1574
- onCheckedChange: () => toggleSelect(org.id),
1575
- "aria-label": `Select ${org.name}`
1576
- }
1577
- ) }),
1578
- /* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
1579
- /* @__PURE__ */ jsxRuntime.jsx(OrgAvatar, { name: org.name, logo: org.logo }),
1580
- /* @__PURE__ */ jsxRuntime.jsx(OrgName, { children: org.name })
1581
- ] }) }),
1582
- /* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(SlugChip, { children: org.slug }) }),
1583
- /* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(CountChip, { children: org.memberCount }) }),
1584
- /* @__PURE__ */ jsxRuntime.jsx(TD, { children: /* @__PURE__ */ jsxRuntime.jsx(DateText, { children: new Date(org.createdAt).toLocaleDateString() }) }),
1585
- /* @__PURE__ */ jsxRuntime.jsx(TDActions, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "flex-end", children: [
1586
- rbac.allowedActions.canUpdate && /* @__PURE__ */ jsxRuntime.jsx(
1587
- designSystem.IconButton,
1588
- {
1589
- label: "Edit organization",
1590
- onClick: () => setDetailOrgId(org.id),
1591
- "data-testid": "edit-org-btn",
1592
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {})
1593
- }
1594
- ),
1595
- rbac.allowedActions.canDelete && /* @__PURE__ */ jsxRuntime.jsx(
1596
- designSystem.IconButton,
1597
- {
1598
- label: "Delete organization",
1599
- onClick: () => setConfirmDelete(org.id),
1600
- "data-testid": "delete-org-btn",
1601
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1602
- }
1603
- )
1604
- ] }) })
1605
- ]
1606
- },
1607
- org.id
1608
- )) })
1609
- ] }) }),
1610
- pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1611
- /* @__PURE__ */ jsxRuntime.jsx(
1612
- designSystem.Button,
1613
- {
1614
- variant: "tertiary",
1615
- size: "S",
1616
- disabled: page === 1,
1617
- onClick: () => setPage((p) => p - 1),
1618
- children: "Previous"
1619
- }
1620
- ),
1621
- /* @__PURE__ */ jsxRuntime.jsx(
1622
- designSystem.Button,
1623
- {
1624
- variant: "tertiary",
1625
- size: "S",
1626
- disabled: page >= pageCount,
1627
- onClick: () => setPage((p) => p + 1),
1628
- children: "Next"
1629
- }
1630
- )
1631
- ] }) }),
1632
- rbac.allowedActions.canCreate && showCreate && /* @__PURE__ */ jsxRuntime.jsx(
1633
- CreateOrganizationDialog,
1634
- {
1635
- teamsEnabled,
1636
- onClose: () => setShowCreate(false)
1637
- }
1638
- ),
1639
- rbac.allowedActions.canUpdate && detailOrgId && /* @__PURE__ */ jsxRuntime.jsx(
1640
- OrganizationDetail,
1641
- {
1642
- organizationId: detailOrgId,
1643
- teamsEnabled,
1644
- onClose: () => setDetailOrgId(null)
1645
- }
1646
- ),
1647
- rbac.allowedActions.canDelete && confirmDelete && /* @__PURE__ */ jsxRuntime.jsx(
1648
- useModelSchema.ConfirmDialog,
1649
- {
1650
- title: "Delete organization",
1651
- message: "Are you sure you want to delete this organization? All members and teams will be removed. This action cannot be undone.",
1652
- confirmLabel: "Delete",
1653
- loading: deleteMutation.isLoading,
1654
- onConfirm: () => deleteMutation.mutate(confirmDelete),
1655
- onCancel: () => setConfirmDelete(null)
1656
- }
1657
- ),
1658
- rbac.allowedActions.canDelete && confirmDeleteMany && /* @__PURE__ */ jsxRuntime.jsx(
1659
- useModelSchema.ConfirmDialog,
1660
- {
1661
- title: `Delete ${selected.size} organization${selected.size !== 1 ? "s" : ""}`,
1662
- message: `Are you sure you want to delete ${selected.size} organization${selected.size !== 1 ? "s" : ""}? This action cannot be undone.`,
1663
- confirmLabel: "Delete all",
1664
- loading: deleteManyMutation.isLoading,
1665
- onConfirm: () => deleteManyMutation.mutate([...selected]),
1666
- onCancel: () => setConfirmDeleteMany(false)
1667
- }
1668
- )
1669
- ] });
1670
- }
1671
- function ProtectedOrganizationsPage() {
1672
- const { data: organizationOptions } = index.useOrganizationOptions();
1673
- const teamsEnabled = organizationOptions?.teamsEnabled ?? false;
1674
- return /* @__PURE__ */ jsxRuntime.jsx(
1675
- admin.Page.Protect,
1676
- {
1677
- permissions: index.PERMISSIONS.organization.filter(
1678
- (p) => p.action === `plugin::${index.PLUGIN_ID}.organization.read`
1679
- ),
1680
- children: /* @__PURE__ */ jsxRuntime.jsx(OrganizationsPage, { teamsEnabled })
1681
- }
1682
- );
1683
- }
1684
- exports.OrganizationsPage = OrganizationsPage;
1685
- exports.default = ProtectedOrganizationsPage;