@elevasis/ui 2.74.0 → 2.76.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 (105) hide show
  1. package/dist/api/index.js +3 -3
  2. package/dist/app/index.d.ts +277 -249
  3. package/dist/app/index.js +12 -11
  4. package/dist/auth/index.js +6 -7
  5. package/dist/charts/index.js +6 -7
  6. package/dist/{chunk-GO4XJ5D4.js → chunk-2GIHWU5O.js} +4 -3
  7. package/dist/chunk-2Y6AKWGU.js +42 -0
  8. package/dist/chunk-3QYRZSHP.js +854 -0
  9. package/dist/{chunk-CRA7WQZH.js → chunk-3RK2NNJX.js} +1 -1
  10. package/dist/{chunk-R6NMRXNO.js → chunk-4WLAVUPS.js} +1 -1
  11. package/dist/{chunk-GPQQGQGT.js → chunk-5R4I2PAN.js} +9 -43
  12. package/dist/{chunk-R5UGIFYU.js → chunk-5ZK7K7YP.js} +25 -1
  13. package/dist/chunk-6B4ZPBQJ.js +7 -0
  14. package/dist/{chunk-ARAFQBNI.js → chunk-73G3I5HJ.js} +160 -160
  15. package/dist/{chunk-ZWI62BQ6.js → chunk-BFLYDVEN.js} +32 -42
  16. package/dist/{chunk-W2QRBQQA.js → chunk-BG6PWDJ3.js} +381 -381
  17. package/dist/{chunk-7ARBBELS.js → chunk-BV2RNLPP.js} +1 -1
  18. package/dist/{chunk-FA7LMNQO.js → chunk-CTT4RDXE.js} +150 -151
  19. package/dist/chunk-E2LJ3FRA.js +237 -0
  20. package/dist/{chunk-E6YGA5WH.js → chunk-EFQLXRYT.js} +1 -2
  21. package/dist/{chunk-FZSG6M6W.js → chunk-EVNLJHDD.js} +3 -3
  22. package/dist/{chunk-ZYNVCWDZ.js → chunk-GDQQX5SL.js} +4 -42
  23. package/dist/{chunk-DCQKTDIG.js → chunk-GFSOQH3N.js} +1 -1
  24. package/dist/{chunk-3LOKYZJC.js → chunk-H5AEYSVX.js} +164 -3
  25. package/dist/{chunk-ZFISGKYW.js → chunk-IIEZXXDD.js} +1 -1
  26. package/dist/{chunk-3QHX4ZKV.js → chunk-INCEZAM3.js} +1 -1
  27. package/dist/{chunk-O32UQ26A.js → chunk-JZQNUQE4.js} +1 -1
  28. package/dist/{chunk-6JN2TLV2.js → chunk-K55TU6CC.js} +2023 -1700
  29. package/dist/chunk-KB5CAN5D.js +33 -0
  30. package/dist/{chunk-2MRRDF32.js → chunk-KMA4FOSV.js} +5682 -5675
  31. package/dist/{chunk-HYQOGRWG.js → chunk-KXNRTJAE.js} +1 -1
  32. package/dist/{chunk-7RJ7P6S6.js → chunk-MGAELVTZ.js} +12 -12
  33. package/dist/chunk-MKH2KOAO.js +39 -0
  34. package/dist/{chunk-4MXNHJTA.js → chunk-NAPXMYFW.js} +939 -1570
  35. package/dist/{chunk-CQPULKKL.js → chunk-OGG3QJYW.js} +14 -5
  36. package/dist/{chunk-4M2BSHVT.js → chunk-P6HHXMLJ.js} +1 -1
  37. package/dist/{chunk-WZU3MCKI.js → chunk-QAILYRC2.js} +1 -1
  38. package/dist/{chunk-RBGVNPA6.js → chunk-RXZ3XDBM.js} +1 -1
  39. package/dist/{chunk-BA3AZJUR.js → chunk-SPW6UA6B.js} +1 -1
  40. package/dist/{chunk-RFNJ2PTO.js → chunk-SVZR4GYA.js} +1 -1
  41. package/dist/{chunk-5A6KCFS5.js → chunk-UEY4AB3R.js} +789 -66
  42. package/dist/{chunk-LN4UW6S4.js → chunk-UL6MPMKY.js} +1 -1
  43. package/dist/{chunk-ZFA56VSR.js → chunk-WELN7DDV.js} +2 -2
  44. package/dist/{chunk-43LC6XAX.js → chunk-WJN2VVVJ.js} +1 -1
  45. package/dist/{chunk-BRTGCFBH.js → chunk-XODQUPFW.js} +11 -11
  46. package/dist/components/index.d.ts +181 -34
  47. package/dist/components/index.js +2249 -1804
  48. package/dist/components/navigation/index.js +7 -8
  49. package/dist/features/auth/index.d.ts +35 -3716
  50. package/dist/features/auth/index.js +9 -10
  51. package/dist/features/clients/index.js +18 -12
  52. package/dist/features/content/index.js +34 -31
  53. package/dist/features/crm/index.css +59 -59
  54. package/dist/features/crm/index.d.ts +18 -1
  55. package/dist/features/crm/index.js +13 -11
  56. package/dist/features/dashboard/index.css +59 -59
  57. package/dist/features/dashboard/index.js +8 -9
  58. package/dist/features/delivery/index.d.ts +3 -0
  59. package/dist/features/delivery/index.js +21 -20
  60. package/dist/features/knowledge/index.js +6 -7
  61. package/dist/features/lead-gen/index.css +59 -59
  62. package/dist/features/lead-gen/index.d.ts +3903 -2
  63. package/dist/features/lead-gen/index.js +15 -13
  64. package/dist/features/monitoring/index.js +10 -11
  65. package/dist/features/monitoring/requests/index.css +59 -59
  66. package/dist/features/monitoring/requests/index.js +8 -9
  67. package/dist/features/notes/index.js +4 -4
  68. package/dist/features/operations/index.css +59 -59
  69. package/dist/features/operations/index.js +13 -15
  70. package/dist/features/seo/index.css +659 -0
  71. package/dist/features/seo/index.d.ts +3962 -1
  72. package/dist/features/seo/index.js +31 -2
  73. package/dist/features/settings/index.css +59 -59
  74. package/dist/features/settings/index.d.ts +87 -3745
  75. package/dist/features/settings/index.js +10 -12
  76. package/dist/hooks/access/index.js +6 -7
  77. package/dist/hooks/delivery/index.css +59 -59
  78. package/dist/hooks/delivery/index.d.ts +13 -4
  79. package/dist/hooks/delivery/index.js +3 -3
  80. package/dist/hooks/published.d.ts +69 -27
  81. package/dist/hooks/published.js +19 -19
  82. package/dist/initialization/index.d.ts +35 -3716
  83. package/dist/knowledge/index.js +11 -11
  84. package/dist/layout/index.d.ts +55 -2
  85. package/dist/layout/index.js +10 -10
  86. package/dist/organization/index.css +59 -59
  87. package/dist/profile/index.d.ts +64 -3716
  88. package/dist/provider/index.d.ts +3 -0
  89. package/dist/provider/index.js +6 -7
  90. package/dist/provider/published.d.ts +3 -0
  91. package/dist/provider/published.js +6 -7
  92. package/dist/test-utils/index.js +3 -3
  93. package/dist/theme/index.js +3 -3
  94. package/dist/theme/presets/index.js +1 -1
  95. package/dist/types/index.d.ts +312 -251
  96. package/dist/utils/index.d.ts +1 -1
  97. package/dist/utils/index.js +1 -1
  98. package/package.json +9 -9
  99. package/src/hooks/README.md +22 -23
  100. package/dist/chunk-6ZAP3FOA.js +0 -93
  101. package/dist/chunk-AH52XQB7.js +0 -1
  102. package/dist/chunk-GDOR57D7.js +0 -165
  103. package/dist/chunk-MISHPEJX.js +0 -244
  104. package/dist/hooks/operations/command-view/utils/transformCommandViewData.d.ts +0 -879
  105. package/dist/hooks/operations/command-view/utils/transformCommandViewData.js +0 -2
@@ -1,58 +1,25 @@
1
- import { useAvailablePresets } from './chunk-ZFISGKYW.js';
2
- import { MEMBERSHIP_STATUS_COLORS, PROVISIONING_STATE_DISPLAY, transformMembershipToTableRow, useUserMemberships, useUpdateWebhookEndpoint, useDeleteWebhookEndpoint, useCreateWebhookEndpoint, useListWebhookEndpoints, useOrgRoles, useAssignRole, useRevokeRole, useOrganizationMembers, usePermissionCatalog } from './chunk-ARAFQBNI.js';
3
- import { useDeleteCredential, useCreateCredential, useCredentials, useUpdateCredential, CredentialSchemas } from './chunk-3LOKYZJC.js';
4
- import { AppShellLoader } from './chunk-E6YGA5WH.js';
1
+ import { useAvailablePresets } from './chunk-IIEZXXDD.js';
2
+ import { MEMBERSHIP_STATUS_COLORS, PROVISIONING_STATE_DISPLAY, transformMembershipToTableRow, useUserMemberships, useUpdateWebhookEndpoint, useDeleteWebhookEndpoint, useCreateWebhookEndpoint, useListWebhookEndpoints, useOrgRoles, useAssignRole, useRevokeRole, useOrganizationMembers, usePermissionCatalog } from './chunk-73G3I5HJ.js';
3
+ import { AppShellLoader } from './chunk-EFQLXRYT.js';
5
4
  import { FilterBar } from './chunk-PDHTXPSF.js';
6
- import { ListSkeleton, EmptyState, CustomModal, PageTitleCaption, APIErrorAlert, useResources, StatCard, useAccess, AccessKeys, AccessGuard } from './chunk-6JN2TLV2.js';
7
- import { showApiErrorNotification, showErrorNotification, showSuccessNotification } from './chunk-HYQOGRWG.js';
5
+ import { CustomModal, APIErrorAlert, EmptyState, PageTitleCaption, useResources, ListSkeleton, StatCard, useAccess, AccessKeys, AccessGuard } from './chunk-K55TU6CC.js';
6
+ import { showApiErrorNotification, showErrorNotification, showSuccessNotification } from './chunk-KXNRTJAE.js';
8
7
  import { CardHeader } from './chunk-62GVNH5U.js';
9
8
  import { useInitialization } from './chunk-P45GQ3ZW.js';
10
9
  import { RoleBadge } from './chunk-UD657ICS.js';
11
10
  import { useOrganization } from './chunk-DD3CCMCZ.js';
12
- import { isAPIClientError, formatDateTime, OAUTH_FLOW_TIMEOUT } from './chunk-R5UGIFYU.js';
11
+ import { isAPIClientError, formatDateTime, OAUTH_FLOW_TIMEOUT } from './chunk-5ZK7K7YP.js';
13
12
  import { useElevasisServices } from './chunk-BI5VDO2K.js';
14
- import { brandSupabaseOrgId } from './chunk-MQZE7SUI.js';
15
13
  import { useAuthContext } from './chunk-BRJ3QZ4E.js';
16
- import { createUseExternalEvents, Text, Table, Group, Tooltip, ActionIcon, Stack, Title, Button, Select, TextInput, Alert, PasswordInput, Anchor, Paper, Badge, Center, Loader, Box, Code, CopyButton, ThemeIcon, SimpleGrid, UnstyledButton, Card, Switch, Divider, Textarea, MultiSelect } from '@mantine/core';
17
- import { IconShieldOff, IconKey, IconCalendar, IconPencil, IconTrash, IconAlertTriangle, IconInfoCircle, IconExclamationMark, IconPlus, IconAlertCircle, IconUsers, IconSearch, IconBuilding, IconWebhook, IconCheck, IconCopy, IconUser, IconMail, IconPalette, IconEye, IconSparkles, IconTrendingUp, IconClock, IconPlayerPause, IconPlayerPlay, IconActivity, IconShieldLock, IconStar, IconUserCog, IconUsersGroup, IconSettings, IconRefresh, IconBrandDropbox, IconBrandGoogleDrive, IconPlug, IconEdit, IconUserX, IconUserCheck } from '@tabler/icons-react';
18
- import { randomId } from '@mantine/hooks';
14
+ import { Text, Stack, Title, Alert, TextInput, Group, Button, Paper, SimpleGrid, Card, ThemeIcon, Badge, Tooltip, Select, Table, Center, Loader, Box, Code, CopyButton, ActionIcon, UnstyledButton, Switch, Divider, Textarea, MultiSelect } from '@mantine/core';
15
+ import { IconShieldOff, IconAlertCircle, IconBrandDropbox, IconBrandGoogleDrive, IconPlug, IconUsers, IconSearch, IconBuilding, IconWebhook, IconCheck, IconCopy, IconUser, IconMail, IconPalette, IconEye, IconSparkles, IconTrendingUp, IconExclamationMark, IconPencil, IconClock, IconPlayerPause, IconPlayerPlay, IconTrash, IconAlertTriangle, IconPlus, IconActivity, IconShieldLock, IconStar, IconUserCog, IconUsersGroup, IconSettings, IconEdit, IconUserX, IconUserCheck } from '@tabler/icons-react';
16
+ import { modals } from '@mantine/modals';
19
17
  import { notifications } from '@mantine/notifications';
20
- import { z } from 'zod';
21
18
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
22
19
  import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
23
- import { useForm } from '@mantine/form';
24
- import { create } from 'zustand';
25
20
  import { useQueryClient } from '@tanstack/react-query';
26
-
27
- var [useModalsEvents, createEvent] = createUseExternalEvents("mantine-modals");
28
- var openModal = (payload) => {
29
- const id = payload.modalId || randomId();
30
- createEvent("openModal")({ ...payload, modalId: id });
31
- return id;
32
- };
33
- var openConfirmModal = (payload) => {
34
- const id = payload.modalId || randomId();
35
- createEvent("openConfirmModal")({ ...payload, modalId: id });
36
- return id;
37
- };
38
- var openContextModal = (payload) => {
39
- const id = payload.modalId || randomId();
40
- createEvent("openContextModal")({ ...payload, modalId: id });
41
- return id;
42
- };
43
- var closeModal = createEvent("closeModal");
44
- var closeAllModals = createEvent("closeAllModals");
45
- var updateModal = (payload) => createEvent("updateModal")(payload);
46
- var updateContextModal = (payload) => createEvent("updateContextModal")(payload);
47
- var modals = {
48
- open: openModal,
49
- close: closeModal,
50
- closeAll: closeAllModals,
51
- openConfirmModal,
52
- openContextModal,
53
- updateModal,
54
- updateContextModal
55
- };
21
+ import { create } from 'zustand';
22
+ import { useForm } from '@mantine/form';
56
23
 
57
24
  // ../core/src/integrations/oauth/provider-registry.ts
58
25
  var OAUTH_PROVIDERS = {
@@ -115,178 +82,6 @@ var OAUTH_PROVIDERS = {
115
82
  ]
116
83
  }
117
84
  };
118
- var CredentialFieldSchema = z.object({
119
- key: z.string(),
120
- label: z.string(),
121
- type: z.enum(["password", "text"]),
122
- required: z.boolean(),
123
- placeholder: z.string().optional(),
124
- description: z.string().optional()
125
- });
126
- var CredentialSchemaZod = z.object({
127
- type: z.string(),
128
- label: z.string(),
129
- description: z.string(),
130
- fields: z.array(CredentialFieldSchema).optional(),
131
- docsUrl: z.string().url().optional(),
132
- nameSuggestions: z.array(z.string()),
133
- oauthProvider: z.string().optional()
134
- });
135
- var CREDENTIAL_SCHEMAS = {
136
- "google-sheets": {
137
- type: "oauth",
138
- label: "Google Sheets",
139
- description: "Google Sheets OAuth integration",
140
- oauthProvider: "google-sheets",
141
- nameSuggestions: ["google-sheets-prod", "google-sheets-dev", "sheets-workspace"]
142
- },
143
- dropbox: {
144
- type: "oauth",
145
- label: "Dropbox",
146
- description: "Dropbox OAuth integration",
147
- oauthProvider: "dropbox",
148
- nameSuggestions: ["dropbox-prod", "dropbox-dev", "elevasis-dropbox"]
149
- },
150
- oauth: {
151
- type: "oauth",
152
- label: "OAuth",
153
- description: "Generic OAuth credential",
154
- nameSuggestions: []
155
- },
156
- "api-key": {
157
- type: "single_field",
158
- label: "API Key",
159
- description: "Single-field API key credential",
160
- fields: [
161
- {
162
- key: "apiKey",
163
- label: "API Key",
164
- type: "password",
165
- required: true,
166
- placeholder: "Enter your API key",
167
- description: "API key for the service"
168
- }
169
- ],
170
- nameSuggestions: ["service-prod", "service-dev", "api-key"]
171
- },
172
- "webhook-secret": {
173
- type: "single_field",
174
- label: "Webhook Secret",
175
- description: "Webhook signing secret for signature validation",
176
- fields: [
177
- {
178
- key: "signingSecret",
179
- label: "Signing Secret",
180
- type: "password",
181
- required: true,
182
- placeholder: "whsec_...",
183
- description: "Webhook signing secret from provider dashboard"
184
- }
185
- ],
186
- nameSuggestions: ["my-org-cal-com-webhook", "my-org-stripe-webhook", "my-org-signature-api-webhook"]
187
- },
188
- "api-key-secret": {
189
- type: "api-key-secret",
190
- label: "API Key + Secret",
191
- description: "API key and secret pair authentication",
192
- fields: [
193
- {
194
- key: "apiKey",
195
- label: "API Key",
196
- type: "password",
197
- required: true
198
- },
199
- {
200
- key: "apiSecret",
201
- label: "API Secret",
202
- type: "password",
203
- required: true
204
- }
205
- ],
206
- nameSuggestions: []
207
- },
208
- apify: {
209
- type: "single_field",
210
- label: "Apify",
211
- description: "Apify API token for running web-scraping actors (e.g. website crawl, Google Places scrape).",
212
- fields: [
213
- {
214
- key: "apiKey",
215
- label: "API Token",
216
- type: "password",
217
- required: true,
218
- placeholder: "apify_api_...",
219
- description: "Personal API token from https://console.apify.com/account/integrations"
220
- }
221
- ],
222
- docsUrl: "https://docs.apify.com/platform/integrations/api",
223
- nameSuggestions: ["elevasis-apify", "apify-prod", "apify-dev"]
224
- },
225
- clickup: {
226
- type: "single_field",
227
- label: "ClickUp",
228
- description: "ClickUp personal API token for creating tasks in tenant-managed ClickUp workspaces.",
229
- fields: [
230
- {
231
- key: "apiToken",
232
- label: "API Token",
233
- type: "password",
234
- required: true,
235
- placeholder: "pk_...",
236
- description: "Personal API token from ClickUp settings. Personal tokens start with pk_."
237
- }
238
- ],
239
- docsUrl: "https://developer.clickup.com/docs/authentication",
240
- nameSuggestions: ["clickup-demo", "clickup-prod", "clickup-dev"]
241
- },
242
- instagram: {
243
- type: "single_field",
244
- label: "Instagram",
245
- description: "Long-lived Instagram access token for publishing through Meta\u2019s Content Publishing API.",
246
- fields: [
247
- {
248
- key: "accessToken",
249
- label: "Access Token",
250
- type: "password",
251
- required: true,
252
- placeholder: "IGAA...",
253
- description: "Long-lived token from the Meta app dashboard. Valid 60 days from issue or last refresh."
254
- },
255
- {
256
- key: "igUserId",
257
- label: "Instagram User ID",
258
- type: "text",
259
- required: true,
260
- placeholder: "17841400000000000",
261
- description: "The Instagram professional account id shown beside the token on the same Meta screen."
262
- }
263
- ],
264
- docsUrl: "https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/content-publishing",
265
- nameSuggestions: ["elevasis-instagram", "instagram-prod", "instagram-dev"]
266
- }
267
- };
268
- function getCredentialSchema(type) {
269
- const schema = CREDENTIAL_SCHEMAS[type] || CREDENTIAL_SCHEMAS["api-key"];
270
- return CredentialSchemaZod.parse(schema);
271
- }
272
-
273
- // ../core/src/integrations/credentials/utils.ts
274
- function buildCredentialValue(type, formValues) {
275
- const schema = getCredentialSchema(type);
276
- if (schema.type === "oauth") {
277
- throw new Error(`buildCredentialValue should not be used for OAuth type: ${type}`);
278
- }
279
- const value = {};
280
- if (schema.fields) {
281
- for (const field of schema.fields) {
282
- const fieldValue = formValues[field.key];
283
- if (fieldValue !== void 0 && fieldValue !== null) {
284
- value[field.key] = fieldValue.trim();
285
- }
286
- }
287
- }
288
- return value;
289
- }
290
85
  function showAuthError(error) {
291
86
  if (isAPIClientError(error)) {
292
87
  if (error.code === "LAST_OWNER_PROTECTED") {
@@ -315,282 +110,6 @@ function showAuthError(error) {
315
110
  }
316
111
  showApiErrorNotification(error);
317
112
  }
318
- function CredentialList({ credentials, isLoading, onEdit }) {
319
- const [deleteCredential, setDeleteCredential] = useState(null);
320
- const deleteMutation = useDeleteCredential();
321
- const handleDelete = () => {
322
- if (deleteCredential) {
323
- deleteMutation.mutate(deleteCredential.id);
324
- setDeleteCredential(null);
325
- }
326
- };
327
- const handleCloseDelete = () => {
328
- if (!deleteMutation.isPending) {
329
- setDeleteCredential(null);
330
- }
331
- };
332
- if (isLoading) {
333
- return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 60 });
334
- }
335
- if (credentials.length === 0) {
336
- return /* @__PURE__ */ jsx(
337
- EmptyState,
338
- {
339
- icon: IconKey,
340
- title: "No credentials yet",
341
- description: "Create your first credential to enable external service integrations"
342
- }
343
- );
344
- }
345
- return /* @__PURE__ */ jsxs(Fragment, { children: [
346
- /* @__PURE__ */ jsxs(Table, { children: [
347
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
348
- /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
349
- /* @__PURE__ */ jsx(Table.Th, { children: "Created" }),
350
- /* @__PURE__ */ jsx(Table.Th, { w: 100 })
351
- ] }) }),
352
- /* @__PURE__ */ jsx(Table.Tbody, { children: credentials.map((credential) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
353
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
354
- /* @__PURE__ */ jsx(IconKey, { size: 16, style: { opacity: 0.6 } }),
355
- /* @__PURE__ */ jsx(Text, { fw: 500, children: credential.name })
356
- ] }) }),
357
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
358
- /* @__PURE__ */ jsx(IconCalendar, { size: 14, style: { opacity: 0.5 } }),
359
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(credential.createdAt) })
360
- ] }) }),
361
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
362
- onEdit && /* @__PURE__ */ jsx(Tooltip, { label: "Edit credential", children: /* @__PURE__ */ jsx(
363
- ActionIcon,
364
- {
365
- variant: "subtle",
366
- color: "gray",
367
- onClick: () => onEdit(credential),
368
- disabled: deleteMutation.isPending,
369
- children: /* @__PURE__ */ jsx(IconPencil, { size: 16 })
370
- }
371
- ) }),
372
- /* @__PURE__ */ jsx(Tooltip, { label: "Delete credential", position: "left", children: /* @__PURE__ */ jsx(
373
- ActionIcon,
374
- {
375
- variant: "subtle",
376
- color: "red",
377
- onClick: () => setDeleteCredential(credential),
378
- disabled: deleteMutation.isPending,
379
- children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
380
- }
381
- ) })
382
- ] }) })
383
- ] }, credential.id)) })
384
- ] }),
385
- /* @__PURE__ */ jsx(CustomModal, { opened: !!deleteCredential, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
386
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
387
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
388
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Credential" })
389
- ] }),
390
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
391
- "Are you sure you want to delete",
392
- " ",
393
- /* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
394
- '"',
395
- deleteCredential?.name,
396
- '"'
397
- ] }),
398
- "?"
399
- ] }),
400
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone. Any agents or tools using this credential will stop working immediately." }),
401
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
402
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
403
- /* @__PURE__ */ jsx(
404
- Button,
405
- {
406
- color: "red",
407
- onClick: handleDelete,
408
- loading: deleteMutation.isPending,
409
- leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
410
- children: "Delete Credential"
411
- }
412
- )
413
- ] })
414
- ] }) })
415
- ] });
416
- }
417
- function CreateCredentialModal({ opened, onClose, onSuccess }) {
418
- const createCredential = useCreateCredential();
419
- const [selectedType, setSelectedType] = useState("api-key");
420
- const [schema, setSchema] = useState(getCredentialSchema("api-key"));
421
- useEffect(() => {
422
- setSchema(getCredentialSchema(selectedType));
423
- }, [selectedType]);
424
- const getInitialValues = (currentSchema) => {
425
- const values = { name: "" };
426
- if (currentSchema.fields) {
427
- currentSchema.fields.forEach((field) => {
428
- values[field.key] = "";
429
- });
430
- }
431
- return values;
432
- };
433
- const getValidation = (currentSchema) => {
434
- const validation = {
435
- name: (value) => {
436
- const result = CredentialSchemas.CreateRequest.shape.name.safeParse(value);
437
- return result.success ? null : result.error.issues[0]?.message || "Invalid credential name";
438
- }
439
- };
440
- if (currentSchema.fields) {
441
- currentSchema.fields.forEach((field) => {
442
- if (field.required) {
443
- validation[field.key] = (value) => !value.trim() ? `${field.label} is required` : null;
444
- }
445
- });
446
- }
447
- return validation;
448
- };
449
- const form = useForm({
450
- initialValues: getInitialValues(schema),
451
- validate: getValidation(schema)
452
- });
453
- useEffect(() => {
454
- const newValues = getInitialValues(schema);
455
- form.setValues(newValues);
456
- form.setFieldValue("name", form.values.name || "");
457
- form.resetDirty();
458
- }, [selectedType]);
459
- const handleSubmit = async (values) => {
460
- if (schema.type === "oauth") {
461
- console.error("OAuth credentials cannot be created via this modal");
462
- return;
463
- }
464
- const data = {
465
- name: values.name.trim(),
466
- type: selectedType,
467
- value: buildCredentialValue(selectedType, values)
468
- };
469
- const validationResult = CredentialSchemas.CreateRequest.safeParse(data);
470
- if (!validationResult.success) {
471
- console.error("Validation failed:", validationResult.error);
472
- return;
473
- }
474
- try {
475
- const result = await createCredential.mutateAsync(data);
476
- form.reset();
477
- setSelectedType("api-key");
478
- onSuccess(result);
479
- } catch (error) {
480
- console.error("Failed to create credential:", error);
481
- }
482
- };
483
- const handleClose = () => {
484
- if (!createCredential.isPending) {
485
- form.reset();
486
- setSelectedType("api-key");
487
- onClose();
488
- }
489
- };
490
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "lg", loading: createCredential.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
491
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Create New Credential" }),
492
- /* @__PURE__ */ jsx(
493
- Select,
494
- {
495
- label: "Credential Type",
496
- description: "Select the integration type",
497
- placeholder: "Select credential type",
498
- data: Object.entries(CREDENTIAL_SCHEMAS).filter(([_, s]) => s.type !== "oauth").map(([key, s]) => ({ value: key, label: s.label })),
499
- value: selectedType,
500
- onChange: (value) => setSelectedType(value || "api-key"),
501
- required: true,
502
- disabled: createCredential.isPending
503
- }
504
- ),
505
- /* @__PURE__ */ jsx(
506
- TextInput,
507
- {
508
- label: "Credential Name",
509
- description: schema.nameSuggestions.length > 0 ? `Suggested: ${schema.nameSuggestions.join(", ")}` : "Lowercase letters, numbers, and hyphens only (e.g., service-dev, service-prod)",
510
- placeholder: schema.nameSuggestions[0] || "e.g., service-prod",
511
- leftSection: /* @__PURE__ */ jsx(IconKey, { size: 16 }),
512
- maxLength: 100,
513
- required: true,
514
- ...form.getInputProps("name"),
515
- disabled: createCredential.isPending
516
- }
517
- ),
518
- schema.type === "oauth" ? /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 16 }), color: "blue", children: [
519
- schema.label,
520
- " uses OAuth authentication. Please go to the OAuth Integrations page to connect your",
521
- " ",
522
- schema.label,
523
- " account."
524
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
525
- schema.fields?.map((field) => {
526
- const Input = field.type === "text" ? TextInput : PasswordInput;
527
- return /* @__PURE__ */ jsx(
528
- Input,
529
- {
530
- label: field.label,
531
- description: field.description,
532
- placeholder: field.placeholder,
533
- required: field.required,
534
- value: form.values[field.key] || "",
535
- onChange: (e) => form.setFieldValue(field.key, e.target.value),
536
- error: form.errors[field.key],
537
- disabled: createCredential.isPending
538
- },
539
- field.key
540
- );
541
- }),
542
- schema.docsUrl && /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
543
- "Get credentials from",
544
- " ",
545
- /* @__PURE__ */ jsx(Anchor, { href: schema.docsUrl, target: "_blank", rel: "noopener noreferrer", children: schema.docsUrl })
546
- ] })
547
- ] }),
548
- createCredential.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: createCredential.error instanceof Error ? createCredential.error.message : "An error occurred while creating the credential" }),
549
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
550
- /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createCredential.isPending, children: "Cancel" }),
551
- /* @__PURE__ */ jsx(Button, { type: "submit", loading: createCredential.isPending, leftSection: /* @__PURE__ */ jsx(IconKey, { size: 16 }), children: "Create Credential" })
552
- ] })
553
- ] }) }) });
554
- }
555
- function CredentialSettings({ oauthSection }) {
556
- const { organizationReady } = useInitialization();
557
- const [showCreateModal, setShowCreateModal] = useState(false);
558
- const { data: credentials = [], isLoading } = useCredentials();
559
- if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
560
- const handleCredentialCreated = () => {
561
- setShowCreateModal(false);
562
- };
563
- return /* @__PURE__ */ jsxs(Stack, { children: [
564
- /* @__PURE__ */ jsx(
565
- PageTitleCaption,
566
- {
567
- title: "Organization Credentials",
568
- caption: "Manage encrypted credentials for third-party service integrations"
569
- }
570
- ),
571
- oauthSection,
572
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
573
- /* @__PURE__ */ jsx(
574
- CardHeader,
575
- {
576
- icon: /* @__PURE__ */ jsx(IconKey, { size: 18 }),
577
- title: "Credentials",
578
- subtitle: "Configured API keys and credentials",
579
- rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Credential" })
580
- }
581
- ),
582
- /* @__PURE__ */ jsx(CredentialList, { credentials, isLoading })
583
- ] }),
584
- /* @__PURE__ */ jsx(
585
- CreateCredentialModal,
586
- {
587
- opened: showCreateModal,
588
- onClose: () => setShowCreateModal(false),
589
- onSuccess: handleCredentialCreated
590
- }
591
- )
592
- ] });
593
- }
594
113
  function OAuthConnectModal({ opened, onClose, provider, onConnect, error, isLoading }) {
595
114
  const [credentialName, setCredentialName] = useState("");
596
115
  useEffect(() => {
@@ -648,80 +167,291 @@ function OAuthConnectModal({ opened, onClose, provider, onConnect, error, isLoad
648
167
  ] })
649
168
  ] }) });
650
169
  }
651
- function MembershipStatusBadge({ status, size = "sm", variant = "light" }) {
652
- const getStatusLabel = (status2) => {
653
- switch (status2) {
654
- case "active":
655
- return "Active";
656
- case "inactive":
657
- return "Inactive";
658
- default:
659
- return status2;
660
- }
661
- };
662
- return /* @__PURE__ */ jsx(Badge, { color: MEMBERSHIP_STATUS_COLORS[status], size, variant, children: getStatusLabel(status) });
663
- }
664
- function ProvisioningStateBadge({ state, size = "sm", variant = "light" }) {
665
- if (!state || state === "linked") {
666
- return null;
667
- }
668
- const display = PROVISIONING_STATE_DISPLAY[state];
669
- return /* @__PURE__ */ jsx(Tooltip, { label: display.description, multiline: true, w: 280, children: /* @__PURE__ */ jsx(Badge, { color: display.color, size, variant, children: display.label }) });
670
- }
671
- function OrganizationMembershipsList({
672
- memberships,
673
- loading,
674
- error,
675
- onEditRole,
676
- onLeaveOrganization
677
- }) {
678
- const [searchTerm, setSearchTerm] = useState("");
679
- const [statusFilter, setStatusFilter] = useState("");
680
- const tableRows = memberships?.map(transformMembershipToTableRow) || [];
681
- const filteredRows = tableRows.filter((row) => {
682
- const matchesSearch = row.organizationName.toLowerCase().includes(searchTerm.toLowerCase()) || row.role.toLowerCase().includes(searchTerm.toLowerCase());
683
- const matchesStatus = !statusFilter || row.status === statusFilter;
684
- return matchesSearch && matchesStatus;
685
- });
686
- const renderActions = (row) => {
687
- if (!onEditRole && !onLeaveOrganization) {
688
- return /* @__PURE__ */ jsx(ActionIcon, { variant: "light", size: "sm", color: "gray", title: "Configure member access", children: /* @__PURE__ */ jsx(IconSettings, { size: 14 }) });
689
- }
690
- return /* @__PURE__ */ jsxs(Fragment, { children: [
691
- row.canEdit && onEditRole && /* @__PURE__ */ jsx(
692
- ActionIcon,
693
- {
694
- variant: "subtle",
695
- size: "sm",
696
- color: "blue",
697
- onClick: () => onEditRole(row.id),
698
- disabled: row.status !== "active",
699
- children: /* @__PURE__ */ jsx(IconEdit, { size: 16 })
700
- }
701
- ),
702
- row.canRemove && onLeaveOrganization && row.status === "active" && /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", color: "red", onClick: () => onLeaveOrganization(row.id), children: /* @__PURE__ */ jsx(IconUserX, { size: 16 }) }),
703
- row.status === "inactive" && /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", color: "green", disabled: true, title: "Contact admin to reactivate", children: /* @__PURE__ */ jsx(IconUserCheck, { size: 16 }) })
704
- ] });
705
- };
706
- if (error) {
707
- return /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Failed to load memberships" }) });
708
- }
709
- return /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
710
- /* @__PURE__ */ jsx(
711
- CardHeader,
712
- {
713
- icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
714
- title: `Your Organizations (${loading ? "..." : filteredRows.length})`,
715
- subtitle: loading ? "Loading memberships..." : `Showing ${filteredRows.length} ${filteredRows.length === 1 ? "membership" : "memberships"}`
716
- }
717
- ),
718
- /* @__PURE__ */ jsxs(Stack, { children: [
719
- /* @__PURE__ */ jsxs(FilterBar, { children: [
720
- /* @__PURE__ */ jsx(
721
- TextInput,
722
- {
723
- placeholder: "Search organizations or roles...",
724
- leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
170
+ var OAUTH_COMPLETION_CHANNEL = "elevasis-oauth";
171
+ var OAUTH_COMPLETION_STORAGE_KEY = "elevasis:oauth-complete";
172
+ function useOAuthFlow({ apiUrl: apiUrlOverride } = {}) {
173
+ const [isAuthorizing, setIsAuthorizing] = useState(false);
174
+ const [error, setError] = useState(null);
175
+ const { isReady, apiUrl: contextApiUrl } = useElevasisServices();
176
+ const { getAccessToken, organizationId } = useAuthContext();
177
+ const apiUrl = apiUrlOverride ?? contextApiUrl;
178
+ const authorize = useCallback(
179
+ async (provider, credentialName) => {
180
+ if (!isReady || !organizationId) {
181
+ setError("Organization context not ready");
182
+ return;
183
+ }
184
+ setIsAuthorizing(true);
185
+ setError(null);
186
+ const width = 600;
187
+ const height = 700;
188
+ const left = Math.round(window.screen.width / 2 - width / 2);
189
+ const top = Math.round(window.screen.height / 2 - height / 2);
190
+ const popup = window.open(
191
+ "about:blank",
192
+ `oauth-${provider}`,
193
+ `width=${width},height=${height},left=${left},top=${top}`
194
+ );
195
+ if (!popup) {
196
+ setIsAuthorizing(false);
197
+ setError("Please enable popups and try again");
198
+ return;
199
+ }
200
+ let token;
201
+ try {
202
+ token = await getAccessToken();
203
+ } catch {
204
+ popup.close();
205
+ setIsAuthorizing(false);
206
+ setError("Authorization failed. Please sign in again.");
207
+ return;
208
+ }
209
+ try {
210
+ const authorizeUrl = new URL(`${apiUrl}/api/oauth/authorize/${provider}`);
211
+ authorizeUrl.searchParams.set("credentialName", credentialName);
212
+ authorizeUrl.searchParams.set("token", token);
213
+ authorizeUrl.searchParams.set("orgId", organizationId);
214
+ authorizeUrl.searchParams.set("appOrigin", window.location.origin);
215
+ popup.location.href = authorizeUrl.toString();
216
+ await waitForOAuthCallback(popup, Date.now());
217
+ } catch (err) {
218
+ const rawError = err instanceof Error ? err.message : "unknown";
219
+ if (rawError.includes("popup_blocked")) {
220
+ setError("Please enable popups and try again");
221
+ } else if (rawError.includes("timeout")) {
222
+ setError("Authorization timed out. Please try again.");
223
+ } else if (rawError.includes("popup_closed")) {
224
+ setError("Authorization was cancelled.");
225
+ } else if (rawError.includes("token_exchange_failed")) {
226
+ setError("Failed to obtain access token. Please try again.");
227
+ } else if (rawError.includes("invalid_state")) {
228
+ setError("Security validation failed. Please try again.");
229
+ } else if (rawError.includes("state_expired")) {
230
+ setError("Authorization expired. Please try again.");
231
+ } else {
232
+ setError(`Failed to connect: ${rawError}`);
233
+ }
234
+ throw err;
235
+ } finally {
236
+ setIsAuthorizing(false);
237
+ }
238
+ },
239
+ [isReady, organizationId, apiUrl, getAccessToken]
240
+ );
241
+ const waitForOAuthCallback = (popup, startedAt) => {
242
+ return new Promise((resolve, reject) => {
243
+ let broadcastChannel = null;
244
+ const complete = (data) => {
245
+ const payload = parseOAuthCompletionPayload(data, startedAt);
246
+ if (!payload) return;
247
+ cleanup();
248
+ if (payload.status === "success") {
249
+ resolve();
250
+ } else {
251
+ reject(new Error(payload.error || "oauth_failed"));
252
+ }
253
+ };
254
+ const messageHandler = (event) => {
255
+ if (!isTrustedCallbackOrigin(event.origin)) return;
256
+ complete(event.data);
257
+ };
258
+ const storageHandler = (event) => {
259
+ if (event.key !== OAUTH_COMPLETION_STORAGE_KEY || !event.newValue) return;
260
+ try {
261
+ complete(JSON.parse(event.newValue));
262
+ } catch {
263
+ }
264
+ };
265
+ const rejectIfPopupClosed = () => {
266
+ if (popup.closed) {
267
+ cleanup();
268
+ reject(new Error("popup_closed"));
269
+ }
270
+ };
271
+ window.addEventListener("message", messageHandler);
272
+ window.addEventListener("storage", storageHandler);
273
+ window.addEventListener("focus", rejectIfPopupClosed);
274
+ if ("BroadcastChannel" in window) {
275
+ broadcastChannel = new BroadcastChannel(OAUTH_COMPLETION_CHANNEL);
276
+ broadcastChannel.onmessage = (event) => complete(event.data);
277
+ }
278
+ const timeout = setTimeout(() => {
279
+ cleanup();
280
+ reject(new Error("timeout"));
281
+ }, OAUTH_FLOW_TIMEOUT);
282
+ const cleanup = () => {
283
+ clearTimeout(timeout);
284
+ broadcastChannel?.close();
285
+ window.removeEventListener("focus", rejectIfPopupClosed);
286
+ window.removeEventListener("storage", storageHandler);
287
+ window.removeEventListener("message", messageHandler);
288
+ };
289
+ });
290
+ };
291
+ return { authorize, isAuthorizing, error };
292
+ }
293
+ function parseOAuthCompletionPayload(data, startedAt) {
294
+ if (!data || typeof data !== "object") return null;
295
+ const payload = data;
296
+ if (payload.type !== "oauth-complete") return null;
297
+ if (payload.completedAt && payload.completedAt < startedAt) return null;
298
+ return payload;
299
+ }
300
+ function isTrustedCallbackOrigin(origin) {
301
+ if (origin === window.location.origin || origin === "https://app.elevasis.io") {
302
+ return true;
303
+ }
304
+ try {
305
+ const url = new URL(origin);
306
+ return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
307
+ } catch {
308
+ return false;
309
+ }
310
+ }
311
+ var PROVIDER_ICONS = {
312
+ "google-sheets": IconBrandGoogleDrive,
313
+ dropbox: IconBrandDropbox
314
+ };
315
+ function OAuthIntegrationsCard({ apiUrl } = {}) {
316
+ const [selectedProviderId, setSelectedProviderId] = useState(null);
317
+ const [modalOpened, setModalOpened] = useState(false);
318
+ const { authorize, isAuthorizing, error } = useOAuthFlow({ apiUrl });
319
+ const queryClient = useQueryClient();
320
+ const handleConnect = async (credentialName) => {
321
+ if (!selectedProviderId) return;
322
+ try {
323
+ await authorize(selectedProviderId, credentialName);
324
+ setModalOpened(false);
325
+ setSelectedProviderId(null);
326
+ queryClient.invalidateQueries({ queryKey: ["credentials"] });
327
+ } catch (err) {
328
+ console.error("OAuth flow failed:", err);
329
+ }
330
+ };
331
+ const handleOpenModal = (providerId) => {
332
+ setSelectedProviderId(providerId);
333
+ setModalOpened(true);
334
+ };
335
+ useEffect(() => {
336
+ const params = new URLSearchParams(window.location.search);
337
+ const requested = params.get("connect");
338
+ if (requested && OAUTH_PROVIDERS[requested]) {
339
+ handleOpenModal(requested);
340
+ params.delete("connect");
341
+ const remaining = params.toString();
342
+ const newUrl = window.location.pathname + (remaining ? `?${remaining}` : "") + window.location.hash;
343
+ window.history.replaceState({}, "", newUrl);
344
+ }
345
+ }, []);
346
+ const providers = Object.values(OAUTH_PROVIDERS);
347
+ const selectedProvider = selectedProviderId ? OAUTH_PROVIDERS[selectedProviderId] : null;
348
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
349
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
350
+ /* @__PURE__ */ jsx(Group, { justify: "space-between", children: /* @__PURE__ */ jsxs("div", { children: [
351
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "OAuth Integrations" }),
352
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mt: 4, children: "Connect third-party services using OAuth 2.0" })
353
+ ] }) }),
354
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, md: 3 }, children: providers.map((provider) => {
355
+ const Icon = PROVIDER_ICONS[provider.id] || IconPlug;
356
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
357
+ /* @__PURE__ */ jsx(Group, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
358
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", children: /* @__PURE__ */ jsx(Icon, { size: 20 }) }),
359
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Text, { fw: 600, style: { fontFamily: "var(--mantine-font-family-headings)" }, children: provider.name }) })
360
+ ] }) }),
361
+ /* @__PURE__ */ jsx(Button, { variant: "filled", size: "sm", onClick: () => handleOpenModal(provider.id), fullWidth: true, children: "Connect" })
362
+ ] }) }, provider.id);
363
+ }) })
364
+ ] }) }),
365
+ /* @__PURE__ */ jsx(
366
+ OAuthConnectModal,
367
+ {
368
+ opened: modalOpened,
369
+ onClose: () => {
370
+ setModalOpened(false);
371
+ setSelectedProviderId(null);
372
+ },
373
+ provider: selectedProvider,
374
+ onConnect: handleConnect,
375
+ error,
376
+ isLoading: isAuthorizing
377
+ }
378
+ )
379
+ ] });
380
+ }
381
+ function MembershipStatusBadge({ status, size = "sm", variant = "light" }) {
382
+ const getStatusLabel = (status2) => {
383
+ switch (status2) {
384
+ case "active":
385
+ return "Active";
386
+ case "inactive":
387
+ return "Inactive";
388
+ default:
389
+ return status2;
390
+ }
391
+ };
392
+ return /* @__PURE__ */ jsx(Badge, { color: MEMBERSHIP_STATUS_COLORS[status], size, variant, children: getStatusLabel(status) });
393
+ }
394
+ function ProvisioningStateBadge({ state, size = "sm", variant = "light" }) {
395
+ if (!state || state === "linked") {
396
+ return null;
397
+ }
398
+ const display = PROVISIONING_STATE_DISPLAY[state];
399
+ return /* @__PURE__ */ jsx(Tooltip, { label: display.description, multiline: true, w: 280, children: /* @__PURE__ */ jsx(Badge, { color: display.color, size, variant, children: display.label }) });
400
+ }
401
+ function OrganizationMembershipsList({
402
+ memberships,
403
+ loading,
404
+ error,
405
+ onEditRole,
406
+ onLeaveOrganization
407
+ }) {
408
+ const [searchTerm, setSearchTerm] = useState("");
409
+ const [statusFilter, setStatusFilter] = useState("");
410
+ const tableRows = memberships?.map(transformMembershipToTableRow) || [];
411
+ const filteredRows = tableRows.filter((row) => {
412
+ const matchesSearch = row.organizationName.toLowerCase().includes(searchTerm.toLowerCase()) || row.role.toLowerCase().includes(searchTerm.toLowerCase());
413
+ const matchesStatus = !statusFilter || row.status === statusFilter;
414
+ return matchesSearch && matchesStatus;
415
+ });
416
+ const renderActions = (row) => {
417
+ if (!onEditRole && !onLeaveOrganization) {
418
+ return /* @__PURE__ */ jsx(ActionIcon, { variant: "light", size: "sm", color: "gray", title: "Configure member access", children: /* @__PURE__ */ jsx(IconSettings, { size: 14 }) });
419
+ }
420
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
421
+ row.canEdit && onEditRole && /* @__PURE__ */ jsx(
422
+ ActionIcon,
423
+ {
424
+ variant: "subtle",
425
+ size: "sm",
426
+ color: "blue",
427
+ onClick: () => onEditRole(row.id),
428
+ disabled: row.status !== "active",
429
+ children: /* @__PURE__ */ jsx(IconEdit, { size: 16 })
430
+ }
431
+ ),
432
+ row.canRemove && onLeaveOrganization && row.status === "active" && /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", color: "red", onClick: () => onLeaveOrganization(row.id), children: /* @__PURE__ */ jsx(IconUserX, { size: 16 }) }),
433
+ row.status === "inactive" && /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", size: "sm", color: "green", disabled: true, title: "Contact admin to reactivate", children: /* @__PURE__ */ jsx(IconUserCheck, { size: 16 }) })
434
+ ] });
435
+ };
436
+ if (error) {
437
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Failed to load memberships" }) });
438
+ }
439
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
440
+ /* @__PURE__ */ jsx(
441
+ CardHeader,
442
+ {
443
+ icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
444
+ title: `Your Organizations (${loading ? "..." : filteredRows.length})`,
445
+ subtitle: loading ? "Loading memberships..." : `Showing ${filteredRows.length} ${filteredRows.length === 1 ? "membership" : "memberships"}`
446
+ }
447
+ ),
448
+ /* @__PURE__ */ jsxs(Stack, { children: [
449
+ /* @__PURE__ */ jsxs(FilterBar, { children: [
450
+ /* @__PURE__ */ jsx(
451
+ TextInput,
452
+ {
453
+ placeholder: "Search organizations or roles...",
454
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
725
455
  value: searchTerm,
726
456
  onChange: (event) => setSearchTerm(event.currentTarget.value),
727
457
  style: { flex: 1 },
@@ -1109,264 +839,34 @@ function AppearanceSettings({ initialTheme, onThemeChange }) {
1109
839
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: row.last }) })
1110
840
  ] }, row.name)) })
1111
841
  ] }) })
1112
- ] }) }),
1113
- /* @__PURE__ */ jsx(Divider, {}),
1114
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1115
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Alerts" }),
1116
- /* @__PURE__ */ jsx(Alert, { variant: "light", color: "blue", icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Informational", children: "This is how informational alerts look with your current theme." }),
1117
- /* @__PURE__ */ jsx(Alert, { variant: "light", color: "green", icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), title: "Success", children: "Operation completed successfully." })
1118
- ] })
1119
- ] })
1120
- ] }),
1121
- /* @__PURE__ */ jsx(CustomModal, { opened: modalOpen, onClose: () => setModalOpen(false), size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
1122
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Modal Preview" }),
1123
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1124
- "Modals should sit visibly above the page background. Elevation comes from the",
1125
- " ",
1126
- /* @__PURE__ */ jsx(Text, { span: true, ff: "monospace", size: "sm", children: "--color-elevated" }),
1127
- " ",
1128
- "token, which every preset defines as an opaque surface."
1129
- ] }),
1130
- /* @__PURE__ */ jsx(TextInput, { label: "Example field", placeholder: "Type something...", size: "xs" }),
1131
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1132
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", onClick: () => setModalOpen(false), children: "Cancel" }),
1133
- /* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => setModalOpen(false), children: "Confirm" })
1134
- ] })
1135
- ] }) })
1136
- ] });
1137
- }
1138
- function EditWebhookEndpointModal({ opened, endpoint, onClose }) {
1139
- const updateEndpoint = useUpdateWebhookEndpoint();
1140
- const { data: resourcesData } = useResources();
1141
- const resourceOptions = useMemo(() => {
1142
- if (!resourcesData) return [];
1143
- const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
1144
- value: w.resourceId,
1145
- label: `Workflow: ${w.resourceId}`
1146
- }));
1147
- const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
1148
- value: a.resourceId,
1149
- label: `Agent: ${a.resourceId}`
1150
- }));
1151
- return [...workflowOptions, ...agentOptions];
1152
- }, [resourcesData]);
1153
- const form = useForm({
1154
- initialValues: {
1155
- name: "",
1156
- resourceId: ""
1157
- },
1158
- validate: {
1159
- name: (value) => {
1160
- if (!value.trim()) return "Name is required";
1161
- if (value.trim().length < 2) return "Name must be at least 2 characters";
1162
- if (value.trim().length > 100) return "Name must be less than 100 characters";
1163
- return null;
1164
- }
1165
- }
1166
- });
1167
- useEffect(() => {
1168
- if (endpoint && opened) {
1169
- form.setValues({
1170
- name: endpoint.name,
1171
- resourceId: endpoint.resourceId ?? ""
1172
- });
1173
- }
1174
- }, [endpoint, opened]);
1175
- const handleSubmit = async (values) => {
1176
- if (!endpoint) return;
1177
- try {
1178
- await updateEndpoint.mutateAsync({
1179
- endpointId: endpoint.id,
1180
- data: {
1181
- name: values.name.trim(),
1182
- resourceId: values.resourceId || void 0
1183
- }
1184
- });
1185
- onClose();
1186
- } catch (error) {
1187
- console.error("Failed to update webhook endpoint:", error);
1188
- }
1189
- };
1190
- const handleClose = () => {
1191
- if (!updateEndpoint.isPending) {
1192
- onClose();
1193
- }
1194
- };
1195
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: updateEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
1196
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Edit Webhook Endpoint" }),
1197
- /* @__PURE__ */ jsx(
1198
- TextInput,
1199
- {
1200
- label: "Name",
1201
- description: "A descriptive label for this endpoint",
1202
- leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
1203
- required: true,
1204
- ...form.getInputProps("name"),
1205
- disabled: updateEndpoint.isPending
1206
- }
1207
- ),
1208
- /* @__PURE__ */ jsx(
1209
- Select,
1210
- {
1211
- label: "Target Resource",
1212
- description: "The workflow or agent to trigger on each inbound request",
1213
- placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
1214
- data: resourceOptions,
1215
- searchable: true,
1216
- clearable: true,
1217
- ...form.getInputProps("resourceId"),
1218
- disabled: !resourcesData || updateEndpoint.isPending
1219
- }
1220
- ),
1221
- updateEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: updateEndpoint.error instanceof Error ? updateEndpoint.error.message : "An error occurred while updating the webhook endpoint" }),
1222
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1223
- /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: updateEndpoint.isPending, children: "Cancel" }),
1224
- /* @__PURE__ */ jsx(Button, { type: "submit", loading: updateEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconPencil, { size: 16 }), children: "Save Changes" })
1225
- ] })
1226
- ] }) }) });
1227
- }
1228
- function formatKeyDisplay(keyPrefix) {
1229
- if (!keyPrefix) return "Unavailable";
1230
- return `${keyPrefix}...`;
1231
- }
1232
- function WebhookEndpointList({ endpoints, isLoading }) {
1233
- const [deleteTarget, setDeleteTarget] = useState(null);
1234
- const [editTarget, setEditTarget] = useState(null);
1235
- const deleteMutation = useDeleteWebhookEndpoint();
1236
- const updateMutation = useUpdateWebhookEndpoint();
1237
- const handleDelete = () => {
1238
- if (deleteTarget) {
1239
- deleteMutation.mutate(deleteTarget.id);
1240
- setDeleteTarget(null);
1241
- }
1242
- };
1243
- const handleCloseDelete = () => {
1244
- if (!deleteMutation.isPending) {
1245
- setDeleteTarget(null);
1246
- }
1247
- };
1248
- const handleToggleStatus = (endpoint) => {
1249
- const newStatus = endpoint.status === "active" ? "paused" : "active";
1250
- updateMutation.mutate({ endpointId: endpoint.id, data: { status: newStatus } });
1251
- };
1252
- if (isLoading) {
1253
- return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 50 });
1254
- }
1255
- if (endpoints.length === 0) {
1256
- return /* @__PURE__ */ jsx(
1257
- EmptyState,
1258
- {
1259
- icon: IconWebhook,
1260
- title: "No webhook endpoints yet",
1261
- description: "Create your first webhook endpoint to let third-party apps trigger your resources"
1262
- }
1263
- );
1264
- }
1265
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1266
- /* @__PURE__ */ jsxs(Table, { children: [
1267
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1268
- /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
1269
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1270
- /* @__PURE__ */ jsx(Table.Th, { children: "Target Resource" }),
1271
- /* @__PURE__ */ jsx(Table.Th, { children: "Key" }),
1272
- /* @__PURE__ */ jsx(Table.Th, { children: "Last Triggered" }),
1273
- /* @__PURE__ */ jsx(Table.Th, { children: "Requests" }),
1274
- /* @__PURE__ */ jsx(Table.Th, { w: 120 })
1275
- ] }) }),
1276
- /* @__PURE__ */ jsx(Table.Tbody, { children: endpoints.map((endpoint) => {
1277
- const keyDisplay = formatKeyDisplay(endpoint.keyPrefix);
1278
- const isToggling = updateMutation.isPending;
1279
- return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1280
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1281
- /* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6 } }),
1282
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1283
- /* @__PURE__ */ jsx(Text, { fw: 500, children: endpoint.name }),
1284
- endpoint.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: endpoint.description })
1285
- ] })
1286
- ] }) }),
1287
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { color: endpoint.status === "active" ? "teal" : "gray", variant: "light", children: endpoint.status }) }),
1288
- /* @__PURE__ */ jsx(Table.Td, { children: endpoint.resourceId ? /* @__PURE__ */ jsx(Code, { children: endpoint.resourceId }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", fs: "italic", children: "Not configured" }) }),
1289
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1290
- Tooltip,
1291
- {
1292
- label: "Full URL is shown only at creation. Use the prefix to identify which endpoint this is.",
1293
- position: "top",
1294
- children: /* @__PURE__ */ jsx(Paper, { withBorder: true, style: { display: "inline-flex", alignItems: "center", padding: "2px 8px" }, children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { userSelect: "all" }, children: keyDisplay }) })
1295
- }
1296
- ) }),
1297
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
1298
- /* @__PURE__ */ jsx(IconClock, { size: 14, style: { opacity: 0.5 } }),
1299
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(endpoint.lastTriggeredAt) })
1300
- ] }) }),
1301
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: endpoint.requestCount.toLocaleString() }) }),
1302
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
1303
- /* @__PURE__ */ jsx(Tooltip, { label: "Edit endpoint", position: "left", children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "blue", onClick: () => setEditTarget(endpoint), children: /* @__PURE__ */ jsx(IconPencil, { size: 16 }) }) }),
1304
- /* @__PURE__ */ jsx(
1305
- Tooltip,
1306
- {
1307
- label: endpoint.status === "active" ? "Pause endpoint" : "Resume endpoint",
1308
- position: "left",
1309
- children: /* @__PURE__ */ jsx(
1310
- ActionIcon,
1311
- {
1312
- variant: "subtle",
1313
- color: endpoint.status === "active" ? "yellow" : "teal",
1314
- onClick: () => handleToggleStatus(endpoint),
1315
- disabled: isToggling,
1316
- children: endpoint.status === "active" ? /* @__PURE__ */ jsx(IconPlayerPause, { size: 16 }) : /* @__PURE__ */ jsx(IconPlayerPlay, { size: 16 })
1317
- }
1318
- )
1319
- }
1320
- ),
1321
- /* @__PURE__ */ jsx(Tooltip, { label: "Delete endpoint", position: "left", children: /* @__PURE__ */ jsx(
1322
- ActionIcon,
1323
- {
1324
- variant: "subtle",
1325
- color: "red",
1326
- onClick: () => setDeleteTarget(endpoint),
1327
- disabled: deleteMutation.isPending,
1328
- children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
1329
- }
1330
- ) })
1331
- ] }) })
1332
- ] }, endpoint.id);
1333
- }) })
842
+ ] }) }),
843
+ /* @__PURE__ */ jsx(Divider, {}),
844
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
845
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Alerts" }),
846
+ /* @__PURE__ */ jsx(Alert, { variant: "light", color: "blue", icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Informational", children: "This is how informational alerts look with your current theme." }),
847
+ /* @__PURE__ */ jsx(Alert, { variant: "light", color: "green", icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), title: "Success", children: "Operation completed successfully." })
848
+ ] })
849
+ ] })
1334
850
  ] }),
1335
- /* @__PURE__ */ jsx(EditWebhookEndpointModal, { opened: !!editTarget, endpoint: editTarget, onClose: () => setEditTarget(null) }),
1336
- /* @__PURE__ */ jsx(CustomModal, { opened: !!deleteTarget, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
1337
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1338
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
1339
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Webhook Endpoint" })
1340
- ] }),
851
+ /* @__PURE__ */ jsx(CustomModal, { opened: modalOpen, onClose: () => setModalOpen(false), size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
852
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Modal Preview" }),
1341
853
  /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1342
- "Are you sure you want to delete",
854
+ "Modals should sit visibly above the page background. Elevation comes from the",
1343
855
  " ",
1344
- /* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
1345
- '"',
1346
- deleteTarget?.name,
1347
- '"'
1348
- ] }),
1349
- "?"
856
+ /* @__PURE__ */ jsx(Text, { span: true, ff: "monospace", size: "sm", children: "--color-elevated" }),
857
+ " ",
858
+ "token, which every preset defines as an opaque surface."
1350
859
  ] }),
1351
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone. Any third-party apps posting to this URL will receive a 404. The URL and its key will be permanently revoked." }),
1352
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1353
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
1354
- /* @__PURE__ */ jsx(
1355
- Button,
1356
- {
1357
- color: "red",
1358
- onClick: handleDelete,
1359
- loading: deleteMutation.isPending,
1360
- leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
1361
- children: "Delete Endpoint"
1362
- }
1363
- )
860
+ /* @__PURE__ */ jsx(TextInput, { label: "Example field", placeholder: "Type something...", size: "xs" }),
861
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
862
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", onClick: () => setModalOpen(false), children: "Cancel" }),
863
+ /* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => setModalOpen(false), children: "Confirm" })
1364
864
  ] })
1365
865
  ] }) })
1366
866
  ] });
1367
867
  }
1368
- function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
1369
- const createEndpoint = useCreateWebhookEndpoint();
868
+ function EditWebhookEndpointModal({ opened, endpoint, onClose }) {
869
+ const updateEndpoint = useUpdateWebhookEndpoint();
1370
870
  const { data: resourcesData } = useResources();
1371
871
  const resourceOptions = useMemo(() => {
1372
872
  if (!resourcesData) return [];
@@ -1383,8 +883,7 @@ function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
1383
883
  const form = useForm({
1384
884
  initialValues: {
1385
885
  name: "",
1386
- resourceId: "",
1387
- description: ""
886
+ resourceId: ""
1388
887
  },
1389
888
  validate: {
1390
889
  name: (value) => {
@@ -1395,833 +894,703 @@ function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
1395
894
  }
1396
895
  }
1397
896
  });
897
+ useEffect(() => {
898
+ if (endpoint && opened) {
899
+ form.setValues({
900
+ name: endpoint.name,
901
+ resourceId: endpoint.resourceId ?? ""
902
+ });
903
+ }
904
+ }, [endpoint, opened]);
1398
905
  const handleSubmit = async (values) => {
906
+ if (!endpoint) return;
1399
907
  try {
1400
- const result = await createEndpoint.mutateAsync({
1401
- name: values.name.trim(),
1402
- ...values.resourceId ? { resourceId: values.resourceId } : {},
1403
- ...values.description.trim() ? { description: values.description.trim() } : {}
908
+ await updateEndpoint.mutateAsync({
909
+ endpointId: endpoint.id,
910
+ data: {
911
+ name: values.name.trim(),
912
+ resourceId: values.resourceId || void 0
913
+ }
1404
914
  });
1405
- form.reset();
1406
- onSuccess(result);
915
+ onClose();
1407
916
  } catch (error) {
1408
- console.error("Failed to create webhook endpoint:", error);
917
+ console.error("Failed to update webhook endpoint:", error);
1409
918
  }
1410
919
  };
1411
920
  const handleClose = () => {
1412
- if (!createEndpoint.isPending) {
1413
- form.reset();
921
+ if (!updateEndpoint.isPending) {
1414
922
  onClose();
1415
923
  }
1416
924
  };
1417
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: createEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
1418
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Create Webhook Endpoint" }),
1419
- /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "blue", children: "A unique URL will be generated that any third-party app can POST to, triggering your target resource. The URL contains a secret key \u2014 treat it like a credential." }),
925
+ return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: updateEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
926
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Edit Webhook Endpoint" }),
1420
927
  /* @__PURE__ */ jsx(
1421
928
  TextInput,
1422
- {
1423
- label: "Name",
1424
- description: "A descriptive label for this endpoint",
1425
- placeholder: "e.g., Zapier Lead Intake, Typeform Responses",
1426
- leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
1427
- required: true,
1428
- ...form.getInputProps("name"),
1429
- disabled: createEndpoint.isPending
1430
- }
1431
- ),
1432
- /* @__PURE__ */ jsx(
1433
- Select,
1434
- {
1435
- label: "Target Resource",
1436
- description: "The workflow or agent to trigger on each inbound request (can be set later)",
1437
- placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
1438
- data: resourceOptions,
1439
- searchable: true,
1440
- clearable: true,
1441
- ...form.getInputProps("resourceId"),
1442
- disabled: !resourcesData || createEndpoint.isPending
1443
- }
1444
- ),
1445
- /* @__PURE__ */ jsx(
1446
- Textarea,
1447
- {
1448
- label: "Description",
1449
- description: "Optional notes about what this endpoint is for",
1450
- placeholder: "e.g., Receives new lead submissions from Zapier",
1451
- rows: 3,
1452
- ...form.getInputProps("description"),
1453
- disabled: createEndpoint.isPending
1454
- }
1455
- ),
1456
- createEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: createEndpoint.error instanceof Error ? createEndpoint.error.message : "An error occurred while creating the webhook endpoint" }),
1457
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1458
- /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createEndpoint.isPending, children: "Cancel" }),
1459
- /* @__PURE__ */ jsx(Button, { type: "submit", loading: createEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }), children: "Create Endpoint" })
1460
- ] })
1461
- ] }) }) });
1462
- }
1463
- function getWebhookUrl(apiUrl, key) {
1464
- return `${apiUrl}/api/webhooks/inbound/${key}`;
1465
- }
1466
- function WebhookEndpointSettings({ apiUrl }) {
1467
- const { organizationReady } = useInitialization();
1468
- const [showCreateModal, setShowCreateModal] = useState(false);
1469
- const [newEndpoint, setNewEndpoint] = useState(null);
1470
- const { data: endpoints = [], isLoading } = useListWebhookEndpoints();
1471
- if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
1472
- const handleEndpointCreated = (endpoint) => {
1473
- setNewEndpoint(endpoint);
1474
- setShowCreateModal(false);
1475
- };
1476
- const activeCount = endpoints.filter((e) => e.status === "active").length;
1477
- const totalRequests = endpoints.reduce((sum, e) => sum + e.requestCount, 0);
1478
- return /* @__PURE__ */ jsxs(Stack, { children: [
1479
- /* @__PURE__ */ jsx(
1480
- PageTitleCaption,
1481
- {
1482
- title: "Webhooks",
1483
- caption: "Create webhook URLs that third-party apps can send data to, triggering your resources.",
1484
- rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Endpoint" })
1485
- }
1486
- ),
1487
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
1488
- /* @__PURE__ */ jsx(
1489
- StatCard,
1490
- {
1491
- variant: "hero",
1492
- icon: IconWebhook,
1493
- value: activeCount,
1494
- label: "Active Endpoints",
1495
- isLoading
1496
- }
1497
- ),
1498
- /* @__PURE__ */ jsx(
1499
- StatCard,
1500
- {
1501
- variant: "hero",
1502
- icon: IconActivity,
1503
- value: totalRequests,
1504
- label: "Total Requests",
1505
- isLoading
1506
- }
1507
- )
1508
- ] }),
1509
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(WebhookEndpointList, { endpoints, isLoading }) }),
1510
- /* @__PURE__ */ jsx(
1511
- CreateWebhookEndpointModal,
1512
- {
1513
- opened: showCreateModal,
1514
- onClose: () => setShowCreateModal(false),
1515
- onSuccess: handleEndpointCreated
1516
- }
1517
- ),
1518
- newEndpoint && newEndpoint.key && /* @__PURE__ */ jsx(
1519
- WebhookUrlDisplayModal,
1520
- {
1521
- opened: true,
1522
- endpoint: newEndpoint,
1523
- webhookUrl: getWebhookUrl(apiUrl, newEndpoint.key),
1524
- onClose: () => setNewEndpoint(null)
1525
- }
1526
- )
1527
- ] });
1528
- }
1529
- function diffRoles(current, next) {
1530
- const adds = next.filter((id) => !current.includes(id));
1531
- const removes = current.filter((id) => !next.includes(id));
1532
- return { adds, removes };
1533
- }
1534
- function MemberAccessModal({ opened, onClose, orgId, member, defaultTab = "roles" }) {
1535
- const [activeTab, setActiveTab] = useState(defaultTab);
1536
- useEffect(() => {
1537
- if (opened) setActiveTab(defaultTab);
1538
- }, [opened, defaultTab]);
1539
- const handleClose = () => {
1540
- onClose();
1541
- };
1542
- if (!member) return null;
1543
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "xl", children: /* @__PURE__ */ jsxs(Stack, { p: "md", gap: "md", children: [
1544
- /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1545
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Member access" }),
1546
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1547
- /* @__PURE__ */ jsx(Text, { fw: 500, children: member.name }),
1548
- /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: member.role })
1549
- ] }),
1550
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.email })
1551
- ] }),
1552
- activeTab === "roles" && /* @__PURE__ */ jsx(RolesTabPanel, { orgId, member, onSaved: onClose })
1553
- ] }) });
1554
- }
1555
- function RolesTabPanel({ orgId, member, onSaved }) {
1556
- const { data: rolesData, isLoading: rolesLoading } = useOrgRoles(orgId ? brandSupabaseOrgId(orgId) : void 0);
1557
- const assignMutation = useAssignRole();
1558
- const revokeMutation = useRevokeRole();
1559
- const canManagePrivileged = useAccess(AccessKeys.rolesManage).allowed;
1560
- const allRoles = rolesData?.roles ?? [];
1561
- const currentRoleIds = useMemo(() => {
1562
- const matched = allRoles.find((r) => r.slug === member.role);
1563
- return matched ? [matched.id] : [];
1564
- }, [allRoles, member.role]);
1565
- const [selectedIds, setSelectedIds] = useState(currentRoleIds);
1566
- const [saving, setSaving] = useState(false);
1567
- useEffect(() => {
1568
- setSelectedIds(currentRoleIds);
1569
- }, [currentRoleIds, member.id]);
1570
- const options = allRoles.map((r) => ({
1571
- value: r.id,
1572
- label: r.name + (r.is_system ? " (built-in)" : ""),
1573
- disabled: !canManagePrivileged && (r.slug === "owner" || r.slug === "admin")
1574
- }));
1575
- const hasZeroRoles = selectedIds.length === 0;
1576
- const isUnchanged = selectedIds.length === currentRoleIds.length && selectedIds.every((id) => currentRoleIds.includes(id));
1577
- async function handleSave() {
1578
- if (hasZeroRoles || isUnchanged) return;
1579
- setSaving(true);
1580
- try {
1581
- const { adds, removes } = diffRoles(currentRoleIds, selectedIds);
1582
- await Promise.all([
1583
- ...adds.map((roleId) => assignMutation.mutateAsync({ membershipId: member.id, roleId })),
1584
- ...removes.map((roleId) => revokeMutation.mutateAsync({ membershipId: member.id, roleId }))
1585
- ]);
1586
- showSuccessNotification("Roles updated");
1587
- onSaved();
1588
- } catch (err) {
1589
- showAuthError(err);
1590
- } finally {
1591
- setSaving(false);
1592
- }
1593
- }
1594
- return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1595
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Select the roles assigned to this member. Each role grants a bundle of permissions; the member's effective permissions are the union of all assigned roles." }),
929
+ {
930
+ label: "Name",
931
+ description: "A descriptive label for this endpoint",
932
+ leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
933
+ required: true,
934
+ ...form.getInputProps("name"),
935
+ disabled: updateEndpoint.isPending
936
+ }
937
+ ),
1596
938
  /* @__PURE__ */ jsx(
1597
- MultiSelect,
939
+ Select,
1598
940
  {
1599
- data: options,
1600
- value: selectedIds,
1601
- onChange: setSelectedIds,
1602
- placeholder: rolesLoading ? "Loading roles..." : "Select roles",
1603
- disabled: saving || rolesLoading,
941
+ label: "Target Resource",
942
+ description: "The workflow or agent to trigger on each inbound request",
943
+ placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
944
+ data: resourceOptions,
1604
945
  searchable: true,
1605
- clearable: false
946
+ clearable: true,
947
+ ...form.getInputProps("resourceId"),
948
+ disabled: !resourcesData || updateEndpoint.isPending
1606
949
  }
1607
950
  ),
1608
- !canManagePrivileged && /* @__PURE__ */ jsxs(Alert, { color: "blue", variant: "light", children: [
1609
- "Only owners (with ",
1610
- /* @__PURE__ */ jsx("code", { children: "roles.manage" }),
1611
- ") can grant or revoke the owner / admin system roles."
1612
- ] }),
1613
- !rolesLoading && hasZeroRoles && currentRoleIds.length > 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "At least one role is required for an active membership." }),
1614
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
1615
- /* @__PURE__ */ jsx(Button, { variant: "default", onClick: onSaved, disabled: saving, children: "Cancel" }),
1616
- /* @__PURE__ */ jsx(
1617
- Button,
1618
- {
1619
- leftSection: /* @__PURE__ */ jsx(IconCheck, { size: 14 }),
1620
- loading: saving,
1621
- disabled: hasZeroRoles || isUnchanged,
1622
- onClick: handleSave,
1623
- children: "Save roles"
1624
- }
1625
- )
951
+ updateEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: updateEndpoint.error instanceof Error ? updateEndpoint.error.message : "An error occurred while updating the webhook endpoint" }),
952
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
953
+ /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: updateEndpoint.isPending, children: "Cancel" }),
954
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: updateEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconPencil, { size: 16 }), children: "Save Changes" })
1626
955
  ] })
1627
- ] });
956
+ ] }) }) });
1628
957
  }
1629
- function transformMemberForModal(member) {
1630
- const firstName = member.user?.firstName || "";
1631
- const lastName = member.user?.lastName || "";
1632
- const name = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1633
- return {
1634
- id: member.id,
1635
- name,
1636
- email: member.user?.email || "Unknown",
1637
- role: member.role.slug
1638
- };
958
+ function formatKeyDisplay(keyPrefix) {
959
+ if (!keyPrefix) return "Unavailable";
960
+ return `${keyPrefix}...`;
1639
961
  }
1640
- function OrgMembersList({ orgId }) {
1641
- const { data: members, isLoading, error } = useOrganizationMembers(brandSupabaseOrgId(orgId));
1642
- const [selectedMember, setSelectedMember] = useState(null);
1643
- const [modalOpened, setModalOpened] = useState(false);
1644
- const [defaultTab, setDefaultTab] = useState("roles");
1645
- const openModal2 = (member, tab) => {
1646
- setSelectedMember(member);
1647
- setDefaultTab(tab);
1648
- setModalOpened(true);
962
+ function WebhookEndpointList({ endpoints, isLoading }) {
963
+ const [deleteTarget, setDeleteTarget] = useState(null);
964
+ const [editTarget, setEditTarget] = useState(null);
965
+ const deleteMutation = useDeleteWebhookEndpoint();
966
+ const updateMutation = useUpdateWebhookEndpoint();
967
+ const handleDelete = () => {
968
+ if (deleteTarget) {
969
+ deleteMutation.mutate(deleteTarget.id);
970
+ setDeleteTarget(null);
971
+ }
1649
972
  };
1650
- const handleModalClose = () => {
1651
- setModalOpened(false);
1652
- setSelectedMember(null);
973
+ const handleCloseDelete = () => {
974
+ if (!deleteMutation.isPending) {
975
+ setDeleteTarget(null);
976
+ }
977
+ };
978
+ const handleToggleStatus = (endpoint) => {
979
+ const newStatus = endpoint.status === "active" ? "paused" : "active";
980
+ updateMutation.mutate({ endpointId: endpoint.id, data: { status: newStatus } });
1653
981
  };
1654
982
  if (isLoading) {
1655
- return /* @__PURE__ */ jsx(Center, { py: "xl", children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "sm", children: [
1656
- /* @__PURE__ */ jsx(Loader, { size: "md" }),
1657
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Loading members..." })
1658
- ] }) });
1659
- }
1660
- if (error) {
1661
- return /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Error loading members" });
983
+ return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 50 });
1662
984
  }
1663
- if (!members || members.length === 0) {
1664
- return /* @__PURE__ */ jsx(EmptyState, { icon: IconUsers, title: "No members found" });
985
+ if (endpoints.length === 0) {
986
+ return /* @__PURE__ */ jsx(
987
+ EmptyState,
988
+ {
989
+ icon: IconWebhook,
990
+ title: "No webhook endpoints yet",
991
+ description: "Create your first webhook endpoint to let third-party apps trigger your resources"
992
+ }
993
+ );
1665
994
  }
1666
995
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1667
- /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 500, children: /* @__PURE__ */ jsxs(Table, { children: [
996
+ /* @__PURE__ */ jsxs(Table, { children: [
1668
997
  /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1669
998
  /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
1670
- /* @__PURE__ */ jsx(Table.Th, { children: "Email" }),
1671
- /* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
1672
999
  /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1673
- /* @__PURE__ */ jsx(Table.Th, { children: "Actions" })
1000
+ /* @__PURE__ */ jsx(Table.Th, { children: "Target Resource" }),
1001
+ /* @__PURE__ */ jsx(Table.Th, { children: "Key" }),
1002
+ /* @__PURE__ */ jsx(Table.Th, { children: "Last Triggered" }),
1003
+ /* @__PURE__ */ jsx(Table.Th, { children: "Requests" }),
1004
+ /* @__PURE__ */ jsx(Table.Th, { w: 120 })
1674
1005
  ] }) }),
1675
- /* @__PURE__ */ jsx(Table.Tbody, { children: members.map((member) => {
1676
- const firstName = member.user?.firstName || "";
1677
- const lastName = member.user?.lastName || "";
1678
- const displayName = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1006
+ /* @__PURE__ */ jsx(Table.Tbody, { children: endpoints.map((endpoint) => {
1007
+ const keyDisplay = formatKeyDisplay(endpoint.keyPrefix);
1008
+ const isToggling = updateMutation.isPending;
1679
1009
  return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1680
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: displayName }) }),
1681
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.user?.email || "Unknown" }) }),
1682
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1683
- RoleBadge,
1684
- {
1685
- slug: member.role.slug,
1686
- name: member.role.slug.charAt(0).toUpperCase() + member.role.slug.slice(1),
1687
- isSystem: true
1688
- }
1689
- ) }),
1690
1010
  /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1691
- /* @__PURE__ */ jsx(MembershipStatusBadge, { status: member.status }),
1692
- /* @__PURE__ */ jsx(ProvisioningStateBadge, { state: member.provisioningState })
1011
+ /* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6 } }),
1012
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1013
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: endpoint.name }),
1014
+ endpoint.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: endpoint.description })
1015
+ ] })
1693
1016
  ] }) }),
1694
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Tooltip, { label: "Edit member access", withArrow: true, children: /* @__PURE__ */ jsx(ActionIcon, { variant: "light", size: "sm", color: "gray", onClick: () => openModal2(member, "roles"), children: /* @__PURE__ */ jsx(IconPencil, { size: 14 }) }) }) })
1695
- ] }, member.id);
1696
- }) })
1697
- ] }) }),
1698
- /* @__PURE__ */ jsx(
1699
- MemberAccessModal,
1700
- {
1701
- opened: modalOpened,
1702
- onClose: handleModalClose,
1703
- orgId,
1704
- member: selectedMember ? transformMemberForModal(selectedMember) : null,
1705
- defaultTab
1706
- }
1707
- )
1708
- ] });
1709
- }
1710
- function OrganizationSettings({ user, currentMembership, isOrgAdmin }) {
1711
- return /* @__PURE__ */ jsxs(Stack, { children: [
1712
- /* @__PURE__ */ jsx(PageTitleCaption, { title: "Organization Settings", caption: "Manage your current organization" }),
1713
- currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1714
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1715
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconBuilding, { size: 18 }) }),
1716
- /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1717
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Current Organization" }),
1718
- /* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: currentMembership.organization.name })
1719
- ] }),
1720
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1721
- /* @__PURE__ */ jsx(Code, { children: currentMembership.organization.id }),
1722
- /* @__PURE__ */ jsx(CopyButton, { value: currentMembership.organization.id, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
1723
- ActionIcon,
1724
- {
1725
- color: copied ? "teal" : "gray",
1726
- variant: copied ? "filled" : "subtle",
1727
- onClick: copy,
1728
- size: "sm",
1729
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1730
- }
1731
- ) }) })
1732
- ] })
1733
- ] }),
1734
- user && /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1735
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(IconUsers, { size: 18 }) }),
1736
- /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1737
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Signed in as" }),
1738
- /* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
1739
- user.firstName,
1740
- " ",
1741
- user.lastName,
1742
- " \u2022 ",
1743
- user.email
1744
- ] })
1745
- ] }),
1746
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1747
- /* @__PURE__ */ jsx(Code, { children: currentMembership.userId }),
1748
- /* @__PURE__ */ jsx(CopyButton, { value: currentMembership.userId, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy User ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
1749
- ActionIcon,
1017
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { color: endpoint.status === "active" ? "teal" : "gray", variant: "light", children: endpoint.status }) }),
1018
+ /* @__PURE__ */ jsx(Table.Td, { children: endpoint.resourceId ? /* @__PURE__ */ jsx(Code, { children: endpoint.resourceId }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", fs: "italic", children: "Not configured" }) }),
1019
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1020
+ Tooltip,
1750
1021
  {
1751
- color: copied ? "teal" : "gray",
1752
- variant: copied ? "filled" : "subtle",
1753
- onClick: copy,
1754
- size: "sm",
1755
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1022
+ label: "Full URL is shown only at creation. Use the prefix to identify which endpoint this is.",
1023
+ position: "top",
1024
+ children: /* @__PURE__ */ jsx(Paper, { withBorder: true, style: { display: "inline-flex", alignItems: "center", padding: "2px 8px" }, children: /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { userSelect: "all" }, children: keyDisplay }) })
1756
1025
  }
1757
- ) }) })
1758
- ] })
1759
- ] })
1760
- ] }) }),
1761
- isOrgAdmin && currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
1762
- /* @__PURE__ */ jsx(
1763
- CardHeader,
1764
- {
1765
- icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
1766
- title: "Manage Members",
1767
- subtitle: "Configure feature access for organization members."
1768
- }
1769
- ),
1770
- /* @__PURE__ */ jsx(OrgMembersList, { orgId: currentMembership.organization.id })
1771
- ] }) })
1772
- ] });
1773
- }
1774
- function capitalize(value) {
1775
- return value.charAt(0).toUpperCase() + value.slice(1);
1776
- }
1777
- function MyRolesPage({ onManageMembers, onManageOrganizationRoles }) {
1778
- const { currentMembership } = useOrganization();
1779
- const orgName = currentMembership?.organization?.name ?? "this organization";
1780
- const access = useAccess("platform");
1781
- const { permissions, isReady: permissionsReady, isPlatformAdmin } = access;
1782
- const catalogQuery = usePermissionCatalog();
1783
- const catalogMap = new Map(
1784
- (catalogQuery.data?.permissions ?? []).map((permission) => [permission.key, permission.description])
1785
- );
1786
- const roleSlug = currentMembership?.role?.slug ?? null;
1787
- const isLoading = !permissionsReady || catalogQuery.isLoading;
1788
- const showManagePanel = Boolean(onManageMembers || onManageOrganizationRoles);
1789
- const groupedPermissions = permissions.reduce((groups, key) => {
1790
- const domain = key.split(".")[0] ?? "general";
1791
- groups[domain] ??= [];
1792
- groups[domain].push(key);
1793
- return groups;
1794
- }, {});
1795
- return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1796
- /* @__PURE__ */ jsx(PageTitleCaption, { title: "My Roles", caption: `Roles assigned to you in ${orgName} and what each one lets you do.` }),
1797
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
1798
- /* @__PURE__ */ jsx(
1799
- CardHeader,
1800
- {
1801
- icon: /* @__PURE__ */ jsx(IconShieldLock, { size: 18 }),
1802
- title: "Your roles",
1803
- subtitle: `Roles assigned to your membership in ${orgName}.`
1804
- }
1805
- ),
1026
+ ) }),
1027
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
1028
+ /* @__PURE__ */ jsx(IconClock, { size: 14, style: { opacity: 0.5 } }),
1029
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(endpoint.lastTriggeredAt) })
1030
+ ] }) }),
1031
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: endpoint.requestCount.toLocaleString() }) }),
1032
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
1033
+ /* @__PURE__ */ jsx(Tooltip, { label: "Edit endpoint", position: "left", children: /* @__PURE__ */ jsx(ActionIcon, { variant: "subtle", color: "blue", onClick: () => setEditTarget(endpoint), children: /* @__PURE__ */ jsx(IconPencil, { size: 16 }) }) }),
1034
+ /* @__PURE__ */ jsx(
1035
+ Tooltip,
1036
+ {
1037
+ label: endpoint.status === "active" ? "Pause endpoint" : "Resume endpoint",
1038
+ position: "left",
1039
+ children: /* @__PURE__ */ jsx(
1040
+ ActionIcon,
1041
+ {
1042
+ variant: "subtle",
1043
+ color: endpoint.status === "active" ? "yellow" : "teal",
1044
+ onClick: () => handleToggleStatus(endpoint),
1045
+ disabled: isToggling,
1046
+ children: endpoint.status === "active" ? /* @__PURE__ */ jsx(IconPlayerPause, { size: 16 }) : /* @__PURE__ */ jsx(IconPlayerPlay, { size: 16 })
1047
+ }
1048
+ )
1049
+ }
1050
+ ),
1051
+ /* @__PURE__ */ jsx(Tooltip, { label: "Delete endpoint", position: "left", children: /* @__PURE__ */ jsx(
1052
+ ActionIcon,
1053
+ {
1054
+ variant: "subtle",
1055
+ color: "red",
1056
+ onClick: () => setDeleteTarget(endpoint),
1057
+ disabled: deleteMutation.isPending,
1058
+ children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
1059
+ }
1060
+ ) })
1061
+ ] }) })
1062
+ ] }, endpoint.id);
1063
+ }) })
1064
+ ] }),
1065
+ /* @__PURE__ */ jsx(EditWebhookEndpointModal, { opened: !!editTarget, endpoint: editTarget, onClose: () => setEditTarget(null) }),
1066
+ /* @__PURE__ */ jsx(CustomModal, { opened: !!deleteTarget, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
1806
1067
  /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1807
- roleSlug ? /* @__PURE__ */ jsx(RoleBadge, { slug: roleSlug, name: capitalize(roleSlug), isSystem: true }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No roles assigned yet." }),
1808
- isPlatformAdmin && /* @__PURE__ */ jsx(Badge, { color: "grape", variant: "light", leftSection: /* @__PURE__ */ jsx(IconStar, { size: 12 }), children: "Platform admin" })
1068
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
1069
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Webhook Endpoint" })
1809
1070
  ] }),
1810
- isPlatformAdmin && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Platform admin bypasses every org-level permission check; you have full access regardless of role." })
1811
- ] }) }),
1812
- /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
1813
- /* @__PURE__ */ jsx(
1814
- CardHeader,
1815
- {
1816
- icon: /* @__PURE__ */ jsx(IconCheck, { size: 18 }),
1817
- title: "What you can do",
1818
- subtitle: isPlatformAdmin ? "Showing all platform permissions (platform admin bypass)." : "Permissions granted by your assigned roles."
1819
- }
1820
- ),
1821
- isLoading ? /* @__PURE__ */ jsx(Center, { children: /* @__PURE__ */ jsx(Loader, {}) }) : permissions.length === 0 ? /* @__PURE__ */ jsx(Alert, { color: "yellow", children: "You don't have any permissions in this organization yet. Ask an admin to assign you a role." }) : /* @__PURE__ */ jsx(Stack, { gap: "md", children: Object.entries(groupedPermissions).map(([domain, keys]) => /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1822
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: capitalize(domain) }),
1823
- keys.map((key) => /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1824
- /* @__PURE__ */ jsx(IconCheck, { size: 14, color: "var(--color-success)" }),
1825
- /* @__PURE__ */ jsx(Code, { children: key }),
1826
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: catalogMap.get(key) ?? "" })
1827
- ] }, key))
1828
- ] }, domain)) })
1829
- ] }) }),
1830
- showManagePanel && /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.membersManage, children: /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
1831
- /* @__PURE__ */ jsx(
1832
- CardHeader,
1833
- {
1834
- icon: /* @__PURE__ */ jsx(IconUserCog, { size: 18 }),
1835
- title: "Manage roles & members",
1836
- subtitle: "Assign roles to members, create custom roles, and edit permission sets."
1837
- }
1838
- ),
1839
- /* @__PURE__ */ jsxs(Group, { children: [
1840
- onManageMembers ? /* @__PURE__ */ jsx(Button, { variant: "light", leftSection: /* @__PURE__ */ jsx(IconUsersGroup, { size: 14 }), onClick: onManageMembers, children: "Members" }) : null,
1841
- onManageOrganizationRoles ? /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.organizationManage, children: /* @__PURE__ */ jsx(Button, { variant: "light", leftSection: /* @__PURE__ */ jsx(IconShieldLock, { size: 14 }), onClick: onManageOrganizationRoles, children: "Organization roles" }) }) : null
1071
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1072
+ "Are you sure you want to delete",
1073
+ " ",
1074
+ /* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
1075
+ '"',
1076
+ deleteTarget?.name,
1077
+ '"'
1078
+ ] }),
1079
+ "?"
1080
+ ] }),
1081
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone. Any third-party apps posting to this URL will receive a 404. The URL and its key will be permanently revoked." }),
1082
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1083
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
1084
+ /* @__PURE__ */ jsx(
1085
+ Button,
1086
+ {
1087
+ color: "red",
1088
+ onClick: handleDelete,
1089
+ loading: deleteMutation.isPending,
1090
+ leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
1091
+ children: "Delete Endpoint"
1092
+ }
1093
+ )
1842
1094
  ] })
1843
- ] }) }) })
1095
+ ] }) })
1844
1096
  ] });
1845
1097
  }
1846
- var OAUTH_COMPLETION_CHANNEL = "elevasis-oauth";
1847
- var OAUTH_COMPLETION_STORAGE_KEY = "elevasis:oauth-complete";
1848
- function useOAuthFlow({ apiUrl }) {
1849
- const [isAuthorizing, setIsAuthorizing] = useState(false);
1850
- const [error, setError] = useState(null);
1851
- const { isReady } = useElevasisServices();
1852
- const { getAccessToken, organizationId } = useAuthContext();
1853
- const authorize = useCallback(
1854
- async (provider, credentialName) => {
1855
- if (!isReady || !organizationId) {
1856
- setError("Organization context not ready");
1857
- return;
1098
+ function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
1099
+ const createEndpoint = useCreateWebhookEndpoint();
1100
+ const { data: resourcesData } = useResources();
1101
+ const resourceOptions = useMemo(() => {
1102
+ if (!resourcesData) return [];
1103
+ const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
1104
+ value: w.resourceId,
1105
+ label: `Workflow: ${w.resourceId}`
1106
+ }));
1107
+ const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
1108
+ value: a.resourceId,
1109
+ label: `Agent: ${a.resourceId}`
1110
+ }));
1111
+ return [...workflowOptions, ...agentOptions];
1112
+ }, [resourcesData]);
1113
+ const form = useForm({
1114
+ initialValues: {
1115
+ name: "",
1116
+ resourceId: "",
1117
+ description: ""
1118
+ },
1119
+ validate: {
1120
+ name: (value) => {
1121
+ if (!value.trim()) return "Name is required";
1122
+ if (value.trim().length < 2) return "Name must be at least 2 characters";
1123
+ if (value.trim().length > 100) return "Name must be less than 100 characters";
1124
+ return null;
1858
1125
  }
1859
- setIsAuthorizing(true);
1860
- setError(null);
1861
- const width = 600;
1862
- const height = 700;
1863
- const left = Math.round(window.screen.width / 2 - width / 2);
1864
- const top = Math.round(window.screen.height / 2 - height / 2);
1865
- const popup = window.open(
1866
- "about:blank",
1867
- `oauth-${provider}`,
1868
- `width=${width},height=${height},left=${left},top=${top}`
1869
- );
1870
- if (!popup) {
1871
- setIsAuthorizing(false);
1872
- setError("Please enable popups and try again");
1873
- return;
1126
+ }
1127
+ });
1128
+ const handleSubmit = async (values) => {
1129
+ try {
1130
+ const result = await createEndpoint.mutateAsync({
1131
+ name: values.name.trim(),
1132
+ ...values.resourceId ? { resourceId: values.resourceId } : {},
1133
+ ...values.description.trim() ? { description: values.description.trim() } : {}
1134
+ });
1135
+ form.reset();
1136
+ onSuccess(result);
1137
+ } catch (error) {
1138
+ console.error("Failed to create webhook endpoint:", error);
1139
+ }
1140
+ };
1141
+ const handleClose = () => {
1142
+ if (!createEndpoint.isPending) {
1143
+ form.reset();
1144
+ onClose();
1145
+ }
1146
+ };
1147
+ return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: createEndpoint.isPending, children: /* @__PURE__ */ jsx("form", { onSubmit: form.onSubmit(handleSubmit), children: /* @__PURE__ */ jsxs(Stack, { children: [
1148
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Create Webhook Endpoint" }),
1149
+ /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "blue", children: "A unique URL will be generated that any third-party app can POST to, triggering your target resource. The URL contains a secret key \u2014 treat it like a credential." }),
1150
+ /* @__PURE__ */ jsx(
1151
+ TextInput,
1152
+ {
1153
+ label: "Name",
1154
+ description: "A descriptive label for this endpoint",
1155
+ placeholder: "e.g., Zapier Lead Intake, Typeform Responses",
1156
+ leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
1157
+ required: true,
1158
+ ...form.getInputProps("name"),
1159
+ disabled: createEndpoint.isPending
1874
1160
  }
1875
- let token;
1876
- try {
1877
- token = await getAccessToken();
1878
- } catch {
1879
- popup.close();
1880
- setIsAuthorizing(false);
1881
- setError("Authorization failed. Please sign in again.");
1882
- return;
1161
+ ),
1162
+ /* @__PURE__ */ jsx(
1163
+ Select,
1164
+ {
1165
+ label: "Target Resource",
1166
+ description: "The workflow or agent to trigger on each inbound request (can be set later)",
1167
+ placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
1168
+ data: resourceOptions,
1169
+ searchable: true,
1170
+ clearable: true,
1171
+ ...form.getInputProps("resourceId"),
1172
+ disabled: !resourcesData || createEndpoint.isPending
1883
1173
  }
1884
- try {
1885
- const authorizeUrl = new URL(`${apiUrl}/api/oauth/authorize/${provider}`);
1886
- authorizeUrl.searchParams.set("credentialName", credentialName);
1887
- authorizeUrl.searchParams.set("token", token);
1888
- authorizeUrl.searchParams.set("orgId", organizationId);
1889
- popup.location.href = authorizeUrl.toString();
1890
- await waitForOAuthCallback(popup, Date.now());
1891
- } catch (err) {
1892
- const rawError = err instanceof Error ? err.message : "unknown";
1893
- if (rawError.includes("popup_blocked")) {
1894
- setError("Please enable popups and try again");
1895
- } else if (rawError.includes("timeout")) {
1896
- setError("Authorization timed out. Please try again.");
1897
- } else if (rawError.includes("popup_closed")) {
1898
- setError("Authorization was cancelled.");
1899
- } else if (rawError.includes("token_exchange_failed")) {
1900
- setError("Failed to obtain access token. Please try again.");
1901
- } else if (rawError.includes("invalid_state")) {
1902
- setError("Security validation failed. Please try again.");
1903
- } else if (rawError.includes("state_expired")) {
1904
- setError("Authorization expired. Please try again.");
1905
- } else {
1906
- setError(`Failed to connect: ${rawError}`);
1907
- }
1908
- throw err;
1909
- } finally {
1910
- setIsAuthorizing(false);
1174
+ ),
1175
+ /* @__PURE__ */ jsx(
1176
+ Textarea,
1177
+ {
1178
+ label: "Description",
1179
+ description: "Optional notes about what this endpoint is for",
1180
+ placeholder: "e.g., Receives new lead submissions from Zapier",
1181
+ rows: 3,
1182
+ ...form.getInputProps("description"),
1183
+ disabled: createEndpoint.isPending
1184
+ }
1185
+ ),
1186
+ createEndpoint.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconExclamationMark, { size: 16 }), color: "red", children: createEndpoint.error instanceof Error ? createEndpoint.error.message : "An error occurred while creating the webhook endpoint" }),
1187
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1188
+ /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createEndpoint.isPending, children: "Cancel" }),
1189
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: createEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }), children: "Create Endpoint" })
1190
+ ] })
1191
+ ] }) }) });
1192
+ }
1193
+ function getWebhookUrl(apiUrl, key) {
1194
+ return `${apiUrl}/api/webhooks/inbound/${key}`;
1195
+ }
1196
+ function WebhookEndpointSettings({ apiUrl }) {
1197
+ const { organizationReady } = useInitialization();
1198
+ const [showCreateModal, setShowCreateModal] = useState(false);
1199
+ const [newEndpoint, setNewEndpoint] = useState(null);
1200
+ const { data: endpoints = [], isLoading } = useListWebhookEndpoints();
1201
+ if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
1202
+ const handleEndpointCreated = (endpoint) => {
1203
+ setNewEndpoint(endpoint);
1204
+ setShowCreateModal(false);
1205
+ };
1206
+ const activeCount = endpoints.filter((e) => e.status === "active").length;
1207
+ const totalRequests = endpoints.reduce((sum, e) => sum + e.requestCount, 0);
1208
+ return /* @__PURE__ */ jsxs(Stack, { children: [
1209
+ /* @__PURE__ */ jsx(
1210
+ PageTitleCaption,
1211
+ {
1212
+ title: "Webhooks",
1213
+ caption: "Create webhook URLs that third-party apps can send data to, triggering your resources.",
1214
+ rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Endpoint" })
1911
1215
  }
1912
- },
1913
- [isReady, organizationId, apiUrl, getAccessToken]
1914
- );
1915
- const waitForOAuthCallback = (popup, startedAt) => {
1916
- return new Promise((resolve, reject) => {
1917
- let broadcastChannel = null;
1918
- const complete = (data) => {
1919
- const payload = parseOAuthCompletionPayload(data, startedAt);
1920
- if (!payload) return;
1921
- cleanup();
1922
- if (payload.status === "success") {
1923
- resolve();
1924
- } else {
1925
- reject(new Error(payload.error || "oauth_failed"));
1926
- }
1927
- };
1928
- const messageHandler = (event) => {
1929
- if (!isTrustedCallbackOrigin(event.origin)) return;
1930
- complete(event.data);
1931
- };
1932
- const storageHandler = (event) => {
1933
- if (event.key !== OAUTH_COMPLETION_STORAGE_KEY || !event.newValue) return;
1934
- try {
1935
- complete(JSON.parse(event.newValue));
1936
- } catch {
1216
+ ),
1217
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
1218
+ /* @__PURE__ */ jsx(
1219
+ StatCard,
1220
+ {
1221
+ variant: "hero",
1222
+ icon: IconWebhook,
1223
+ value: activeCount,
1224
+ label: "Active Endpoints",
1225
+ isLoading
1937
1226
  }
1938
- };
1939
- const rejectIfPopupClosed = () => {
1940
- if (popup.closed) {
1941
- cleanup();
1942
- reject(new Error("popup_closed"));
1227
+ ),
1228
+ /* @__PURE__ */ jsx(
1229
+ StatCard,
1230
+ {
1231
+ variant: "hero",
1232
+ icon: IconActivity,
1233
+ value: totalRequests,
1234
+ label: "Total Requests",
1235
+ isLoading
1943
1236
  }
1944
- };
1945
- window.addEventListener("message", messageHandler);
1946
- window.addEventListener("storage", storageHandler);
1947
- window.addEventListener("focus", rejectIfPopupClosed);
1948
- if ("BroadcastChannel" in window) {
1949
- broadcastChannel = new BroadcastChannel(OAUTH_COMPLETION_CHANNEL);
1950
- broadcastChannel.onmessage = (event) => complete(event.data);
1237
+ )
1238
+ ] }),
1239
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(WebhookEndpointList, { endpoints, isLoading }) }),
1240
+ /* @__PURE__ */ jsx(
1241
+ CreateWebhookEndpointModal,
1242
+ {
1243
+ opened: showCreateModal,
1244
+ onClose: () => setShowCreateModal(false),
1245
+ onSuccess: handleEndpointCreated
1951
1246
  }
1952
- const timeout = setTimeout(() => {
1953
- cleanup();
1954
- reject(new Error("timeout"));
1955
- }, OAUTH_FLOW_TIMEOUT);
1956
- const cleanup = () => {
1957
- clearTimeout(timeout);
1958
- broadcastChannel?.close();
1959
- window.removeEventListener("focus", rejectIfPopupClosed);
1960
- window.removeEventListener("storage", storageHandler);
1961
- window.removeEventListener("message", messageHandler);
1962
- };
1963
- });
1964
- };
1965
- return { authorize, isAuthorizing, error };
1966
- }
1967
- function parseOAuthCompletionPayload(data, startedAt) {
1968
- if (!data || typeof data !== "object") return null;
1969
- const payload = data;
1970
- if (payload.type !== "oauth-complete") return null;
1971
- if (payload.completedAt && payload.completedAt < startedAt) return null;
1972
- return payload;
1247
+ ),
1248
+ newEndpoint && newEndpoint.key && /* @__PURE__ */ jsx(
1249
+ WebhookUrlDisplayModal,
1250
+ {
1251
+ opened: true,
1252
+ endpoint: newEndpoint,
1253
+ webhookUrl: getWebhookUrl(apiUrl, newEndpoint.key),
1254
+ onClose: () => setNewEndpoint(null)
1255
+ }
1256
+ )
1257
+ ] });
1973
1258
  }
1974
- function isTrustedCallbackOrigin(origin) {
1975
- if (origin === window.location.origin || origin === "https://app.elevasis.io") {
1976
- return true;
1977
- }
1978
- try {
1979
- const url = new URL(origin);
1980
- return url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
1981
- } catch {
1982
- return false;
1983
- }
1259
+ function diffRoles(current, next) {
1260
+ const adds = next.filter((id) => !current.includes(id));
1261
+ const removes = current.filter((id) => !next.includes(id));
1262
+ return { adds, removes };
1984
1263
  }
1985
- function EditCredentialModal({ apiUrl, credential, onClose }) {
1986
- const [credentialName, setCredentialName] = useState("");
1987
- const [nameError, setNameError] = useState(null);
1988
- const [fieldValues, setFieldValues] = useState({});
1989
- const { authorize, isAuthorizing, error: oauthError } = useOAuthFlow({ apiUrl });
1990
- const updateMutation = useUpdateCredential();
1264
+ function MemberAccessModal({ opened, onClose, orgId, member, defaultTab = "roles" }) {
1265
+ const [activeTab, setActiveTab] = useState(defaultTab);
1991
1266
  useEffect(() => {
1992
- if (credential) {
1993
- setCredentialName(credential.name);
1994
- }
1995
- }, [credential]);
1996
- if (!credential) return null;
1997
- const credentialType = credential.type || "api-key";
1998
- const schema = getCredentialSchema(credentialType);
1999
- const provider = credential.provider;
2000
- const isOAuth = !!provider || schema.type === "oauth";
2001
- const hasMetadataChanged = credentialName !== credential.name;
2002
- const isPending = isAuthorizing || updateMutation.isPending;
1267
+ if (opened) setActiveTab(defaultTab);
1268
+ }, [opened, defaultTab]);
2003
1269
  const handleClose = () => {
2004
- if (!isPending) {
2005
- onClose();
2006
- }
2007
- };
2008
- const handleOAuthReconnect = async () => {
2009
- if (!provider) return;
2010
- try {
2011
- await authorize(provider, credential.name);
2012
- onClose();
2013
- } catch (err) {
2014
- console.error("Reconnect failed:", err);
2015
- }
2016
- };
2017
- const handleSaveChanges = async () => {
2018
- const nameValidation = CredentialSchemas.UpdateRequest.shape.name.safeParse(credentialName);
2019
- if (!nameValidation.success) {
2020
- setNameError(nameValidation.error.issues[0]?.message || "Invalid credential name");
2021
- return;
2022
- }
2023
- setNameError(null);
2024
- const updateData = { name: credentialName };
2025
- const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
2026
- if (!validationResult.success) {
2027
- console.error("Validation failed:", validationResult.error);
2028
- return;
2029
- }
2030
- try {
2031
- await updateMutation.mutateAsync({
2032
- credentialId: credential.id,
2033
- updates: updateData
2034
- });
2035
- onClose();
2036
- } catch (err) {
2037
- console.error("Save changes failed:", err);
2038
- }
1270
+ onClose();
2039
1271
  };
2040
- const handleCredentialUpdate = async () => {
2041
- const hasValues = Object.values(fieldValues).some((v) => v.trim());
2042
- if (!hasValues) return;
1272
+ if (!member) return null;
1273
+ return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "xl", children: /* @__PURE__ */ jsxs(Stack, { p: "md", gap: "md", children: [
1274
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1275
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Member access" }),
1276
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1277
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: member.name }),
1278
+ /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: member.role })
1279
+ ] }),
1280
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.email })
1281
+ ] }),
1282
+ activeTab === "roles" && /* @__PURE__ */ jsx(RolesTabPanel, { orgId, member, onSaved: onClose })
1283
+ ] }) });
1284
+ }
1285
+ function RolesTabPanel({ orgId, member, onSaved }) {
1286
+ const { data: rolesData, isLoading: rolesLoading } = useOrgRoles(orgId ?? void 0);
1287
+ const assignMutation = useAssignRole();
1288
+ const revokeMutation = useRevokeRole();
1289
+ const canManagePrivileged = useAccess(AccessKeys.rolesManage).allowed;
1290
+ const allRoles = rolesData?.roles ?? [];
1291
+ const currentRoleIds = useMemo(() => {
1292
+ const matched = allRoles.find((r) => r.slug === member.role);
1293
+ return matched ? [matched.id] : [];
1294
+ }, [allRoles, member.role]);
1295
+ const [selectedIds, setSelectedIds] = useState(currentRoleIds);
1296
+ const [saving, setSaving] = useState(false);
1297
+ useEffect(() => {
1298
+ setSelectedIds(currentRoleIds);
1299
+ }, [currentRoleIds, member.id]);
1300
+ const options = allRoles.map((r) => ({
1301
+ value: r.id,
1302
+ label: r.name + (r.is_system ? " (built-in)" : ""),
1303
+ disabled: !canManagePrivileged && (r.slug === "owner" || r.slug === "admin")
1304
+ }));
1305
+ const hasZeroRoles = selectedIds.length === 0;
1306
+ const isUnchanged = selectedIds.length === currentRoleIds.length && selectedIds.every((id) => currentRoleIds.includes(id));
1307
+ async function handleSave() {
1308
+ if (hasZeroRoles || isUnchanged) return;
1309
+ setSaving(true);
2043
1310
  try {
2044
- const credentialValue = buildCredentialValue(credentialType, fieldValues);
2045
- const updateData = { value: credentialValue };
2046
- const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
2047
- if (!validationResult.success) {
2048
- console.error("Validation failed:", validationResult.error);
2049
- return;
2050
- }
2051
- await updateMutation.mutateAsync({
2052
- credentialId: credential.id,
2053
- updates: updateData
2054
- });
2055
- onClose();
1311
+ const { adds, removes } = diffRoles(currentRoleIds, selectedIds);
1312
+ await Promise.all([
1313
+ ...adds.map((roleId) => assignMutation.mutateAsync({ membershipId: member.id, roleId })),
1314
+ ...removes.map((roleId) => revokeMutation.mutateAsync({ membershipId: member.id, roleId }))
1315
+ ]);
1316
+ showSuccessNotification("Roles updated");
1317
+ onSaved();
2056
1318
  } catch (err) {
2057
- console.error("Update failed:", err);
1319
+ showAuthError(err);
1320
+ } finally {
1321
+ setSaving(false);
2058
1322
  }
2059
- };
2060
- const handleFieldChange = (key, value) => {
2061
- setFieldValues((prev) => ({ ...prev, [key]: value }));
2062
- };
2063
- const handleNameChange = (value) => {
2064
- setCredentialName(value);
2065
- if (nameError) setNameError(null);
2066
- };
2067
- const hasFieldValues = Object.values(fieldValues).some((v) => v.trim());
2068
- return /* @__PURE__ */ jsx(CustomModal, { opened: true, onClose: handleClose, loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
2069
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
2070
- /* @__PURE__ */ jsx(IconSettings, { size: 24 }),
2071
- /* @__PURE__ */ jsx(Title, { order: 3, children: isOAuth ? "Manage OAuth Credential" : "Edit API Key" })
2072
- ] }),
2073
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
2074
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Type:" }),
2075
- /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: schema.label })
2076
- ] }),
1323
+ }
1324
+ return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1325
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Select the roles assigned to this member. Each role grants a bundle of permissions; the member's effective permissions are the union of all assigned roles." }),
2077
1326
  /* @__PURE__ */ jsx(
2078
- TextInput,
1327
+ MultiSelect,
2079
1328
  {
2080
- label: "Name",
2081
- value: credentialName,
2082
- onChange: (e) => handleNameChange(e.target.value),
2083
- disabled: isPending,
2084
- maxLength: 100,
2085
- description: "Lowercase letters, numbers, and hyphens only (e.g., gmail-dev, notion-prod)",
2086
- error: nameError
1329
+ data: options,
1330
+ value: selectedIds,
1331
+ onChange: setSelectedIds,
1332
+ placeholder: rolesLoading ? "Loading roles..." : "Select roles",
1333
+ disabled: saving || rolesLoading,
1334
+ searchable: true,
1335
+ clearable: false
2087
1336
  }
2088
1337
  ),
2089
- isOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
2090
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: 'OAuth credentials are managed by the provider. You can update the name above, or click "Reconnect" to refresh your connection and update access tokens.' }),
2091
- oauthError && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Connection Failed", children: oauthError }),
2092
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
2093
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
2094
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
2095
- /* @__PURE__ */ jsx(
2096
- Button,
2097
- {
2098
- variant: "light",
2099
- onClick: handleSaveChanges,
2100
- loading: updateMutation.isPending,
2101
- disabled: !hasMetadataChanged || isAuthorizing,
2102
- children: "Save Changes"
2103
- }
2104
- ),
2105
- /* @__PURE__ */ jsx(
2106
- Button,
2107
- {
2108
- color: "blue",
2109
- onClick: handleOAuthReconnect,
2110
- loading: isAuthorizing,
2111
- leftSection: /* @__PURE__ */ jsx(IconRefresh, { size: 16 }),
2112
- disabled: updateMutation.isPending,
2113
- children: "Reconnect OAuth"
2114
- }
2115
- )
2116
- ] })
2117
- ] })
1338
+ !canManagePrivileged && /* @__PURE__ */ jsxs(Alert, { color: "blue", variant: "light", children: [
1339
+ "Only owners (with ",
1340
+ /* @__PURE__ */ jsx("code", { children: "roles.manage" }),
1341
+ ") can grant or revoke the owner / admin system roles."
2118
1342
  ] }),
2119
- !isOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
2120
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Current values are encrypted and cannot be displayed. Enter new values below to replace them." }),
2121
- schema.fields?.map((field) => {
2122
- const Input = field.type === "text" ? TextInput : PasswordInput;
2123
- return /* @__PURE__ */ jsx(
2124
- Input,
2125
- {
2126
- label: `New ${field.label}`,
2127
- placeholder: field.placeholder || `Enter new ${field.label.toLowerCase()}`,
2128
- value: fieldValues[field.key] || "",
2129
- onChange: (e) => handleFieldChange(field.key, e.target.value),
2130
- description: field.description,
2131
- disabled: isPending
2132
- },
2133
- field.key
2134
- );
2135
- }),
2136
- updateMutation.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Update Failed", children: "Failed to update credential" }),
2137
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
2138
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
2139
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
2140
- /* @__PURE__ */ jsx(
2141
- Button,
2142
- {
2143
- variant: "light",
2144
- onClick: handleSaveChanges,
2145
- loading: updateMutation.isPending,
2146
- disabled: !hasMetadataChanged,
2147
- children: "Save Name"
2148
- }
2149
- ),
2150
- /* @__PURE__ */ jsx(Button, { onClick: handleCredentialUpdate, loading: updateMutation.isPending, disabled: !hasFieldValues, children: "Save Credential" })
2151
- ] })
2152
- ] })
1343
+ !rolesLoading && hasZeroRoles && currentRoleIds.length > 0 && /* @__PURE__ */ jsx(Text, { size: "xs", c: "red", children: "At least one role is required for an active membership." }),
1344
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", mt: "md", children: [
1345
+ /* @__PURE__ */ jsx(Button, { variant: "default", onClick: onSaved, disabled: saving, children: "Cancel" }),
1346
+ /* @__PURE__ */ jsx(
1347
+ Button,
1348
+ {
1349
+ leftSection: /* @__PURE__ */ jsx(IconCheck, { size: 14 }),
1350
+ loading: saving,
1351
+ disabled: hasZeroRoles || isUnchanged,
1352
+ onClick: handleSave,
1353
+ children: "Save roles"
1354
+ }
1355
+ )
2153
1356
  ] })
2154
- ] }) });
1357
+ ] });
2155
1358
  }
2156
- var PROVIDER_ICONS = {
2157
- "google-sheets": IconBrandGoogleDrive,
2158
- dropbox: IconBrandDropbox
2159
- };
2160
- function OAuthIntegrationsCard({ apiUrl }) {
2161
- const [selectedProviderId, setSelectedProviderId] = useState(null);
2162
- const [modalOpened, setModalOpened] = useState(false);
2163
- const { authorize, isAuthorizing, error } = useOAuthFlow({ apiUrl });
2164
- const queryClient = useQueryClient();
2165
- const handleConnect = async (credentialName) => {
2166
- if (!selectedProviderId) return;
2167
- try {
2168
- await authorize(selectedProviderId, credentialName);
2169
- setModalOpened(false);
2170
- setSelectedProviderId(null);
2171
- queryClient.invalidateQueries({ queryKey: ["credentials"] });
2172
- } catch (err) {
2173
- console.error("OAuth flow failed:", err);
2174
- }
1359
+ function transformMemberForModal(member) {
1360
+ const firstName = member.user?.firstName || "";
1361
+ const lastName = member.user?.lastName || "";
1362
+ const name = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1363
+ return {
1364
+ id: member.id,
1365
+ name,
1366
+ email: member.user?.email || "Unknown",
1367
+ role: member.role.slug
2175
1368
  };
2176
- const handleOpenModal = (providerId) => {
2177
- setSelectedProviderId(providerId);
1369
+ }
1370
+ function OrgMembersList({ orgId }) {
1371
+ const { data: members, isLoading, error } = useOrganizationMembers(orgId);
1372
+ const [selectedMember, setSelectedMember] = useState(null);
1373
+ const [modalOpened, setModalOpened] = useState(false);
1374
+ const [defaultTab, setDefaultTab] = useState("roles");
1375
+ const openModal = (member, tab) => {
1376
+ setSelectedMember(member);
1377
+ setDefaultTab(tab);
2178
1378
  setModalOpened(true);
2179
1379
  };
2180
- useEffect(() => {
2181
- const params = new URLSearchParams(window.location.search);
2182
- const requested = params.get("connect");
2183
- if (requested && OAUTH_PROVIDERS[requested]) {
2184
- handleOpenModal(requested);
2185
- params.delete("connect");
2186
- const remaining = params.toString();
2187
- const newUrl = window.location.pathname + (remaining ? `?${remaining}` : "") + window.location.hash;
2188
- window.history.replaceState({}, "", newUrl);
2189
- }
2190
- }, []);
2191
- const providers = Object.values(OAUTH_PROVIDERS);
2192
- const selectedProvider = selectedProviderId ? OAUTH_PROVIDERS[selectedProviderId] : null;
1380
+ const handleModalClose = () => {
1381
+ setModalOpened(false);
1382
+ setSelectedMember(null);
1383
+ };
1384
+ if (isLoading) {
1385
+ return /* @__PURE__ */ jsx(Center, { py: "xl", children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "sm", children: [
1386
+ /* @__PURE__ */ jsx(Loader, { size: "md" }),
1387
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Loading members..." })
1388
+ ] }) });
1389
+ }
1390
+ if (error) {
1391
+ return /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Error loading members" });
1392
+ }
1393
+ if (!members || members.length === 0) {
1394
+ return /* @__PURE__ */ jsx(EmptyState, { icon: IconUsers, title: "No members found" });
1395
+ }
2193
1396
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2194
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
2195
- /* @__PURE__ */ jsx(Group, { justify: "space-between", children: /* @__PURE__ */ jsxs("div", { children: [
2196
- /* @__PURE__ */ jsx(Title, { order: 3, children: "OAuth Integrations" }),
2197
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mt: 4, children: "Connect third-party services using OAuth 2.0" })
1397
+ /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 500, children: /* @__PURE__ */ jsxs(Table, { children: [
1398
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1399
+ /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
1400
+ /* @__PURE__ */ jsx(Table.Th, { children: "Email" }),
1401
+ /* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
1402
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1403
+ /* @__PURE__ */ jsx(Table.Th, { children: "Actions" })
2198
1404
  ] }) }),
2199
- /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, md: 3 }, children: providers.map((provider) => {
2200
- const Icon = PROVIDER_ICONS[provider.id] || IconPlug;
2201
- return /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
2202
- /* @__PURE__ */ jsx(Group, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
2203
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(Icon, { size: 20 }) }),
2204
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Text, { fw: 600, style: { fontFamily: "var(--mantine-font-family-headings)" }, children: provider.name }) })
1405
+ /* @__PURE__ */ jsx(Table.Tbody, { children: members.map((member) => {
1406
+ const firstName = member.user?.firstName || "";
1407
+ const lastName = member.user?.lastName || "";
1408
+ const displayName = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1409
+ return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1410
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: displayName }) }),
1411
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.user?.email || "Unknown" }) }),
1412
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(
1413
+ RoleBadge,
1414
+ {
1415
+ slug: member.role.slug,
1416
+ name: member.role.slug.charAt(0).toUpperCase() + member.role.slug.slice(1),
1417
+ isSystem: true
1418
+ }
1419
+ ) }),
1420
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1421
+ /* @__PURE__ */ jsx(MembershipStatusBadge, { status: member.status }),
1422
+ /* @__PURE__ */ jsx(ProvisioningStateBadge, { state: member.provisioningState })
2205
1423
  ] }) }),
2206
- /* @__PURE__ */ jsx(Button, { variant: "filled", size: "sm", onClick: () => handleOpenModal(provider.id), fullWidth: true, children: "Connect" })
2207
- ] }) }, provider.id);
1424
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Tooltip, { label: "Edit member access", withArrow: true, children: /* @__PURE__ */ jsx(ActionIcon, { variant: "light", size: "sm", color: "gray", onClick: () => openModal(member, "roles"), children: /* @__PURE__ */ jsx(IconPencil, { size: 14 }) }) }) })
1425
+ ] }, member.id);
2208
1426
  }) })
2209
1427
  ] }) }),
2210
1428
  /* @__PURE__ */ jsx(
2211
- OAuthConnectModal,
1429
+ MemberAccessModal,
2212
1430
  {
2213
1431
  opened: modalOpened,
2214
- onClose: () => {
2215
- setModalOpened(false);
2216
- setSelectedProviderId(null);
2217
- },
2218
- provider: selectedProvider,
2219
- onConnect: handleConnect,
2220
- error,
2221
- isLoading: isAuthorizing
1432
+ onClose: handleModalClose,
1433
+ orgId,
1434
+ member: selectedMember ? transformMemberForModal(selectedMember) : null,
1435
+ defaultTab
2222
1436
  }
2223
1437
  )
2224
1438
  ] });
2225
1439
  }
1440
+ function OrganizationSettings({
1441
+ user,
1442
+ currentMembership,
1443
+ organizationId,
1444
+ isOrgAdmin
1445
+ }) {
1446
+ return /* @__PURE__ */ jsxs(Stack, { children: [
1447
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Organization Settings", caption: "Manage your current organization" }),
1448
+ currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1449
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1450
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconBuilding, { size: 18 }) }),
1451
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1452
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Current Organization" }),
1453
+ /* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: currentMembership.organization.name })
1454
+ ] }),
1455
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1456
+ /* @__PURE__ */ jsx(Code, { children: currentMembership.organization.id }),
1457
+ /* @__PURE__ */ jsx(CopyButton, { value: currentMembership.organization.id, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
1458
+ ActionIcon,
1459
+ {
1460
+ color: copied ? "teal" : "gray",
1461
+ variant: copied ? "filled" : "subtle",
1462
+ onClick: copy,
1463
+ size: "sm",
1464
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1465
+ }
1466
+ ) }) })
1467
+ ] })
1468
+ ] }),
1469
+ user && /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1470
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(IconUsers, { size: 18 }) }),
1471
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1472
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Signed in as" }),
1473
+ /* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
1474
+ user.firstName,
1475
+ " ",
1476
+ user.lastName,
1477
+ " \u2022 ",
1478
+ user.email
1479
+ ] })
1480
+ ] }),
1481
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1482
+ /* @__PURE__ */ jsx(Code, { children: currentMembership.userId }),
1483
+ /* @__PURE__ */ jsx(CopyButton, { value: currentMembership.userId, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy User ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
1484
+ ActionIcon,
1485
+ {
1486
+ color: copied ? "teal" : "gray",
1487
+ variant: copied ? "filled" : "subtle",
1488
+ onClick: copy,
1489
+ size: "sm",
1490
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1491
+ }
1492
+ ) }) })
1493
+ ] })
1494
+ ] })
1495
+ ] }) }),
1496
+ isOrgAdmin && organizationId && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
1497
+ /* @__PURE__ */ jsx(
1498
+ CardHeader,
1499
+ {
1500
+ icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
1501
+ title: "Manage Members",
1502
+ subtitle: "Configure feature access for organization members."
1503
+ }
1504
+ ),
1505
+ /* @__PURE__ */ jsx(OrgMembersList, { orgId: organizationId })
1506
+ ] }) })
1507
+ ] });
1508
+ }
1509
+ function capitalize(value) {
1510
+ return value.charAt(0).toUpperCase() + value.slice(1);
1511
+ }
1512
+ function MyRolesPage({ onManageMembers, onManageOrganizationRoles }) {
1513
+ const { currentMembership } = useOrganization();
1514
+ const orgName = currentMembership?.organization?.name ?? "this organization";
1515
+ const access = useAccess("platform");
1516
+ const { permissions, isReady: permissionsReady, isPlatformAdmin } = access;
1517
+ const catalogQuery = usePermissionCatalog();
1518
+ const catalogMap = new Map(
1519
+ (catalogQuery.data?.permissions ?? []).map((permission) => [permission.key, permission.description])
1520
+ );
1521
+ const roleSlug = currentMembership?.role?.slug ?? null;
1522
+ const isLoading = !permissionsReady || catalogQuery.isLoading;
1523
+ const showManagePanel = Boolean(onManageMembers || onManageOrganizationRoles);
1524
+ const groupedPermissions = permissions.reduce((groups, key) => {
1525
+ const domain = key.split(".")[0] ?? "general";
1526
+ groups[domain] ??= [];
1527
+ groups[domain].push(key);
1528
+ return groups;
1529
+ }, {});
1530
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1531
+ /* @__PURE__ */ jsx(
1532
+ PageTitleCaption,
1533
+ {
1534
+ title: "My Roles",
1535
+ caption: `Roles assigned to you in ${orgName} and what each one lets you do.`
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
1539
+ /* @__PURE__ */ jsx(
1540
+ CardHeader,
1541
+ {
1542
+ icon: /* @__PURE__ */ jsx(IconShieldLock, { size: 18 }),
1543
+ title: "Your roles",
1544
+ subtitle: `Roles assigned to your membership in ${orgName}.`
1545
+ }
1546
+ ),
1547
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1548
+ roleSlug ? /* @__PURE__ */ jsx(RoleBadge, { slug: roleSlug, name: capitalize(roleSlug), isSystem: true }) : /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "No roles assigned yet." }),
1549
+ isPlatformAdmin && /* @__PURE__ */ jsx(Badge, { color: "grape", variant: "light", leftSection: /* @__PURE__ */ jsx(IconStar, { size: 12 }), children: "Platform admin" })
1550
+ ] }),
1551
+ isPlatformAdmin && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Platform admin bypasses every org-level permission check; you have full access regardless of role." })
1552
+ ] }) }),
1553
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
1554
+ /* @__PURE__ */ jsx(
1555
+ CardHeader,
1556
+ {
1557
+ icon: /* @__PURE__ */ jsx(IconCheck, { size: 18 }),
1558
+ title: "What you can do",
1559
+ subtitle: isPlatformAdmin ? "Showing all platform permissions (platform admin bypass)." : "Permissions granted by your assigned roles."
1560
+ }
1561
+ ),
1562
+ isLoading ? /* @__PURE__ */ jsx(Center, { children: /* @__PURE__ */ jsx(Loader, {}) }) : permissions.length === 0 ? /* @__PURE__ */ jsx(Alert, { color: "yellow", children: "You don't have any permissions in this organization yet. Ask an admin to assign you a role." }) : /* @__PURE__ */ jsx(Stack, { gap: "md", children: Object.entries(groupedPermissions).map(([domain, keys]) => /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
1563
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, children: capitalize(domain) }),
1564
+ keys.map((key) => /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1565
+ /* @__PURE__ */ jsx(IconCheck, { size: 14, color: "var(--color-success)" }),
1566
+ /* @__PURE__ */ jsx(Code, { children: key }),
1567
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: catalogMap.get(key) ?? "" })
1568
+ ] }, key))
1569
+ ] }, domain)) })
1570
+ ] }) }),
1571
+ showManagePanel && /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.membersManage, children: /* @__PURE__ */ jsx(Paper, { withBorder: true, p: "md", children: /* @__PURE__ */ jsxs(Stack, { children: [
1572
+ /* @__PURE__ */ jsx(
1573
+ CardHeader,
1574
+ {
1575
+ icon: /* @__PURE__ */ jsx(IconUserCog, { size: 18 }),
1576
+ title: "Manage roles & members",
1577
+ subtitle: "Assign roles to members, create custom roles, and edit permission sets."
1578
+ }
1579
+ ),
1580
+ /* @__PURE__ */ jsxs(Group, { children: [
1581
+ onManageMembers ? /* @__PURE__ */ jsx(Button, { variant: "light", leftSection: /* @__PURE__ */ jsx(IconUsersGroup, { size: 14 }), onClick: onManageMembers, children: "Members" }) : null,
1582
+ onManageOrganizationRoles ? /* @__PURE__ */ jsx(AccessGuard, { accessKey: AccessKeys.organizationManage, children: /* @__PURE__ */ jsx(
1583
+ Button,
1584
+ {
1585
+ variant: "light",
1586
+ leftSection: /* @__PURE__ */ jsx(IconShieldLock, { size: 14 }),
1587
+ onClick: onManageOrganizationRoles,
1588
+ children: "Organization roles"
1589
+ }
1590
+ ) }) : null
1591
+ ] })
1592
+ ] }) }) })
1593
+ ] });
1594
+ }
2226
1595
 
2227
- export { AccountSettings, AppearanceSettings, CreateCredentialModal, CreateWebhookEndpointModal, CredentialList, CredentialSettings, EditCredentialModal, EditWebhookEndpointModal, MemberAccessModal, MembershipStatusBadge, MyRolesPage, OAuthConnectModal, OAuthIntegrationsCard, OrgMembersList, OrganizationMembershipsList, OrganizationSettings, ProvisioningStateBadge, WebhookEndpointList, WebhookEndpointSettings, WebhookUrlDisplayModal, showAuthError };
1596
+ export { AccountSettings, AppearanceSettings, CreateWebhookEndpointModal, EditWebhookEndpointModal, MemberAccessModal, MembershipStatusBadge, MyRolesPage, OAuthConnectModal, OAuthIntegrationsCard, OrgMembersList, OrganizationMembershipsList, OrganizationSettings, ProvisioningStateBadge, WebhookEndpointList, WebhookEndpointSettings, WebhookUrlDisplayModal, showAuthError };