@elevasis/ui 1.26.0 → 1.27.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 (53) hide show
  1. package/dist/charts/index.js +2 -2
  2. package/dist/{chunk-AWT255UH.js → chunk-2IJCM3VQ.js} +37 -37
  3. package/dist/chunk-5COLSYBE.js +199 -0
  4. package/dist/{chunk-RMPXGBNI.js → chunk-5JSR6TL5.js} +2 -2
  5. package/dist/chunk-BAGYETKM.js +635 -0
  6. package/dist/{chunk-L3GVDMCA.js → chunk-C27LLJM6.js} +3 -195
  7. package/dist/{chunk-O4UB5DQQ.js → chunk-F2J7675J.js} +1 -1
  8. package/dist/chunk-ITCEULI5.js +238 -0
  9. package/dist/{chunk-4WKWLFBZ.js → chunk-P5EWG45B.js} +1 -1
  10. package/dist/{chunk-BS4J2LAW.js → chunk-QTD5HPKD.js} +1 -1
  11. package/dist/{chunk-ZVJKIJFG.js → chunk-RCQPWA5X.js} +13 -42
  12. package/dist/chunk-TLAIQC7B.js +6382 -0
  13. package/dist/{chunk-FEZZ3IDU.js → chunk-TXPUIHX2.js} +10 -10
  14. package/dist/{chunk-L4XXM55J.js → chunk-W4VYXIN7.js} +142 -3
  15. package/dist/chunk-WJ7W7JU4.js +2115 -0
  16. package/dist/{chunk-YNGQ7U5H.js → chunk-WLNEJ6JJ.js} +2 -2
  17. package/dist/{chunk-4INR75ZS.js → chunk-Y2SYGFRF.js} +589 -65
  18. package/dist/components/index.d.ts +333 -73
  19. package/dist/components/index.js +838 -686
  20. package/dist/features/auth/index.d.ts +125 -0
  21. package/dist/features/auth/index.js +2 -2
  22. package/dist/features/dashboard/index.d.ts +28 -2
  23. package/dist/features/dashboard/index.js +21 -635
  24. package/dist/features/monitoring/index.d.ts +28 -1
  25. package/dist/features/monitoring/index.js +19 -529
  26. package/dist/features/operations/index.d.ts +51 -8
  27. package/dist/features/operations/index.js +25 -3760
  28. package/dist/features/settings/index.d.ts +153 -1
  29. package/dist/features/settings/index.js +19 -1438
  30. package/dist/hooks/index.d.ts +262 -25
  31. package/dist/hooks/index.js +12 -8
  32. package/dist/hooks/published.d.ts +137 -25
  33. package/dist/hooks/published.js +11 -7
  34. package/dist/index.d.ts +310 -28
  35. package/dist/index.js +12 -11
  36. package/dist/initialization/index.d.ts +125 -0
  37. package/dist/layout/index.d.ts +2 -0
  38. package/dist/layout/index.js +6 -5
  39. package/dist/organization/index.js +1 -2
  40. package/dist/profile/index.d.ts +125 -0
  41. package/dist/provider/index.d.ts +48 -3
  42. package/dist/provider/index.js +10 -4
  43. package/dist/provider/published.d.ts +48 -3
  44. package/dist/provider/published.js +8 -2
  45. package/dist/supabase/index.d.ts +242 -0
  46. package/dist/theme/index.js +2 -2
  47. package/dist/types/index.d.ts +126 -1
  48. package/package.json +1 -1
  49. package/dist/chunk-LR4WVA7W.js +0 -682
  50. package/dist/chunk-R7WLWGPO.js +0 -126
  51. package/dist/chunk-TCKIAHDC.js +0 -2626
  52. package/dist/chunk-V7XHGJQZ.js +0 -145
  53. package/dist/{chunk-WWEMNIHW.js → chunk-YYBM5LNJ.js} +1 -1
@@ -0,0 +1,2115 @@
1
+ import { FilterBar } from './chunk-PDHTXPSF.js';
2
+ import { CustomModal } from './chunk-GBMNCNHX.js';
3
+ import { ListSkeleton, EmptyState, PageTitleCaption, CardHeader, APIErrorAlert, StatCard } from './chunk-MCA6LOGM.js';
4
+ import { AppShellLoader } from './chunk-YYBM5LNJ.js';
5
+ import { useAvailablePresets } from './chunk-QTD5HPKD.js';
6
+ import { useDeleteCredential, useCreateCredential, useCredentials, MEMBERSHIP_STATUS_COLORS, transformMembershipToTableRow, useUserMemberships, useUpdateWebhookEndpoint, useDeleteWebhookEndpoint, useCreateWebhookEndpoint, useListWebhookEndpoints, useUpdateMemberConfig, useOrganizationMembers, useUpdateCredential, CredentialSchemas } from './chunk-WLNEJ6JJ.js';
7
+ import { useResources, showErrorNotification } from './chunk-RCQPWA5X.js';
8
+ import { formatDateTime, OAUTH_POPUP_CHECK_INTERVAL, OAUTH_FLOW_TIMEOUT } from './chunk-IOKL7BKE.js';
9
+ import { useInitialization } from './chunk-TUXTSEAF.js';
10
+ import { useElevasisServices } from './chunk-QEPXAWE2.js';
11
+ import { Table, Group, Text, Tooltip, ActionIcon, Stack, Title, Button, Select, TextInput, Alert, PasswordInput, Anchor, Paper, Card, Switch, Badge, Center, Loader, Box, Code, CopyButton, ThemeIcon, useMantineColorScheme, SimpleGrid, UnstyledButton, Divider, Textarea } from '@mantine/core';
12
+ import { IconSettings, IconKey, IconCalendar, IconPencil, IconTrash, IconAlertTriangle, IconInfoCircle, IconExclamationMark, IconPlus, IconAlertCircle, IconUsers, IconSearch, IconBuilding, IconEdit, IconUserX, IconUserCheck, IconWebhook, IconCheck, IconCopy, IconUser, IconMail, IconRefresh, IconPalette, IconSun, IconEye, IconSparkles, IconTrendingUp, IconClock, IconPlayerPause, IconPlayerPlay, IconActivity, IconBrandDropbox, IconRocket, IconBrandGithub, IconBrandGmail, IconBrandGoogleDrive, IconPlug, IconBrandSlack, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
13
+ import { z } from 'zod';
14
+ import { useState, useEffect, useRef, useMemo, useCallback } from 'react';
15
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
16
+ import { useForm } from '@mantine/form';
17
+ import { notifications } from '@mantine/notifications';
18
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
19
+ import { create } from 'zustand';
20
+
21
+ // ../core/src/integrations/oauth/provider-registry.ts
22
+ var OAUTH_PROVIDERS = {
23
+ "google-sheets": {
24
+ id: "google-sheets",
25
+ name: "Google Sheets",
26
+ authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
27
+ tokenUrl: "https://oauth2.googleapis.com/token",
28
+ authParams: {
29
+ access_type: "offline",
30
+ // Required for refresh token
31
+ prompt: "consent"
32
+ // Force consent to get refresh token on reconnect
33
+ },
34
+ tokenExchange: "form-encoded",
35
+ scopes: ["https://www.googleapis.com/auth/spreadsheets"]
36
+ },
37
+ dropbox: {
38
+ id: "dropbox",
39
+ name: "Dropbox",
40
+ authUrl: "https://www.dropbox.com/oauth2/authorize",
41
+ tokenUrl: "https://api.dropboxapi.com/oauth2/token",
42
+ authParams: {
43
+ token_access_type: "offline"
44
+ // Required for refresh token
45
+ },
46
+ tokenExchange: "form-encoded",
47
+ scopes: ["files.content.write", "files.content.read", "files.metadata.write"]
48
+ }
49
+ };
50
+ var CredentialFieldSchema = z.object({
51
+ key: z.string(),
52
+ label: z.string(),
53
+ type: z.enum(["password", "text"]),
54
+ required: z.boolean(),
55
+ placeholder: z.string().optional(),
56
+ description: z.string().optional()
57
+ });
58
+ var CredentialSchemaZod = z.object({
59
+ type: z.string(),
60
+ label: z.string(),
61
+ description: z.string(),
62
+ fields: z.array(CredentialFieldSchema).optional(),
63
+ docsUrl: z.string().url().optional(),
64
+ nameSuggestions: z.array(z.string()),
65
+ oauthProvider: z.string().optional()
66
+ });
67
+ var CREDENTIAL_SCHEMAS = {
68
+ "google-sheets": {
69
+ type: "oauth",
70
+ label: "Google Sheets",
71
+ description: "Google Sheets OAuth integration",
72
+ oauthProvider: "google-sheets",
73
+ nameSuggestions: ["google-sheets-prod", "google-sheets-dev", "sheets-workspace"]
74
+ },
75
+ dropbox: {
76
+ type: "oauth",
77
+ label: "Dropbox",
78
+ description: "Dropbox OAuth integration",
79
+ oauthProvider: "dropbox",
80
+ nameSuggestions: ["dropbox-prod", "dropbox-dev", "elevasis-dropbox"]
81
+ },
82
+ oauth: {
83
+ type: "oauth",
84
+ label: "OAuth",
85
+ description: "Generic OAuth credential",
86
+ nameSuggestions: []
87
+ },
88
+ "api-key": {
89
+ type: "single_field",
90
+ label: "API Key",
91
+ description: "Single-field API key credential",
92
+ fields: [
93
+ {
94
+ key: "apiKey",
95
+ label: "API Key",
96
+ type: "password",
97
+ required: true,
98
+ placeholder: "Enter your API key",
99
+ description: "API key for the service"
100
+ }
101
+ ],
102
+ nameSuggestions: ["service-prod", "service-dev", "api-key"]
103
+ },
104
+ "webhook-secret": {
105
+ type: "single_field",
106
+ label: "Webhook Secret",
107
+ description: "Webhook signing secret for signature validation",
108
+ fields: [
109
+ {
110
+ key: "signingSecret",
111
+ label: "Signing Secret",
112
+ type: "password",
113
+ required: true,
114
+ placeholder: "whsec_...",
115
+ description: "Webhook signing secret from provider dashboard"
116
+ }
117
+ ],
118
+ nameSuggestions: ["my-org-cal-com-webhook", "my-org-stripe-webhook", "my-org-signature-api-webhook"]
119
+ },
120
+ "api-key-secret": {
121
+ type: "api-key-secret",
122
+ label: "API Key + Secret",
123
+ description: "API key and secret pair authentication",
124
+ fields: [
125
+ {
126
+ key: "apiKey",
127
+ label: "API Key",
128
+ type: "password",
129
+ required: true
130
+ },
131
+ {
132
+ key: "apiSecret",
133
+ label: "API Secret",
134
+ type: "password",
135
+ required: true
136
+ }
137
+ ],
138
+ nameSuggestions: []
139
+ }
140
+ };
141
+ function getCredentialSchema(type) {
142
+ const schema = CREDENTIAL_SCHEMAS[type] || CREDENTIAL_SCHEMAS["api-key"];
143
+ return CredentialSchemaZod.parse(schema);
144
+ }
145
+
146
+ // ../core/src/integrations/credentials/utils.ts
147
+ function buildCredentialValue(type, formValues) {
148
+ const schema = getCredentialSchema(type);
149
+ if (schema.type === "oauth") {
150
+ throw new Error(`buildCredentialValue should not be used for OAuth type: ${type}`);
151
+ }
152
+ const value = {};
153
+ if (schema.fields) {
154
+ for (const field of schema.fields) {
155
+ const fieldValue = formValues[field.key];
156
+ if (fieldValue !== void 0 && fieldValue !== null) {
157
+ value[field.key] = fieldValue.trim();
158
+ }
159
+ }
160
+ }
161
+ return value;
162
+ }
163
+ function CredentialList({ credentials, isLoading, onEdit }) {
164
+ const [deleteCredential, setDeleteCredential] = useState(null);
165
+ const deleteMutation = useDeleteCredential();
166
+ const handleDelete = () => {
167
+ if (deleteCredential) {
168
+ deleteMutation.mutate(deleteCredential.id);
169
+ setDeleteCredential(null);
170
+ }
171
+ };
172
+ const handleCloseDelete = () => {
173
+ if (!deleteMutation.isPending) {
174
+ setDeleteCredential(null);
175
+ }
176
+ };
177
+ if (isLoading) {
178
+ return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 60 });
179
+ }
180
+ if (credentials.length === 0) {
181
+ return /* @__PURE__ */ jsx(
182
+ EmptyState,
183
+ {
184
+ icon: IconKey,
185
+ title: "No credentials yet",
186
+ description: "Create your first credential to enable external service integrations"
187
+ }
188
+ );
189
+ }
190
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
191
+ /* @__PURE__ */ jsxs(Table, { children: [
192
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
193
+ /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
194
+ /* @__PURE__ */ jsx(Table.Th, { children: "Created" }),
195
+ /* @__PURE__ */ jsx(Table.Th, { w: 100 })
196
+ ] }) }),
197
+ /* @__PURE__ */ jsx(Table.Tbody, { children: credentials.map((credential) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
198
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
199
+ /* @__PURE__ */ jsx(IconKey, { size: 16, style: { opacity: 0.6 } }),
200
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: credential.name })
201
+ ] }) }),
202
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
203
+ /* @__PURE__ */ jsx(IconCalendar, { size: 14, style: { opacity: 0.5 } }),
204
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(credential.createdAt) })
205
+ ] }) }),
206
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
207
+ onEdit && /* @__PURE__ */ jsx(Tooltip, { label: "Edit credential", children: /* @__PURE__ */ jsx(
208
+ ActionIcon,
209
+ {
210
+ variant: "subtle",
211
+ color: "gray",
212
+ onClick: () => onEdit(credential),
213
+ disabled: deleteMutation.isPending,
214
+ children: /* @__PURE__ */ jsx(IconPencil, { size: 16 })
215
+ }
216
+ ) }),
217
+ /* @__PURE__ */ jsx(Tooltip, { label: "Delete credential", position: "left", children: /* @__PURE__ */ jsx(
218
+ ActionIcon,
219
+ {
220
+ variant: "subtle",
221
+ color: "red",
222
+ onClick: () => setDeleteCredential(credential),
223
+ disabled: deleteMutation.isPending,
224
+ children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
225
+ }
226
+ ) })
227
+ ] }) })
228
+ ] }, credential.id)) })
229
+ ] }),
230
+ /* @__PURE__ */ jsx(CustomModal, { opened: !!deleteCredential, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
231
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
232
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
233
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Credential" })
234
+ ] }),
235
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
236
+ "Are you sure you want to delete",
237
+ " ",
238
+ /* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
239
+ '"',
240
+ deleteCredential?.name,
241
+ '"'
242
+ ] }),
243
+ "?"
244
+ ] }),
245
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "This action cannot be undone. Any agents or tools using this credential will stop working immediately." }),
246
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
247
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
248
+ /* @__PURE__ */ jsx(
249
+ Button,
250
+ {
251
+ color: "red",
252
+ onClick: handleDelete,
253
+ loading: deleteMutation.isPending,
254
+ leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
255
+ children: "Delete Credential"
256
+ }
257
+ )
258
+ ] })
259
+ ] }) })
260
+ ] });
261
+ }
262
+ function CreateCredentialModal({ opened, onClose, onSuccess }) {
263
+ const createCredential = useCreateCredential();
264
+ const [selectedType, setSelectedType] = useState("api-key");
265
+ const [schema, setSchema] = useState(getCredentialSchema("api-key"));
266
+ useEffect(() => {
267
+ setSchema(getCredentialSchema(selectedType));
268
+ }, [selectedType]);
269
+ const getInitialValues = (currentSchema) => {
270
+ const values = { name: "" };
271
+ if (currentSchema.fields) {
272
+ currentSchema.fields.forEach((field) => {
273
+ values[field.key] = "";
274
+ });
275
+ }
276
+ return values;
277
+ };
278
+ const getValidation = (currentSchema) => {
279
+ const validation = {
280
+ name: (value) => {
281
+ const result = CredentialSchemas.CreateRequest.shape.name.safeParse(value);
282
+ return result.success ? null : result.error.issues[0]?.message || "Invalid credential name";
283
+ }
284
+ };
285
+ if (currentSchema.fields) {
286
+ currentSchema.fields.forEach((field) => {
287
+ if (field.required) {
288
+ validation[field.key] = (value) => !value.trim() ? `${field.label} is required` : null;
289
+ }
290
+ });
291
+ }
292
+ return validation;
293
+ };
294
+ const form = useForm({
295
+ initialValues: getInitialValues(schema),
296
+ validate: getValidation(schema)
297
+ });
298
+ useEffect(() => {
299
+ const newValues = getInitialValues(schema);
300
+ form.setValues(newValues);
301
+ form.setFieldValue("name", form.values.name || "");
302
+ form.resetDirty();
303
+ }, [selectedType]);
304
+ const handleSubmit = async (values) => {
305
+ if (schema.type === "oauth") {
306
+ console.error("OAuth credentials cannot be created via this modal");
307
+ return;
308
+ }
309
+ const data = {
310
+ name: values.name.trim(),
311
+ type: selectedType,
312
+ value: buildCredentialValue(selectedType, values)
313
+ };
314
+ const validationResult = CredentialSchemas.CreateRequest.safeParse(data);
315
+ if (!validationResult.success) {
316
+ console.error("Validation failed:", validationResult.error);
317
+ return;
318
+ }
319
+ try {
320
+ const result = await createCredential.mutateAsync(data);
321
+ form.reset();
322
+ setSelectedType("api-key");
323
+ onSuccess(result);
324
+ } catch (error) {
325
+ console.error("Failed to create credential:", error);
326
+ }
327
+ };
328
+ const handleClose = () => {
329
+ if (!createCredential.isPending) {
330
+ form.reset();
331
+ setSelectedType("api-key");
332
+ onClose();
333
+ }
334
+ };
335
+ 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: [
336
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Create New Credential" }),
337
+ /* @__PURE__ */ jsx(
338
+ Select,
339
+ {
340
+ label: "Credential Type",
341
+ description: "Select the integration type",
342
+ placeholder: "Select credential type",
343
+ data: Object.entries(CREDENTIAL_SCHEMAS).filter(([_, s]) => s.type !== "oauth").map(([key, s]) => ({ value: key, label: s.label })),
344
+ value: selectedType,
345
+ onChange: (value) => setSelectedType(value || "api-key"),
346
+ required: true,
347
+ disabled: createCredential.isPending
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsx(
351
+ TextInput,
352
+ {
353
+ label: "Credential Name",
354
+ description: schema.nameSuggestions.length > 0 ? `Suggested: ${schema.nameSuggestions.join(", ")}` : "Lowercase letters, numbers, and hyphens only (e.g., service-dev, service-prod)",
355
+ placeholder: schema.nameSuggestions[0] || "e.g., service-prod",
356
+ leftSection: /* @__PURE__ */ jsx(IconKey, { size: 16 }),
357
+ maxLength: 100,
358
+ required: true,
359
+ ...form.getInputProps("name"),
360
+ disabled: createCredential.isPending
361
+ }
362
+ ),
363
+ schema.type === "oauth" ? /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconInfoCircle, { size: 16 }), color: "blue", children: [
364
+ schema.label,
365
+ " uses OAuth authentication. Please go to the OAuth Integrations page to connect your",
366
+ " ",
367
+ schema.label,
368
+ " account."
369
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
370
+ schema.fields?.map((field) => /* @__PURE__ */ jsx(
371
+ PasswordInput,
372
+ {
373
+ label: field.label,
374
+ description: field.description,
375
+ placeholder: field.placeholder,
376
+ required: field.required,
377
+ value: form.values[field.key] || "",
378
+ onChange: (e) => form.setFieldValue(field.key, e.target.value),
379
+ error: form.errors[field.key],
380
+ disabled: createCredential.isPending
381
+ },
382
+ field.key
383
+ )),
384
+ schema.docsUrl && /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
385
+ "Get credentials from",
386
+ " ",
387
+ /* @__PURE__ */ jsx(Anchor, { href: schema.docsUrl, target: "_blank", rel: "noopener noreferrer", children: schema.docsUrl })
388
+ ] })
389
+ ] }),
390
+ 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" }),
391
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
392
+ /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createCredential.isPending, children: "Cancel" }),
393
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: createCredential.isPending, leftSection: /* @__PURE__ */ jsx(IconKey, { size: 16 }), children: "Create Credential" })
394
+ ] })
395
+ ] }) }) });
396
+ }
397
+ function CredentialSettings({ oauthSection }) {
398
+ const { organizationReady } = useInitialization();
399
+ const [showCreateModal, setShowCreateModal] = useState(false);
400
+ const { data: credentials = [], isLoading } = useCredentials();
401
+ if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
402
+ const handleCredentialCreated = (result) => {
403
+ console.log("Credential created:", result);
404
+ setShowCreateModal(false);
405
+ };
406
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
407
+ /* @__PURE__ */ jsx(
408
+ PageTitleCaption,
409
+ {
410
+ title: "Organization Credentials",
411
+ caption: "Manage encrypted credentials for third-party service integrations"
412
+ }
413
+ ),
414
+ oauthSection,
415
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
416
+ /* @__PURE__ */ jsx(
417
+ CardHeader,
418
+ {
419
+ icon: /* @__PURE__ */ jsx(IconKey, { size: 18 }),
420
+ title: "Credentials",
421
+ subtitle: "Configured API keys and credentials",
422
+ rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Credential" })
423
+ }
424
+ ),
425
+ /* @__PURE__ */ jsx(CredentialList, { credentials, isLoading })
426
+ ] }),
427
+ /* @__PURE__ */ jsx(
428
+ CreateCredentialModal,
429
+ {
430
+ opened: showCreateModal,
431
+ onClose: () => setShowCreateModal(false),
432
+ onSuccess: handleCredentialCreated
433
+ }
434
+ )
435
+ ] });
436
+ }
437
+ function OAuthConnectModal({ opened, onClose, provider, onConnect, error, isLoading }) {
438
+ const [credentialName, setCredentialName] = useState("");
439
+ useEffect(() => {
440
+ if (opened && provider) {
441
+ setCredentialName(`${provider.id}-prod`);
442
+ }
443
+ }, [opened, provider]);
444
+ useEffect(() => {
445
+ if (!opened) {
446
+ setCredentialName("");
447
+ }
448
+ }, [opened]);
449
+ const handleConnect = async () => {
450
+ if (!credentialName.trim() || !provider) return;
451
+ await onConnect(credentialName);
452
+ };
453
+ const handleClose = () => {
454
+ if (!isLoading) {
455
+ onClose();
456
+ }
457
+ };
458
+ return /* @__PURE__ */ jsx(CustomModal, { opened: opened && !!provider, onClose: handleClose, size: "md", loading: isLoading, children: provider && /* @__PURE__ */ jsxs(Stack, { children: [
459
+ /* @__PURE__ */ jsxs(Title, { order: 3, children: [
460
+ "Connect ",
461
+ provider.name
462
+ ] }),
463
+ /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
464
+ "Authorize Elevasis to access your ",
465
+ provider.name,
466
+ " account"
467
+ ] }),
468
+ provider.scopes && provider.scopes.length > 0 && /* @__PURE__ */ jsxs(Text, { size: "sm", c: "dimmed", children: [
469
+ "Permissions requested: ",
470
+ provider.scopes.join(", ")
471
+ ] }),
472
+ error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Connection Failed", children: error }),
473
+ /* @__PURE__ */ jsx(
474
+ TextInput,
475
+ {
476
+ label: "Credential Name",
477
+ placeholder: `${provider.id}-prod`,
478
+ value: credentialName,
479
+ onChange: (e) => setCredentialName(e.target.value),
480
+ required: true,
481
+ disabled: isLoading,
482
+ description: "Use naming convention to indicate environment (e.g., notion-dev, slack-prod)"
483
+ }
484
+ ),
485
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", mt: "md", children: [
486
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isLoading, children: "Cancel" }),
487
+ /* @__PURE__ */ jsxs(Button, { onClick: handleConnect, loading: isLoading, disabled: !credentialName.trim(), children: [
488
+ "Connect ",
489
+ provider.name
490
+ ] })
491
+ ] })
492
+ ] }) });
493
+ }
494
+ var FEATURES = [
495
+ { key: "acquisition", label: "Acquisition" },
496
+ { key: "calibration", label: "Calibration" },
497
+ { key: "seo", label: "SEO" }
498
+ ];
499
+ function MembershipFeaturePanel({
500
+ currentConfig,
501
+ onConfigChange,
502
+ disabled = false
503
+ }) {
504
+ const handleFeatureToggle = (featureKey, enabled) => {
505
+ const newConfig = {
506
+ ...currentConfig,
507
+ features: {
508
+ ...currentConfig?.features,
509
+ [featureKey]: enabled
510
+ }
511
+ };
512
+ onConfigChange(newConfig);
513
+ };
514
+ return /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
515
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Toggle features this member can access." }),
516
+ FEATURES.map(({ key, label }) => {
517
+ const isEnabled = currentConfig?.features?.[key] !== false;
518
+ return /* @__PURE__ */ jsxs(Group, { justify: "space-between", wrap: "nowrap", children: [
519
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: label }),
520
+ /* @__PURE__ */ jsx(
521
+ Switch,
522
+ {
523
+ checked: isEnabled,
524
+ onChange: (e) => handleFeatureToggle(key, e.currentTarget.checked),
525
+ disabled,
526
+ size: "md"
527
+ }
528
+ )
529
+ ] }, key);
530
+ })
531
+ ] }) });
532
+ }
533
+ function MembershipStatusBadge({ status, size = "sm", variant = "light" }) {
534
+ const getStatusLabel = (status2) => {
535
+ switch (status2) {
536
+ case "active":
537
+ return "Active";
538
+ case "inactive":
539
+ return "Inactive";
540
+ default:
541
+ return status2;
542
+ }
543
+ };
544
+ return /* @__PURE__ */ jsx(Badge, { color: MEMBERSHIP_STATUS_COLORS[status], size, variant, children: getStatusLabel(status) });
545
+ }
546
+ function OrganizationMembershipsList({
547
+ memberships,
548
+ loading,
549
+ error,
550
+ onEditRole,
551
+ onLeaveOrganization
552
+ }) {
553
+ const [searchTerm, setSearchTerm] = useState("");
554
+ const [statusFilter, setStatusFilter] = useState("");
555
+ const tableRows = memberships?.map(transformMembershipToTableRow) || [];
556
+ const filteredRows = tableRows.filter((row) => {
557
+ const matchesSearch = row.organizationName.toLowerCase().includes(searchTerm.toLowerCase()) || row.role.toLowerCase().includes(searchTerm.toLowerCase());
558
+ const matchesStatus = !statusFilter || row.status === statusFilter;
559
+ return matchesSearch && matchesStatus;
560
+ });
561
+ if (error) {
562
+ return /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(APIErrorAlert, { error, title: "Failed to load memberships" }) });
563
+ }
564
+ return /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
565
+ /* @__PURE__ */ jsx(
566
+ CardHeader,
567
+ {
568
+ icon: /* @__PURE__ */ jsx(IconUsers, { size: 18 }),
569
+ title: `Your Organizations (${loading ? "..." : filteredRows.length})`,
570
+ subtitle: loading ? "Loading memberships..." : `Showing ${filteredRows.length} ${filteredRows.length === 1 ? "membership" : "memberships"}`
571
+ }
572
+ ),
573
+ /* @__PURE__ */ jsxs(Stack, { children: [
574
+ /* @__PURE__ */ jsxs(FilterBar, { children: [
575
+ /* @__PURE__ */ jsx(
576
+ TextInput,
577
+ {
578
+ placeholder: "Search organizations or roles...",
579
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
580
+ value: searchTerm,
581
+ onChange: (event) => setSearchTerm(event.currentTarget.value),
582
+ style: { flex: 1 },
583
+ disabled: loading
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx(
587
+ Select,
588
+ {
589
+ placeholder: "Filter by status",
590
+ value: statusFilter,
591
+ onChange: (value) => setStatusFilter(value),
592
+ data: [
593
+ { value: "", label: "All Statuses" },
594
+ { value: "active", label: "Active" },
595
+ { value: "inactive", label: "Inactive" }
596
+ ],
597
+ style: { minWidth: 150 },
598
+ clearable: true,
599
+ disabled: loading
600
+ }
601
+ )
602
+ ] }),
603
+ /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 700, children: /* @__PURE__ */ jsxs(Table, { children: [
604
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
605
+ /* @__PURE__ */ jsx(Table.Th, { children: "Organization" }),
606
+ /* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
607
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
608
+ /* @__PURE__ */ jsx(Table.Th, { children: "Joined" }),
609
+ /* @__PURE__ */ jsx(Table.Th, { align: "right", children: "Actions" })
610
+ ] }) }),
611
+ /* @__PURE__ */ jsx(Table.Tbody, { children: loading ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Center, { p: "xl", children: /* @__PURE__ */ jsx(Loader, {}) }) }) }) : filteredRows.length === 0 ? /* @__PURE__ */ jsx(Table.Tr, { children: /* @__PURE__ */ jsx(Table.Td, { colSpan: 5, children: /* @__PURE__ */ jsx(
612
+ EmptyState,
613
+ {
614
+ icon: IconBuilding,
615
+ title: "No memberships found",
616
+ description: searchTerm || statusFilter ? "Try adjusting your search or filter criteria" : "You are not a member of any organizations yet"
617
+ }
618
+ ) }) }) : filteredRows.map((row) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
619
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
620
+ /* @__PURE__ */ jsx(IconBuilding, { size: 16 }),
621
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: row.organizationName }) })
622
+ ] }) }),
623
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: "blue", size: "sm", children: row.role }) }),
624
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(MembershipStatusBadge, { status: row.status }) }),
625
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: row.joinedAt.toLocaleDateString() }) }),
626
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
627
+ row.canEdit && onEditRole && /* @__PURE__ */ jsx(
628
+ ActionIcon,
629
+ {
630
+ variant: "subtle",
631
+ size: "sm",
632
+ color: "blue",
633
+ onClick: () => onEditRole(row.id),
634
+ disabled: row.status !== "active",
635
+ children: /* @__PURE__ */ jsx(IconEdit, { size: 16 })
636
+ }
637
+ ),
638
+ row.canRemove && onLeaveOrganization && row.status === "active" && /* @__PURE__ */ jsx(
639
+ ActionIcon,
640
+ {
641
+ variant: "subtle",
642
+ size: "sm",
643
+ color: "red",
644
+ onClick: () => onLeaveOrganization(row.id),
645
+ children: /* @__PURE__ */ jsx(IconUserX, { size: 16 })
646
+ }
647
+ ),
648
+ row.status === "inactive" && /* @__PURE__ */ jsx(
649
+ ActionIcon,
650
+ {
651
+ variant: "subtle",
652
+ size: "sm",
653
+ color: "green",
654
+ disabled: true,
655
+ title: "Contact admin to reactivate",
656
+ children: /* @__PURE__ */ jsx(IconUserCheck, { size: 16 })
657
+ }
658
+ )
659
+ ] }) })
660
+ ] }, row.id)) })
661
+ ] }) })
662
+ ] })
663
+ ] });
664
+ }
665
+ function WebhookUrlDisplayModal({ opened, endpoint, webhookUrl, onClose }) {
666
+ return /* @__PURE__ */ jsx(CustomModal, { opened, onClose, size: "lg", loading: false, children: /* @__PURE__ */ jsxs(Stack, { children: [
667
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
668
+ /* @__PURE__ */ jsx(IconWebhook, { size: 24, color: "var(--color-success)" }),
669
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Webhook Endpoint Created" })
670
+ ] }),
671
+ /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, {}), color: "yellow", variant: "light", children: [
672
+ /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", style: { fontFamily: "var(--mantine-font-family-headings)" }, children: "Save this URL \u2014 it contains your secret key." }),
673
+ /* @__PURE__ */ jsx(Text, { size: "sm", mt: 4, children: "The URL includes a unique key that acts as the credential for this endpoint. Treat it like a password. You can always copy it again from the Webhooks settings page." })
674
+ ] }),
675
+ /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
676
+ /* @__PURE__ */ jsxs(Text, { size: "sm", fw: 500, children: [
677
+ "Webhook URL for ",
678
+ /* @__PURE__ */ jsx("strong", { children: endpoint.name }),
679
+ ":"
680
+ ] }),
681
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
682
+ /* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6, flexShrink: 0 } }),
683
+ /* @__PURE__ */ jsx(
684
+ Code,
685
+ {
686
+ block: true,
687
+ style: {
688
+ fontSize: "13px",
689
+ wordBreak: "break-all",
690
+ backgroundColor: "transparent",
691
+ padding: 0,
692
+ flex: 1
693
+ },
694
+ children: webhookUrl
695
+ }
696
+ ),
697
+ /* @__PURE__ */ jsx(CopyButton, { value: webhookUrl, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(
698
+ ActionIcon,
699
+ {
700
+ color: copied ? "teal" : "gray",
701
+ variant: copied ? "filled" : "subtle",
702
+ onClick: copy,
703
+ size: "lg",
704
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 18 }) : /* @__PURE__ */ jsx(IconCopy, { size: 18 })
705
+ }
706
+ ) })
707
+ ] }) })
708
+ ] }),
709
+ endpoint.resourceId && /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
710
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Target resource:" }),
711
+ /* @__PURE__ */ jsx(Code, { block: true, style: { fontSize: "13px", backgroundColor: "transparent", padding: "8px" }, children: endpoint.resourceId })
712
+ ] }),
713
+ /* @__PURE__ */ jsx(Group, { justify: "right", children: /* @__PURE__ */ jsx(Button, { onClick: onClose, size: "sm", leftSection: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), children: "Done" }) })
714
+ ] }) });
715
+ }
716
+ function AccountSettings({
717
+ user,
718
+ userProfile,
719
+ profileLoading,
720
+ onRefetchProfile,
721
+ onNavigate
722
+ }) {
723
+ const { apiRequest } = useElevasisServices();
724
+ const userConfig = userProfile?.config;
725
+ const hasCompletedOnboarding = userConfig?.onboarding?.completed;
726
+ const resetWelcomeFlow = useMutation({
727
+ mutationFn: async () => {
728
+ return apiRequest("/users/me", {
729
+ method: "PATCH",
730
+ body: JSON.stringify({
731
+ config: {
732
+ onboarding: {
733
+ completed: false,
734
+ completedAt: null,
735
+ role: null,
736
+ primaryUseCase: null,
737
+ experienceLevel: null
738
+ }
739
+ }
740
+ })
741
+ });
742
+ },
743
+ onSuccess: async () => {
744
+ notifications.show({
745
+ title: "Welcome Flow Reset",
746
+ message: "Redirecting to the welcome flow...",
747
+ color: "teal"
748
+ });
749
+ await onRefetchProfile();
750
+ onNavigate("/welcome");
751
+ },
752
+ onError: (err) => {
753
+ notifications.show({
754
+ title: "Error",
755
+ message: err instanceof Error ? err.message : "Failed to reset welcome flow",
756
+ color: "red"
757
+ });
758
+ }
759
+ });
760
+ const { data: memberships = [], isLoading, error } = useUserMemberships(user?.id || "");
761
+ const handleEditRole = (_membershipId) => {
762
+ notifications.show({
763
+ title: "Edit Role",
764
+ message: "Role editing functionality coming soon",
765
+ color: "blue"
766
+ });
767
+ };
768
+ const handleLeaveOrganization = (_membershipId) => {
769
+ notifications.show({
770
+ title: "Leave Organization",
771
+ message: "Leave organization functionality coming soon",
772
+ color: "orange"
773
+ });
774
+ };
775
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
776
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Account Settings", caption: "Manage your account and organization memberships" }),
777
+ user && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
778
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
779
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", children: /* @__PURE__ */ jsx(IconUser, { size: 18 }) }),
780
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
781
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Name" }),
782
+ /* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
783
+ user.firstName,
784
+ " ",
785
+ user.lastName
786
+ ] })
787
+ ] }),
788
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
789
+ /* @__PURE__ */ jsx(Code, { children: user.id }),
790
+ /* @__PURE__ */ jsx(CopyButton, { value: user.id, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy User ID", withArrow: true, position: "top", children: /* @__PURE__ */ jsx(
791
+ ActionIcon,
792
+ {
793
+ color: copied ? "teal" : "gray",
794
+ variant: copied ? "filled" : "subtle",
795
+ onClick: copy,
796
+ size: "sm",
797
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
798
+ }
799
+ ) }) })
800
+ ] })
801
+ ] }),
802
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
803
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconMail, { size: 18 }) }),
804
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
805
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Email" }),
806
+ /* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: user.email })
807
+ ] })
808
+ ] }),
809
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", children: [
810
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
811
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "teal", children: /* @__PURE__ */ jsx(IconRefresh, { size: 18 }) }),
812
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
813
+ /* @__PURE__ */ jsx(Text, { fw: 600, children: "Welcome Flow" }),
814
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: hasCompletedOnboarding ? "Re-run the onboarding flow to update your preferences" : "You have not completed the welcome flow yet" })
815
+ ] })
816
+ ] }),
817
+ /* @__PURE__ */ jsx(
818
+ Button,
819
+ {
820
+ variant: "light",
821
+ leftSection: profileLoading ? void 0 : /* @__PURE__ */ jsx(IconRefresh, { size: 16 }),
822
+ onClick: () => resetWelcomeFlow.mutate(),
823
+ loading: profileLoading || resetWelcomeFlow.isPending,
824
+ disabled: profileLoading,
825
+ children: profileLoading ? "" : hasCompletedOnboarding ? "Reset & Redo" : "Start"
826
+ }
827
+ )
828
+ ] })
829
+ ] }) }),
830
+ /* @__PURE__ */ jsx(
831
+ OrganizationMembershipsList,
832
+ {
833
+ memberships,
834
+ loading: isLoading,
835
+ error,
836
+ onEditRole: handleEditRole,
837
+ onLeaveOrganization: handleLeaveOrganization
838
+ }
839
+ ),
840
+ error && /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Error", color: "red", children: [
841
+ "Failed to load organization memberships: ",
842
+ error.message
843
+ ] }),
844
+ !isLoading && !error && memberships.length === 0 && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(
845
+ EmptyState,
846
+ {
847
+ icon: IconBuilding,
848
+ title: "No Organization Memberships",
849
+ description: "You are not currently a member of any organizations. Contact an administrator to be added to an organization."
850
+ }
851
+ ) })
852
+ ] });
853
+ }
854
+ var useInternalThemeStore = create((set) => ({
855
+ preset: "default",
856
+ colorScheme: "auto",
857
+ setPreset: (preset) => set({ preset }),
858
+ setColorScheme: (colorScheme) => set({ colorScheme }),
859
+ setTheme: (theme) => set(theme)
860
+ }));
861
+
862
+ // src/hooks/operations/settings/useUpdateThemePreference.ts
863
+ var DEBOUNCE_MS = 500;
864
+ function useUpdateThemePreference() {
865
+ const { apiRequest } = useElevasisServices();
866
+ const setTheme = useInternalThemeStore((s) => s.setTheme);
867
+ const timerRef = useRef(null);
868
+ const updateTheme = useCallback(
869
+ (theme) => {
870
+ if (theme.preset !== void 0 && theme.colorScheme !== void 0) {
871
+ setTheme({ preset: theme.preset, colorScheme: theme.colorScheme });
872
+ } else {
873
+ const current = useInternalThemeStore.getState();
874
+ setTheme({
875
+ preset: theme.preset ?? current.preset,
876
+ colorScheme: theme.colorScheme ?? current.colorScheme
877
+ });
878
+ }
879
+ if (timerRef.current) clearTimeout(timerRef.current);
880
+ timerRef.current = setTimeout(async () => {
881
+ try {
882
+ await apiRequest("/users/me", {
883
+ method: "PATCH",
884
+ body: JSON.stringify({ config: { theme } })
885
+ });
886
+ } catch (error) {
887
+ showErrorNotification(error instanceof Error ? error : new Error("Failed to update appearance"));
888
+ }
889
+ }, DEBOUNCE_MS);
890
+ },
891
+ [apiRequest, setTheme]
892
+ );
893
+ return { updateTheme };
894
+ }
895
+ var COLOR_SCHEMES = [
896
+ { value: "light", label: "Light", icon: /* @__PURE__ */ jsx(IconSun, { size: 16 }) },
897
+ { value: "dark", label: "Dark", icon: /* @__PURE__ */ jsx(IconMoon, { size: 16 }) },
898
+ { value: "auto", label: "System", icon: /* @__PURE__ */ jsx(IconDeviceDesktop, { size: 16 }) }
899
+ ];
900
+ function AppearanceSettings({ initialTheme, onThemeChange }) {
901
+ const preset = useInternalThemeStore((s) => s.preset);
902
+ const colorScheme = useInternalThemeStore((s) => s.colorScheme);
903
+ const setInternalTheme = useInternalThemeStore((s) => s.setTheme);
904
+ const { updateTheme } = useUpdateThemePreference();
905
+ const { setColorScheme: setMantineColorScheme } = useMantineColorScheme();
906
+ const availablePresets = useAvailablePresets();
907
+ const [modalOpen, setModalOpen] = useState(false);
908
+ const hydratedRef = useRef(false);
909
+ if (!hydratedRef.current) {
910
+ setInternalTheme(initialTheme);
911
+ hydratedRef.current = true;
912
+ }
913
+ useEffect(() => {
914
+ if (!hydratedRef.current) return;
915
+ onThemeChange?.({ preset, colorScheme });
916
+ }, [preset, colorScheme]);
917
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
918
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Appearance", caption: "Customize how the command center looks" }),
919
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
920
+ /* @__PURE__ */ jsx(
921
+ CardHeader,
922
+ {
923
+ icon: /* @__PURE__ */ jsx(IconPalette, { size: 18 }),
924
+ title: "Theme",
925
+ subtitle: "Choose a color palette for the command center"
926
+ }
927
+ ),
928
+ /* @__PURE__ */ jsx(Stack, { gap: "md", children: /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, md: 4 }, children: availablePresets.map((p) => {
929
+ const isActive = p.value === preset;
930
+ return /* @__PURE__ */ jsx(
931
+ UnstyledButton,
932
+ {
933
+ onClick: () => {
934
+ if (!isActive) {
935
+ updateTheme({ preset: p.value, colorScheme });
936
+ }
937
+ },
938
+ children: /* @__PURE__ */ jsx(
939
+ Card,
940
+ {
941
+ style: {
942
+ cursor: "pointer",
943
+ border: isActive ? "var(--active-border)" : "1px solid var(--color-border)",
944
+ backgroundColor: isActive ? "var(--active-background)" : "var(--color-surface)",
945
+ transition: `all var(--duration-fast) var(--easing)`
946
+ },
947
+ children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
948
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", children: [
949
+ /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", children: p.label }),
950
+ /* @__PURE__ */ jsx(
951
+ "div",
952
+ {
953
+ style: {
954
+ width: 14,
955
+ height: 14,
956
+ borderRadius: "50%",
957
+ backgroundColor: p.colors[0],
958
+ border: "1px solid var(--color-border)",
959
+ flexShrink: 0
960
+ }
961
+ }
962
+ )
963
+ ] }),
964
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: p.description })
965
+ ] })
966
+ }
967
+ )
968
+ },
969
+ p.value
970
+ );
971
+ }) }) })
972
+ ] }),
973
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
974
+ /* @__PURE__ */ jsx(
975
+ CardHeader,
976
+ {
977
+ icon: /* @__PURE__ */ jsx(IconSun, { size: 18 }),
978
+ title: "Color Scheme",
979
+ subtitle: "Light, dark, or match your system preference"
980
+ }
981
+ ),
982
+ /* @__PURE__ */ jsx(Stack, { gap: "md", children: /* @__PURE__ */ jsx(SimpleGrid, { cols: 3, children: COLOR_SCHEMES.map((s) => {
983
+ const isActive = s.value === colorScheme;
984
+ return /* @__PURE__ */ jsx(
985
+ UnstyledButton,
986
+ {
987
+ onClick: () => {
988
+ if (!isActive) {
989
+ setMantineColorScheme(s.value);
990
+ updateTheme({ preset, colorScheme: s.value });
991
+ }
992
+ },
993
+ style: { borderRadius: "var(--mantine-radius-md)" },
994
+ children: /* @__PURE__ */ jsx(
995
+ Card,
996
+ {
997
+ padding: "md",
998
+ style: {
999
+ cursor: "pointer",
1000
+ border: isActive ? "var(--active-border)" : "1px solid var(--color-border)",
1001
+ backgroundColor: isActive ? "var(--active-background)" : "var(--color-surface)",
1002
+ transition: `all var(--duration-fast) var(--easing)`
1003
+ },
1004
+ children: /* @__PURE__ */ jsxs(Group, { gap: 6, justify: "center", wrap: "nowrap", children: [
1005
+ s.icon,
1006
+ /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", children: s.label }),
1007
+ isActive && /* @__PURE__ */ jsx(IconCheck, { size: 14, color: "var(--color-primary)", style: { flexShrink: 0 } })
1008
+ ] })
1009
+ }
1010
+ )
1011
+ },
1012
+ s.value
1013
+ );
1014
+ }) }) })
1015
+ ] }),
1016
+ /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
1017
+ /* @__PURE__ */ jsx(
1018
+ CardHeader,
1019
+ {
1020
+ icon: /* @__PURE__ */ jsx(IconEye, { size: 18 }),
1021
+ title: "Preview",
1022
+ subtitle: "See how common UI elements look with your current theme"
1023
+ }
1024
+ ),
1025
+ /* @__PURE__ */ jsxs(Stack, { gap: "lg", children: [
1026
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1027
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Page Layout" }),
1028
+ /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1029
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", children: [
1030
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1031
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Operations Overview" }),
1032
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Monitor recent activity across your workspace" })
1033
+ ] }),
1034
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1035
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", children: "Export" }),
1036
+ /* @__PURE__ */ jsx(Button, { size: "xs", leftSection: /* @__PURE__ */ jsx(IconSparkles, { size: 14 }), children: "New Workflow" }),
1037
+ /* @__PURE__ */ jsx(Button, { variant: "light", size: "xs", onClick: () => setModalOpen(true), children: "Open Modal Preview" })
1038
+ ] })
1039
+ ] }),
1040
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 3 }, spacing: "sm", children: [
1041
+ { label: "Active Workflows", value: "24", trend: "+12%" },
1042
+ { label: "Executions Today", value: "1,284", trend: "+8.3%" },
1043
+ { label: "Success Rate", value: "98.2%", trend: "+0.4%" }
1044
+ ].map((stat) => /* @__PURE__ */ jsx(
1045
+ Card,
1046
+ {
1047
+ padding: "sm",
1048
+ withBorder: true,
1049
+ style: {
1050
+ transition: `all var(--duration-fast) var(--easing)`
1051
+ },
1052
+ children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1053
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: stat.label }),
1054
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", wrap: "nowrap", children: [
1055
+ /* @__PURE__ */ jsx(Text, { size: "xl", fw: 700, children: stat.value }),
1056
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "green", leftSection: /* @__PURE__ */ jsx(IconTrendingUp, { size: 10 }), children: stat.trend })
1057
+ ] })
1058
+ ] })
1059
+ },
1060
+ stat.label
1061
+ )) })
1062
+ ] }) })
1063
+ ] }),
1064
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, md: 2 }, spacing: "md", children: [
1065
+ /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1066
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Buttons" }),
1067
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1068
+ /* @__PURE__ */ jsx(Button, { size: "xs", children: "Primary" }),
1069
+ /* @__PURE__ */ jsx(Button, { size: "xs", variant: "light", children: "Light" }),
1070
+ /* @__PURE__ */ jsx(Button, { size: "xs", variant: "outline", children: "Outline" }),
1071
+ /* @__PURE__ */ jsx(Button, { size: "xs", variant: "default", children: "Default" }),
1072
+ /* @__PURE__ */ jsx(Button, { size: "xs", variant: "subtle", children: "Subtle" })
1073
+ ] })
1074
+ ] }) }),
1075
+ /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1076
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Status Badges" }),
1077
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1078
+ /* @__PURE__ */ jsx(Badge, { variant: "light", children: "Active" }),
1079
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: "green", children: "Success" }),
1080
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: "yellow", children: "Pending" }),
1081
+ /* @__PURE__ */ jsx(Badge, { variant: "light", color: "red", children: "Failed" }),
1082
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", children: "Draft" })
1083
+ ] })
1084
+ ] }) }),
1085
+ /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1086
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Inputs" }),
1087
+ /* @__PURE__ */ jsx(TextInput, { placeholder: "Search workflows...", leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 14 }), size: "xs" }),
1088
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
1089
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: "Enable notifications" }),
1090
+ /* @__PURE__ */ jsx(Switch, { defaultChecked: true, size: "sm" })
1091
+ ] })
1092
+ ] }) }),
1093
+ /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1094
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Typography" }),
1095
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
1096
+ /* @__PURE__ */ jsx(Title, { order: 4, children: "Section Heading" }),
1097
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: "The quick brown fox jumps over the lazy dog." }),
1098
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Secondary text with dimmed color for less emphasis." }),
1099
+ /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
1100
+ /* @__PURE__ */ jsx(Text, { span: true, c: "var(--color-primary)", fw: 600, inherit: true, children: "Link" }),
1101
+ " ",
1102
+ "styled with primary color token."
1103
+ ] })
1104
+ ] })
1105
+ ] }) })
1106
+ ] }),
1107
+ /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1108
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Table" }),
1109
+ /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 400, children: /* @__PURE__ */ jsxs(Table, { verticalSpacing: "xs", horizontalSpacing: "sm", children: [
1110
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1111
+ /* @__PURE__ */ jsx(Table.Th, { children: "Workflow" }),
1112
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1113
+ /* @__PURE__ */ jsx(Table.Th, { children: "Runs" }),
1114
+ /* @__PURE__ */ jsx(Table.Th, { children: "Last Run" })
1115
+ ] }) }),
1116
+ /* @__PURE__ */ jsx(Table.Tbody, { children: [
1117
+ { name: "deal-enrichment", status: "Active", color: "green", runs: "482", last: "2m ago" },
1118
+ { name: "ist-campaign-review", status: "Active", color: "green", runs: "156", last: "14m ago" },
1119
+ { name: "tomba-email-finder", status: "Paused", color: "yellow", runs: "89", last: "3h ago" }
1120
+ ].map((row) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
1121
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", children: row.name }) }),
1122
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: row.color, size: "xs", children: row.status }) }),
1123
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: row.runs }) }),
1124
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: row.last }) })
1125
+ ] }, row.name)) })
1126
+ ] }) })
1127
+ ] }) }),
1128
+ /* @__PURE__ */ jsx(Divider, {}),
1129
+ /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
1130
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Alerts" }),
1131
+ /* @__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." }),
1132
+ /* @__PURE__ */ jsx(Alert, { variant: "light", color: "green", icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), title: "Success", children: "Operation completed successfully." })
1133
+ ] })
1134
+ ] })
1135
+ ] }),
1136
+ /* @__PURE__ */ jsx(CustomModal, { opened: modalOpen, onClose: () => setModalOpen(false), size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
1137
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Modal Preview" }),
1138
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1139
+ "Modals should sit visibly above the page background. Elevation comes from the",
1140
+ " ",
1141
+ /* @__PURE__ */ jsx(Text, { span: true, ff: "monospace", size: "sm", children: "--color-elevated" }),
1142
+ " ",
1143
+ "token, which every preset defines as an opaque surface."
1144
+ ] }),
1145
+ /* @__PURE__ */ jsx(TextInput, { label: "Example field", placeholder: "Type something...", size: "xs" }),
1146
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
1147
+ /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", onClick: () => setModalOpen(false), children: "Cancel" }),
1148
+ /* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => setModalOpen(false), children: "Confirm" })
1149
+ ] })
1150
+ ] }) })
1151
+ ] });
1152
+ }
1153
+ function EditWebhookEndpointModal({ opened, endpoint, onClose }) {
1154
+ const updateEndpoint = useUpdateWebhookEndpoint();
1155
+ const { data: resourcesData } = useResources();
1156
+ const resourceOptions = useMemo(() => {
1157
+ if (!resourcesData) return [];
1158
+ const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
1159
+ value: w.resourceId,
1160
+ label: `Workflow: ${w.resourceId}`
1161
+ }));
1162
+ const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
1163
+ value: a.resourceId,
1164
+ label: `Agent: ${a.resourceId}`
1165
+ }));
1166
+ return [...workflowOptions, ...agentOptions];
1167
+ }, [resourcesData]);
1168
+ const form = useForm({
1169
+ initialValues: {
1170
+ name: "",
1171
+ resourceId: ""
1172
+ },
1173
+ validate: {
1174
+ name: (value) => {
1175
+ if (!value.trim()) return "Name is required";
1176
+ if (value.trim().length < 2) return "Name must be at least 2 characters";
1177
+ if (value.trim().length > 100) return "Name must be less than 100 characters";
1178
+ return null;
1179
+ }
1180
+ }
1181
+ });
1182
+ useEffect(() => {
1183
+ if (endpoint && opened) {
1184
+ form.setValues({
1185
+ name: endpoint.name,
1186
+ resourceId: endpoint.resourceId ?? ""
1187
+ });
1188
+ }
1189
+ }, [endpoint, opened]);
1190
+ const handleSubmit = async (values) => {
1191
+ if (!endpoint) return;
1192
+ try {
1193
+ await updateEndpoint.mutateAsync({
1194
+ endpointId: endpoint.id,
1195
+ data: {
1196
+ name: values.name.trim(),
1197
+ resourceId: values.resourceId || void 0
1198
+ }
1199
+ });
1200
+ onClose();
1201
+ } catch (error) {
1202
+ console.error("Failed to update webhook endpoint:", error);
1203
+ }
1204
+ };
1205
+ const handleClose = () => {
1206
+ if (!updateEndpoint.isPending) {
1207
+ onClose();
1208
+ }
1209
+ };
1210
+ 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: [
1211
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Edit Webhook Endpoint" }),
1212
+ /* @__PURE__ */ jsx(
1213
+ TextInput,
1214
+ {
1215
+ label: "Name",
1216
+ description: "A descriptive label for this endpoint",
1217
+ leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
1218
+ required: true,
1219
+ ...form.getInputProps("name"),
1220
+ disabled: updateEndpoint.isPending
1221
+ }
1222
+ ),
1223
+ /* @__PURE__ */ jsx(
1224
+ Select,
1225
+ {
1226
+ label: "Target Resource",
1227
+ description: "The workflow or agent to trigger on each inbound request",
1228
+ placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
1229
+ data: resourceOptions,
1230
+ searchable: true,
1231
+ clearable: true,
1232
+ ...form.getInputProps("resourceId"),
1233
+ disabled: !resourcesData || updateEndpoint.isPending
1234
+ }
1235
+ ),
1236
+ 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" }),
1237
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1238
+ /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: updateEndpoint.isPending, children: "Cancel" }),
1239
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: updateEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconPencil, { size: 16 }), children: "Save Changes" })
1240
+ ] })
1241
+ ] }) }) });
1242
+ }
1243
+ function getWebhookUrl(apiUrl, key) {
1244
+ return `${apiUrl}/api/webhooks/inbound/${key}`;
1245
+ }
1246
+ function WebhookEndpointList({ endpoints, isLoading, apiUrl }) {
1247
+ const [deleteTarget, setDeleteTarget] = useState(null);
1248
+ const [editTarget, setEditTarget] = useState(null);
1249
+ const deleteMutation = useDeleteWebhookEndpoint();
1250
+ const updateMutation = useUpdateWebhookEndpoint();
1251
+ const handleDelete = () => {
1252
+ if (deleteTarget) {
1253
+ deleteMutation.mutate(deleteTarget.id);
1254
+ setDeleteTarget(null);
1255
+ }
1256
+ };
1257
+ const handleCloseDelete = () => {
1258
+ if (!deleteMutation.isPending) {
1259
+ setDeleteTarget(null);
1260
+ }
1261
+ };
1262
+ const handleToggleStatus = (endpoint) => {
1263
+ const newStatus = endpoint.status === "active" ? "paused" : "active";
1264
+ updateMutation.mutate({ endpointId: endpoint.id, data: { status: newStatus } });
1265
+ };
1266
+ if (isLoading) {
1267
+ return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 50 });
1268
+ }
1269
+ if (endpoints.length === 0) {
1270
+ return /* @__PURE__ */ jsx(
1271
+ EmptyState,
1272
+ {
1273
+ icon: IconWebhook,
1274
+ title: "No webhook endpoints yet",
1275
+ description: "Create your first webhook endpoint to let third-party apps trigger your resources"
1276
+ }
1277
+ );
1278
+ }
1279
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1280
+ /* @__PURE__ */ jsxs(Table, { children: [
1281
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1282
+ /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
1283
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1284
+ /* @__PURE__ */ jsx(Table.Th, { children: "Target Resource" }),
1285
+ /* @__PURE__ */ jsx(Table.Th, { children: "URL" }),
1286
+ /* @__PURE__ */ jsx(Table.Th, { children: "Last Triggered" }),
1287
+ /* @__PURE__ */ jsx(Table.Th, { children: "Requests" }),
1288
+ /* @__PURE__ */ jsx(Table.Th, { w: 120 })
1289
+ ] }) }),
1290
+ /* @__PURE__ */ jsx(Table.Tbody, { children: endpoints.map((endpoint) => {
1291
+ const webhookUrl = getWebhookUrl(apiUrl, endpoint.key);
1292
+ const truncatedUrl = webhookUrl.length > 50 ? `...${endpoint.key.slice(-16)}` : webhookUrl;
1293
+ const isToggling = updateMutation.isPending;
1294
+ return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1295
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1296
+ /* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6 } }),
1297
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
1298
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: endpoint.name }),
1299
+ endpoint.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: endpoint.description })
1300
+ ] })
1301
+ ] }) }),
1302
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { color: endpoint.status === "active" ? "teal" : "gray", variant: "light", children: endpoint.status }) }),
1303
+ /* @__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" }) }),
1304
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Paper, { withBorder: true, style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
1305
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { userSelect: "all" }, children: truncatedUrl }),
1306
+ /* @__PURE__ */ jsx(CopyButton, { value: webhookUrl, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy URL", position: "top", children: /* @__PURE__ */ jsx(
1307
+ ActionIcon,
1308
+ {
1309
+ color: copied ? "teal" : "gray",
1310
+ variant: copied ? "filled" : "subtle",
1311
+ onClick: copy,
1312
+ size: "sm",
1313
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 12 }) : /* @__PURE__ */ jsx(IconCopy, { size: 12 })
1314
+ }
1315
+ ) }) })
1316
+ ] }) }),
1317
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
1318
+ /* @__PURE__ */ jsx(IconClock, { size: 14, style: { opacity: 0.5 } }),
1319
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(endpoint.lastTriggeredAt) })
1320
+ ] }) }),
1321
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: endpoint.requestCount.toLocaleString() }) }),
1322
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
1323
+ /* @__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 }) }) }),
1324
+ /* @__PURE__ */ jsx(
1325
+ Tooltip,
1326
+ {
1327
+ label: endpoint.status === "active" ? "Pause endpoint" : "Resume endpoint",
1328
+ position: "left",
1329
+ children: /* @__PURE__ */ jsx(
1330
+ ActionIcon,
1331
+ {
1332
+ variant: "subtle",
1333
+ color: endpoint.status === "active" ? "yellow" : "teal",
1334
+ onClick: () => handleToggleStatus(endpoint),
1335
+ disabled: isToggling,
1336
+ children: endpoint.status === "active" ? /* @__PURE__ */ jsx(IconPlayerPause, { size: 16 }) : /* @__PURE__ */ jsx(IconPlayerPlay, { size: 16 })
1337
+ }
1338
+ )
1339
+ }
1340
+ ),
1341
+ /* @__PURE__ */ jsx(Tooltip, { label: "Delete endpoint", position: "left", children: /* @__PURE__ */ jsx(
1342
+ ActionIcon,
1343
+ {
1344
+ variant: "subtle",
1345
+ color: "red",
1346
+ onClick: () => setDeleteTarget(endpoint),
1347
+ disabled: deleteMutation.isPending,
1348
+ children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
1349
+ }
1350
+ ) })
1351
+ ] }) })
1352
+ ] }, endpoint.id);
1353
+ }) })
1354
+ ] }),
1355
+ /* @__PURE__ */ jsx(EditWebhookEndpointModal, { opened: !!editTarget, endpoint: editTarget, onClose: () => setEditTarget(null) }),
1356
+ /* @__PURE__ */ jsx(CustomModal, { opened: !!deleteTarget, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
1357
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1358
+ /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
1359
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Webhook Endpoint" })
1360
+ ] }),
1361
+ /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
1362
+ "Are you sure you want to delete",
1363
+ " ",
1364
+ /* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
1365
+ '"',
1366
+ deleteTarget?.name,
1367
+ '"'
1368
+ ] }),
1369
+ "?"
1370
+ ] }),
1371
+ /* @__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." }),
1372
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1373
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
1374
+ /* @__PURE__ */ jsx(
1375
+ Button,
1376
+ {
1377
+ color: "red",
1378
+ onClick: handleDelete,
1379
+ loading: deleteMutation.isPending,
1380
+ leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
1381
+ children: "Delete Endpoint"
1382
+ }
1383
+ )
1384
+ ] })
1385
+ ] }) })
1386
+ ] });
1387
+ }
1388
+ function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
1389
+ const createEndpoint = useCreateWebhookEndpoint();
1390
+ const { data: resourcesData } = useResources();
1391
+ const resourceOptions = useMemo(() => {
1392
+ if (!resourcesData) return [];
1393
+ const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
1394
+ value: w.resourceId,
1395
+ label: `Workflow: ${w.resourceId}`
1396
+ }));
1397
+ const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
1398
+ value: a.resourceId,
1399
+ label: `Agent: ${a.resourceId}`
1400
+ }));
1401
+ return [...workflowOptions, ...agentOptions];
1402
+ }, [resourcesData]);
1403
+ const form = useForm({
1404
+ initialValues: {
1405
+ name: "",
1406
+ resourceId: "",
1407
+ description: ""
1408
+ },
1409
+ validate: {
1410
+ name: (value) => {
1411
+ if (!value.trim()) return "Name is required";
1412
+ if (value.trim().length < 2) return "Name must be at least 2 characters";
1413
+ if (value.trim().length > 100) return "Name must be less than 100 characters";
1414
+ return null;
1415
+ }
1416
+ }
1417
+ });
1418
+ const handleSubmit = async (values) => {
1419
+ try {
1420
+ const result = await createEndpoint.mutateAsync({
1421
+ name: values.name.trim(),
1422
+ ...values.resourceId ? { resourceId: values.resourceId } : {},
1423
+ ...values.description.trim() ? { description: values.description.trim() } : {}
1424
+ });
1425
+ form.reset();
1426
+ onSuccess(result);
1427
+ } catch (error) {
1428
+ console.error("Failed to create webhook endpoint:", error);
1429
+ }
1430
+ };
1431
+ const handleClose = () => {
1432
+ if (!createEndpoint.isPending) {
1433
+ form.reset();
1434
+ onClose();
1435
+ }
1436
+ };
1437
+ 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: [
1438
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Create Webhook Endpoint" }),
1439
+ /* @__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." }),
1440
+ /* @__PURE__ */ jsx(
1441
+ TextInput,
1442
+ {
1443
+ label: "Name",
1444
+ description: "A descriptive label for this endpoint",
1445
+ placeholder: "e.g., Zapier Lead Intake, Typeform Responses",
1446
+ leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
1447
+ required: true,
1448
+ ...form.getInputProps("name"),
1449
+ disabled: createEndpoint.isPending
1450
+ }
1451
+ ),
1452
+ /* @__PURE__ */ jsx(
1453
+ Select,
1454
+ {
1455
+ label: "Target Resource",
1456
+ description: "The workflow or agent to trigger on each inbound request (can be set later)",
1457
+ placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
1458
+ data: resourceOptions,
1459
+ searchable: true,
1460
+ clearable: true,
1461
+ ...form.getInputProps("resourceId"),
1462
+ disabled: !resourcesData || createEndpoint.isPending
1463
+ }
1464
+ ),
1465
+ /* @__PURE__ */ jsx(
1466
+ Textarea,
1467
+ {
1468
+ label: "Description",
1469
+ description: "Optional notes about what this endpoint is for",
1470
+ placeholder: "e.g., Receives new lead submissions from Zapier",
1471
+ rows: 3,
1472
+ ...form.getInputProps("description"),
1473
+ disabled: createEndpoint.isPending
1474
+ }
1475
+ ),
1476
+ 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" }),
1477
+ /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
1478
+ /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createEndpoint.isPending, children: "Cancel" }),
1479
+ /* @__PURE__ */ jsx(Button, { type: "submit", loading: createEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }), children: "Create Endpoint" })
1480
+ ] })
1481
+ ] }) }) });
1482
+ }
1483
+ function getWebhookUrl2(apiUrl, key) {
1484
+ return `${apiUrl}/api/webhooks/inbound/${key}`;
1485
+ }
1486
+ function WebhookEndpointSettings({ apiUrl }) {
1487
+ const { organizationReady } = useInitialization();
1488
+ const [showCreateModal, setShowCreateModal] = useState(false);
1489
+ const [newEndpoint, setNewEndpoint] = useState(null);
1490
+ const { data: endpoints = [], isLoading } = useListWebhookEndpoints();
1491
+ if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
1492
+ const handleEndpointCreated = (endpoint) => {
1493
+ setNewEndpoint(endpoint);
1494
+ setShowCreateModal(false);
1495
+ };
1496
+ const activeCount = endpoints.filter((e) => e.status === "active").length;
1497
+ const totalRequests = endpoints.reduce((sum, e) => sum + e.requestCount, 0);
1498
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1499
+ /* @__PURE__ */ jsx(
1500
+ PageTitleCaption,
1501
+ {
1502
+ title: "Webhooks",
1503
+ caption: "Create webhook URLs that third-party apps can send data to, triggering your resources.",
1504
+ rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Endpoint" })
1505
+ }
1506
+ ),
1507
+ /* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
1508
+ /* @__PURE__ */ jsx(
1509
+ StatCard,
1510
+ {
1511
+ variant: "hero",
1512
+ icon: IconWebhook,
1513
+ value: activeCount,
1514
+ label: "Active Endpoints",
1515
+ isLoading
1516
+ }
1517
+ ),
1518
+ /* @__PURE__ */ jsx(
1519
+ StatCard,
1520
+ {
1521
+ variant: "hero",
1522
+ icon: IconActivity,
1523
+ value: totalRequests,
1524
+ label: "Total Requests",
1525
+ isLoading
1526
+ }
1527
+ )
1528
+ ] }),
1529
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(WebhookEndpointList, { endpoints, isLoading, apiUrl }) }),
1530
+ /* @__PURE__ */ jsx(
1531
+ CreateWebhookEndpointModal,
1532
+ {
1533
+ opened: showCreateModal,
1534
+ onClose: () => setShowCreateModal(false),
1535
+ onSuccess: handleEndpointCreated
1536
+ }
1537
+ ),
1538
+ newEndpoint && /* @__PURE__ */ jsx(
1539
+ WebhookUrlDisplayModal,
1540
+ {
1541
+ opened: true,
1542
+ endpoint: newEndpoint,
1543
+ webhookUrl: getWebhookUrl2(apiUrl, newEndpoint.key),
1544
+ onClose: () => setNewEndpoint(null)
1545
+ }
1546
+ )
1547
+ ] });
1548
+ }
1549
+ function MemberConfigModal({ opened, onClose, member }) {
1550
+ const [localConfig, setLocalConfig] = useState(void 0);
1551
+ const mutation = useUpdateMemberConfig();
1552
+ useEffect(() => {
1553
+ if (member) {
1554
+ setLocalConfig(member.config);
1555
+ }
1556
+ }, [member]);
1557
+ useEffect(() => {
1558
+ if (!opened) {
1559
+ setLocalConfig(void 0);
1560
+ }
1561
+ }, [opened]);
1562
+ if (!member) return null;
1563
+ const handleSave = async () => {
1564
+ if (!localConfig) {
1565
+ onClose();
1566
+ return;
1567
+ }
1568
+ try {
1569
+ await mutation.mutateAsync({
1570
+ membershipId: member.id,
1571
+ config: localConfig
1572
+ });
1573
+ onClose();
1574
+ } catch {
1575
+ }
1576
+ };
1577
+ const handleClose = () => {
1578
+ if (!mutation.isPending) {
1579
+ onClose();
1580
+ }
1581
+ };
1582
+ return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: mutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
1583
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Configure Member Access" }),
1584
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1585
+ /* @__PURE__ */ jsx(Text, { fw: 500, children: member.name }),
1586
+ /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: member.role })
1587
+ ] }),
1588
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.email }),
1589
+ /* @__PURE__ */ jsx(
1590
+ MembershipFeaturePanel,
1591
+ {
1592
+ currentConfig: localConfig ?? member.config,
1593
+ onConfigChange: (newConfig) => setLocalConfig(newConfig),
1594
+ disabled: mutation.isPending
1595
+ }
1596
+ ),
1597
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", mt: "md", children: [
1598
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: mutation.isPending, children: "Cancel" }),
1599
+ /* @__PURE__ */ jsx(Button, { onClick: handleSave, loading: mutation.isPending, children: "Save Changes" })
1600
+ ] })
1601
+ ] }) });
1602
+ }
1603
+ function transformMemberForModal(member) {
1604
+ const firstName = member.user?.firstName || "";
1605
+ const lastName = member.user?.lastName || "";
1606
+ const name = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1607
+ return {
1608
+ id: member.id,
1609
+ name,
1610
+ email: member.user?.email || "Unknown",
1611
+ role: member.role.slug,
1612
+ config: member.config
1613
+ };
1614
+ }
1615
+ function OrgMembersList({ orgId }) {
1616
+ const { data: members, isLoading, error } = useOrganizationMembers(orgId);
1617
+ const [selectedMember, setSelectedMember] = useState(null);
1618
+ const [modalOpened, setModalOpened] = useState(false);
1619
+ const handleConfigClick = (member) => {
1620
+ setSelectedMember(member);
1621
+ setModalOpened(true);
1622
+ };
1623
+ const handleModalClose = () => {
1624
+ setModalOpened(false);
1625
+ setSelectedMember(null);
1626
+ };
1627
+ if (isLoading) {
1628
+ return /* @__PURE__ */ jsx(Center, { py: "xl", children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "sm", children: [
1629
+ /* @__PURE__ */ jsx(Loader, { size: "md" }),
1630
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Loading members..." })
1631
+ ] }) });
1632
+ }
1633
+ if (error) {
1634
+ return /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Error loading members", color: "red", variant: "light", children: error.message || "Failed to load organization members. Please try again." });
1635
+ }
1636
+ if (!members || members.length === 0) {
1637
+ return /* @__PURE__ */ jsx(EmptyState, { icon: IconUsers, title: "No members found" });
1638
+ }
1639
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1640
+ /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 500, children: /* @__PURE__ */ jsxs(Table, { children: [
1641
+ /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
1642
+ /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
1643
+ /* @__PURE__ */ jsx(Table.Th, { children: "Email" }),
1644
+ /* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
1645
+ /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1646
+ /* @__PURE__ */ jsx(Table.Th, { children: "Actions" })
1647
+ ] }) }),
1648
+ /* @__PURE__ */ jsx(Table.Tbody, { children: members.map((member) => {
1649
+ const firstName = member.user?.firstName || "";
1650
+ const lastName = member.user?.lastName || "";
1651
+ const displayName = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1652
+ return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1653
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: displayName }) }),
1654
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.user?.email || "Unknown" }) }),
1655
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: "blue", size: "sm", children: member.role.slug }) }),
1656
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(MembershipStatusBadge, { status: member.status }) }),
1657
+ /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Group, { gap: "xs", children: /* @__PURE__ */ jsx(
1658
+ ActionIcon,
1659
+ {
1660
+ variant: "light",
1661
+ size: "sm",
1662
+ color: "gray",
1663
+ onClick: () => handleConfigClick(member),
1664
+ title: "Configure member access",
1665
+ children: /* @__PURE__ */ jsx(IconSettings, { size: 14 })
1666
+ }
1667
+ ) }) })
1668
+ ] }, member.id);
1669
+ }) })
1670
+ ] }) }),
1671
+ /* @__PURE__ */ jsx(
1672
+ MemberConfigModal,
1673
+ {
1674
+ opened: modalOpened,
1675
+ onClose: handleModalClose,
1676
+ member: selectedMember ? transformMemberForModal(selectedMember) : null
1677
+ }
1678
+ )
1679
+ ] });
1680
+ }
1681
+ function OrganizationSettings({ user, currentMembership, isOrgAdmin }) {
1682
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1683
+ /* @__PURE__ */ jsx(PageTitleCaption, { title: "Organization Settings", caption: "Manage your current organization" }),
1684
+ currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1685
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1686
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconBuilding, { size: 18 }) }),
1687
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1688
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Current Organization" }),
1689
+ /* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: currentMembership.organization.name })
1690
+ ] }),
1691
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1692
+ /* @__PURE__ */ jsx(Code, { children: currentMembership.organization.id }),
1693
+ /* @__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(
1694
+ ActionIcon,
1695
+ {
1696
+ color: copied ? "teal" : "gray",
1697
+ variant: copied ? "filled" : "subtle",
1698
+ onClick: copy,
1699
+ size: "sm",
1700
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1701
+ }
1702
+ ) }) })
1703
+ ] })
1704
+ ] }),
1705
+ user && /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1706
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(IconUsers, { size: 18 }) }),
1707
+ /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1708
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Signed in as" }),
1709
+ /* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
1710
+ user.firstName,
1711
+ " ",
1712
+ user.lastName,
1713
+ " \u2022 ",
1714
+ user.email
1715
+ ] })
1716
+ ] }),
1717
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1718
+ /* @__PURE__ */ jsx(Code, { children: currentMembership.userId }),
1719
+ /* @__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(
1720
+ ActionIcon,
1721
+ {
1722
+ color: copied ? "teal" : "gray",
1723
+ variant: copied ? "filled" : "subtle",
1724
+ onClick: copy,
1725
+ size: "sm",
1726
+ children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1727
+ }
1728
+ ) }) })
1729
+ ] })
1730
+ ] })
1731
+ ] }) }),
1732
+ isOrgAdmin && currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1733
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "Manage Members" }),
1734
+ /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "Configure feature access for organization members." }),
1735
+ /* @__PURE__ */ jsx(OrgMembersList, { orgId: currentMembership.organization.id })
1736
+ ] }) })
1737
+ ] });
1738
+ }
1739
+ function useOAuthFlow({ apiUrl }) {
1740
+ const [isAuthorizing, setIsAuthorizing] = useState(false);
1741
+ const [error, setError] = useState(null);
1742
+ const { organizationId, isReady } = useElevasisServices();
1743
+ const authorize = useCallback(
1744
+ async (provider, credentialName) => {
1745
+ if (!isReady || !organizationId) {
1746
+ setError("Organization context not ready");
1747
+ return;
1748
+ }
1749
+ setIsAuthorizing(true);
1750
+ setError(null);
1751
+ try {
1752
+ const authorizeUrl = new URL(`${apiUrl}/api/oauth/authorize/${provider}`);
1753
+ authorizeUrl.searchParams.set("credentialName", credentialName);
1754
+ authorizeUrl.searchParams.set("organizationId", organizationId);
1755
+ const width = 600;
1756
+ const height = 700;
1757
+ const left = Math.round(window.screen.width / 2 - width / 2);
1758
+ const top = Math.round(window.screen.height / 2 - height / 2);
1759
+ const popup = window.open(
1760
+ authorizeUrl.toString(),
1761
+ `oauth-${provider}`,
1762
+ `width=${width},height=${height},left=${left},top=${top}`
1763
+ );
1764
+ if (!popup) {
1765
+ throw new Error("popup_blocked");
1766
+ }
1767
+ await waitForOAuthCallback(popup);
1768
+ } catch (err) {
1769
+ const rawError = err instanceof Error ? err.message : "unknown";
1770
+ if (rawError.includes("popup_blocked")) {
1771
+ setError("Please enable popups and try again");
1772
+ } else if (rawError.includes("cancelled")) {
1773
+ setError("Authorization cancelled");
1774
+ } else if (rawError.includes("timeout")) {
1775
+ setError("Authorization timed out. Please try again.");
1776
+ } else if (rawError.includes("token_exchange_failed")) {
1777
+ setError("Failed to obtain access token. Please try again.");
1778
+ } else if (rawError.includes("invalid_state")) {
1779
+ setError("Security validation failed. Please try again.");
1780
+ } else if (rawError.includes("state_expired")) {
1781
+ setError("Authorization expired. Please try again.");
1782
+ } else {
1783
+ setError(`Failed to connect: ${rawError}`);
1784
+ }
1785
+ throw err;
1786
+ } finally {
1787
+ setIsAuthorizing(false);
1788
+ }
1789
+ },
1790
+ [isReady, organizationId, apiUrl]
1791
+ );
1792
+ const waitForOAuthCallback = (popup) => {
1793
+ return new Promise((resolve, reject) => {
1794
+ const messageHandler = (event) => {
1795
+ const expectedOrigin = window.location.origin;
1796
+ if (event.origin !== expectedOrigin) {
1797
+ return;
1798
+ }
1799
+ if (event.data.type === "oauth-complete") {
1800
+ cleanup();
1801
+ if (event.data.status === "success") {
1802
+ resolve();
1803
+ } else {
1804
+ reject(new Error(event.data.error || "oauth_failed"));
1805
+ }
1806
+ }
1807
+ };
1808
+ window.addEventListener("message", messageHandler);
1809
+ const checkClosed = setInterval(() => {
1810
+ if (popup.closed) {
1811
+ cleanup();
1812
+ reject(new Error("cancelled"));
1813
+ }
1814
+ }, OAUTH_POPUP_CHECK_INTERVAL);
1815
+ const timeout = setTimeout(() => {
1816
+ cleanup();
1817
+ popup.close();
1818
+ reject(new Error("timeout"));
1819
+ }, OAUTH_FLOW_TIMEOUT);
1820
+ const cleanup = () => {
1821
+ clearInterval(checkClosed);
1822
+ clearTimeout(timeout);
1823
+ window.removeEventListener("message", messageHandler);
1824
+ };
1825
+ });
1826
+ };
1827
+ return { authorize, isAuthorizing, error };
1828
+ }
1829
+ function EditCredentialModal({ apiUrl, credential, onClose }) {
1830
+ const [credentialName, setCredentialName] = useState("");
1831
+ const [nameError, setNameError] = useState(null);
1832
+ const [fieldValues, setFieldValues] = useState({});
1833
+ const { authorize, isAuthorizing, error: oauthError } = useOAuthFlow({ apiUrl });
1834
+ const updateMutation = useUpdateCredential();
1835
+ useEffect(() => {
1836
+ if (credential) {
1837
+ setCredentialName(credential.name);
1838
+ }
1839
+ }, [credential]);
1840
+ if (!credential) return null;
1841
+ const credentialType = credential.type || "api-key";
1842
+ const schema = getCredentialSchema(credentialType);
1843
+ const provider = credential.provider;
1844
+ const isOAuth = !!provider || schema.type === "oauth";
1845
+ const hasMetadataChanged = credentialName !== credential.name;
1846
+ const isPending = isAuthorizing || updateMutation.isPending;
1847
+ const handleClose = () => {
1848
+ if (!isPending) {
1849
+ onClose();
1850
+ }
1851
+ };
1852
+ const handleOAuthReconnect = async () => {
1853
+ if (!provider) return;
1854
+ try {
1855
+ await authorize(provider, credential.name);
1856
+ onClose();
1857
+ } catch (err) {
1858
+ console.error("Reconnect failed:", err);
1859
+ }
1860
+ };
1861
+ const handleSaveChanges = async () => {
1862
+ const nameValidation = CredentialSchemas.UpdateRequest.shape.name.safeParse(credentialName);
1863
+ if (!nameValidation.success) {
1864
+ setNameError(nameValidation.error.issues[0]?.message || "Invalid credential name");
1865
+ return;
1866
+ }
1867
+ setNameError(null);
1868
+ const updateData = { name: credentialName };
1869
+ const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
1870
+ if (!validationResult.success) {
1871
+ console.error("Validation failed:", validationResult.error);
1872
+ return;
1873
+ }
1874
+ try {
1875
+ await updateMutation.mutateAsync({
1876
+ credentialId: credential.id,
1877
+ updates: updateData
1878
+ });
1879
+ onClose();
1880
+ } catch (err) {
1881
+ console.error("Save changes failed:", err);
1882
+ }
1883
+ };
1884
+ const handleCredentialUpdate = async () => {
1885
+ const hasValues = Object.values(fieldValues).some((v) => v.trim());
1886
+ if (!hasValues) return;
1887
+ try {
1888
+ const credentialValue = buildCredentialValue(credentialType, fieldValues);
1889
+ const updateData = { value: credentialValue };
1890
+ const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
1891
+ if (!validationResult.success) {
1892
+ console.error("Validation failed:", validationResult.error);
1893
+ return;
1894
+ }
1895
+ await updateMutation.mutateAsync({
1896
+ credentialId: credential.id,
1897
+ updates: updateData
1898
+ });
1899
+ onClose();
1900
+ } catch (err) {
1901
+ console.error("Update failed:", err);
1902
+ }
1903
+ };
1904
+ const handleFieldChange = (key, value) => {
1905
+ setFieldValues((prev) => ({ ...prev, [key]: value }));
1906
+ };
1907
+ const handleNameChange = (value) => {
1908
+ setCredentialName(value);
1909
+ if (nameError) setNameError(null);
1910
+ };
1911
+ const hasFieldValues = Object.values(fieldValues).some((v) => v.trim());
1912
+ return /* @__PURE__ */ jsx(CustomModal, { opened: true, onClose: handleClose, loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
1913
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1914
+ /* @__PURE__ */ jsx(IconSettings, { size: 24 }),
1915
+ /* @__PURE__ */ jsx(Title, { order: 3, children: isOAuth ? "Manage OAuth Credential" : "Edit API Key" })
1916
+ ] }),
1917
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1918
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Type:" }),
1919
+ /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: schema.label })
1920
+ ] }),
1921
+ /* @__PURE__ */ jsx(
1922
+ TextInput,
1923
+ {
1924
+ label: "Name",
1925
+ value: credentialName,
1926
+ onChange: (e) => handleNameChange(e.target.value),
1927
+ disabled: isPending,
1928
+ maxLength: 100,
1929
+ description: "Lowercase letters, numbers, and hyphens only (e.g., gmail-dev, notion-prod)",
1930
+ error: nameError
1931
+ }
1932
+ ),
1933
+ isOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
1934
+ /* @__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.' }),
1935
+ oauthError && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Connection Failed", children: oauthError }),
1936
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1937
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
1938
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1939
+ /* @__PURE__ */ jsx(
1940
+ Button,
1941
+ {
1942
+ variant: "light",
1943
+ onClick: handleSaveChanges,
1944
+ loading: updateMutation.isPending,
1945
+ disabled: !hasMetadataChanged || isAuthorizing,
1946
+ children: "Save Changes"
1947
+ }
1948
+ ),
1949
+ /* @__PURE__ */ jsx(
1950
+ Button,
1951
+ {
1952
+ color: "blue",
1953
+ onClick: handleOAuthReconnect,
1954
+ loading: isAuthorizing,
1955
+ leftSection: /* @__PURE__ */ jsx(IconRefresh, { size: 16 }),
1956
+ disabled: updateMutation.isPending,
1957
+ children: "Reconnect OAuth"
1958
+ }
1959
+ )
1960
+ ] })
1961
+ ] })
1962
+ ] }),
1963
+ !isOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
1964
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Current values are encrypted and cannot be displayed. Enter new values below to replace them." }),
1965
+ schema.fields?.map((field) => /* @__PURE__ */ jsx(
1966
+ PasswordInput,
1967
+ {
1968
+ label: `New ${field.label}`,
1969
+ placeholder: field.placeholder || `Enter new ${field.label.toLowerCase()}`,
1970
+ value: fieldValues[field.key] || "",
1971
+ onChange: (e) => handleFieldChange(field.key, e.target.value),
1972
+ description: field.description,
1973
+ disabled: isPending
1974
+ },
1975
+ field.key
1976
+ )),
1977
+ updateMutation.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Update Failed", children: "Failed to update credential" }),
1978
+ /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1979
+ /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
1980
+ /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1981
+ /* @__PURE__ */ jsx(
1982
+ Button,
1983
+ {
1984
+ variant: "light",
1985
+ onClick: handleSaveChanges,
1986
+ loading: updateMutation.isPending,
1987
+ disabled: !hasMetadataChanged,
1988
+ children: "Save Name"
1989
+ }
1990
+ ),
1991
+ /* @__PURE__ */ jsx(Button, { onClick: handleCredentialUpdate, loading: updateMutation.isPending, disabled: !hasFieldValues, children: "Save Credential" })
1992
+ ] })
1993
+ ] })
1994
+ ] })
1995
+ ] }) });
1996
+ }
1997
+ var PROVIDER_ICONS = {
1998
+ slack: IconBrandSlack,
1999
+ attio: IconPlug,
2000
+ // Attio doesn't have a brand icon
2001
+ "google-sheets": IconBrandGoogleDrive,
2002
+ gmail: IconBrandGmail,
2003
+ github: IconBrandGithub,
2004
+ linear: IconRocket,
2005
+ dropbox: IconBrandDropbox
2006
+ };
2007
+ var MOCK_PROVIDERS = [
2008
+ { id: "google-sheets", name: "Google Sheets", available: false },
2009
+ { id: "gmail", name: "Gmail", available: false },
2010
+ { id: "github", name: "GitHub", available: false },
2011
+ { id: "linear", name: "Linear", available: false }
2012
+ ];
2013
+ function OAuthIntegrationsCard({ apiUrl }) {
2014
+ const [selectedProviderId, setSelectedProviderId] = useState(null);
2015
+ const [modalOpened, setModalOpened] = useState(false);
2016
+ const { authorize, isAuthorizing, error } = useOAuthFlow({ apiUrl });
2017
+ const queryClient = useQueryClient();
2018
+ const handleConnect = async (credentialName) => {
2019
+ if (!selectedProviderId) return;
2020
+ try {
2021
+ await authorize(selectedProviderId, credentialName);
2022
+ setModalOpened(false);
2023
+ setSelectedProviderId(null);
2024
+ queryClient.invalidateQueries({ queryKey: ["credentials"] });
2025
+ } catch (err) {
2026
+ console.error("OAuth flow failed:", err);
2027
+ }
2028
+ };
2029
+ const handleOpenModal = (providerId) => {
2030
+ setSelectedProviderId(providerId);
2031
+ setModalOpened(true);
2032
+ };
2033
+ const realProviders = Object.values(OAUTH_PROVIDERS).map((p) => ({
2034
+ ...p,
2035
+ available: true
2036
+ }));
2037
+ const allProviders = [...realProviders, ...MOCK_PROVIDERS];
2038
+ const selectedProvider = selectedProviderId ? OAUTH_PROVIDERS[selectedProviderId] : null;
2039
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2040
+ /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
2041
+ /* @__PURE__ */ jsx(Group, { justify: "space-between", children: /* @__PURE__ */ jsxs("div", { children: [
2042
+ /* @__PURE__ */ jsx(Title, { order: 3, children: "OAuth Integrations" }),
2043
+ /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mt: 4, children: "Connect third-party services using OAuth 2.0" })
2044
+ ] }) }),
2045
+ /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, md: 3 }, children: allProviders.map((provider) => {
2046
+ const Icon = PROVIDER_ICONS[provider.id] || IconPlug;
2047
+ return /* @__PURE__ */ jsx(
2048
+ Card,
2049
+ {
2050
+ withBorder: true,
2051
+ style: {
2052
+ opacity: provider.available ? 1 : 0.6
2053
+ },
2054
+ children: /* @__PURE__ */ jsxs(Stack, { children: [
2055
+ /* @__PURE__ */ jsx(Group, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
2056
+ /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: provider.available ? "blue" : "gray", children: /* @__PURE__ */ jsx(Icon, { size: 20 }) }),
2057
+ /* @__PURE__ */ jsxs("div", { children: [
2058
+ /* @__PURE__ */ jsx(Text, { fw: 600, style: { fontFamily: "var(--mantine-font-family-headings)" }, children: provider.name }),
2059
+ !provider.available && /* @__PURE__ */ jsx(Badge, { color: "gray", variant: "light", size: "xs", mt: 4, children: "Coming Soon" })
2060
+ ] })
2061
+ ] }) }),
2062
+ /* @__PURE__ */ jsx(
2063
+ Button,
2064
+ {
2065
+ variant: "filled",
2066
+ size: "sm",
2067
+ onClick: () => handleOpenModal(provider.id),
2068
+ disabled: !provider.available,
2069
+ fullWidth: true,
2070
+ children: "Connect"
2071
+ }
2072
+ )
2073
+ ] })
2074
+ },
2075
+ provider.id
2076
+ );
2077
+ }) })
2078
+ ] }) }),
2079
+ /* @__PURE__ */ jsx(
2080
+ OAuthConnectModal,
2081
+ {
2082
+ opened: modalOpened,
2083
+ onClose: () => {
2084
+ setModalOpened(false);
2085
+ setSelectedProviderId(null);
2086
+ },
2087
+ provider: selectedProvider,
2088
+ onConnect: handleConnect,
2089
+ error,
2090
+ isLoading: isAuthorizing
2091
+ }
2092
+ )
2093
+ ] });
2094
+ }
2095
+ var settingsManifest = {
2096
+ key: "settings",
2097
+ label: "Settings",
2098
+ navEntry: {
2099
+ label: "Settings",
2100
+ icon: IconSettings,
2101
+ link: "/settings",
2102
+ dataOnboardingTourId: "settings-gear",
2103
+ links: [
2104
+ { label: "Account", link: "/settings/account" },
2105
+ { label: "Appearance", link: "/settings/appearance" },
2106
+ { label: "Organization", link: "/settings/organization" },
2107
+ { label: "Credentials", link: "/settings/credentials" },
2108
+ { label: "API Keys", link: "/settings/api-keys" },
2109
+ { label: "Webhooks", link: "/settings/webhooks" },
2110
+ { label: "Deployments", link: "/settings/deployments" }
2111
+ ]
2112
+ }
2113
+ };
2114
+
2115
+ export { AccountSettings, AppearanceSettings, CreateCredentialModal, CreateWebhookEndpointModal, CredentialList, CredentialSettings, EditCredentialModal, EditWebhookEndpointModal, MemberConfigModal, MembershipFeaturePanel, MembershipStatusBadge, OAuthConnectModal, OAuthIntegrationsCard, OrgMembersList, OrganizationMembershipsList, OrganizationSettings, WebhookEndpointList, WebhookEndpointSettings, WebhookUrlDisplayModal, settingsManifest };