@elevasis/ui 1.26.1 → 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 +3 -3
  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
@@ -1,1453 +1,34 @@
1
- import { useAvailablePresets } from '../../chunk-BS4J2LAW.js';
2
- import { OrganizationMembershipsList, WebhookUrlDisplayModal, MembershipFeaturePanel, MembershipStatusBadge, getCredentialSchema, OAuthConnectModal, buildCredentialValue } from '../../chunk-LR4WVA7W.js';
1
+ export { AccountSettings, AppearanceSettings, CreateWebhookEndpointModal, EditCredentialModal, EditWebhookEndpointModal, MemberConfigModal, OAuthIntegrationsCard, OrgMembersList, OrganizationSettings, WebhookEndpointList, WebhookEndpointSettings, settingsManifest } from '../../chunk-WJ7W7JU4.js';
3
2
  import '../../chunk-PDHTXPSF.js';
4
- import { CustomModal } from '../../chunk-GBMNCNHX.js';
5
- import { PageTitleCaption, EmptyState, CardHeader, ListSkeleton, StatCard } from '../../chunk-MCA6LOGM.js';
3
+ import '../../chunk-GBMNCNHX.js';
4
+ import '../../chunk-MCA6LOGM.js';
6
5
  import '../../chunk-3KMDHCAR.js';
7
6
  import '../../chunk-NNKKBSJN.js';
8
- import { AppShellLoader } from '../../chunk-WWEMNIHW.js';
9
- import '../../chunk-QJ2S46NI.js';
10
- import { useUserMemberships, useUpdateWebhookEndpoint, useDeleteWebhookEndpoint, useCreateWebhookEndpoint, useListWebhookEndpoints, useUpdateMemberConfig, useOrganizationMembers, useUpdateCredential, CredentialSchemas } from '../../chunk-YNGQ7U5H.js';
11
- import { useResources, showErrorNotification } from '../../chunk-ZVJKIJFG.js';
7
+ import '../../chunk-YYBM5LNJ.js';
8
+ import '../../chunk-QTD5HPKD.js';
9
+ import '../../chunk-WLNEJ6JJ.js';
10
+ import '../../chunk-RCQPWA5X.js';
12
11
  import '../../chunk-LXHZYSMQ.js';
13
- import '../../chunk-MHW43EOH.js';
14
12
  import '../../chunk-F6RBK7NJ.js';
15
13
  import '../../chunk-ELJIFLCB.js';
16
- import '../../chunk-L4XXM55J.js';
17
- import '../../chunk-SLVC5OJ2.js';
18
- import '../../chunk-RNP5R5I3.js';
19
- import '../../chunk-RMPXGBNI.js';
14
+ import '../../chunk-5JSR6TL5.js';
20
15
  import '../../chunk-SZHARWKU.js';
21
- import '../../chunk-FEZZ3IDU.js';
16
+ import '../../chunk-TXPUIHX2.js';
22
17
  import '../../chunk-CYXZHBP4.js';
23
- import '../../chunk-R7WLWGPO.js';
18
+ import '../../chunk-ITCEULI5.js';
19
+ import '../../chunk-5COLSYBE.js';
24
20
  import '../../chunk-NVOCKXUQ.js';
25
- import '../../chunk-V7XHGJQZ.js';
21
+ import '../../chunk-MHW43EOH.js';
22
+ import '../../chunk-W4VYXIN7.js';
26
23
  import '../../chunk-QJ2KCHKX.js';
27
- import { formatDateTime, OAUTH_POPUP_CHECK_INTERVAL, OAUTH_FLOW_TIMEOUT } from '../../chunk-IOKL7BKE.js';
24
+ import '../../chunk-QJ2S46NI.js';
25
+ import '../../chunk-SLVC5OJ2.js';
26
+ import '../../chunk-RNP5R5I3.js';
27
+ import '../../chunk-IOKL7BKE.js';
28
28
  import '../../chunk-RWQIFKMJ.js';
29
29
  import '../../chunk-ALA56RGZ.js';
30
- import { useInitialization } from '../../chunk-TUXTSEAF.js';
30
+ import '../../chunk-TUXTSEAF.js';
31
31
  import '../../chunk-DD3CCMCZ.js';
32
- import { useElevasisServices } from '../../chunk-QEPXAWE2.js';
32
+ import '../../chunk-QEPXAWE2.js';
33
33
  import '../../chunk-BRJ3QZ4E.js';
34
34
  import '../../chunk-Q7DJKLEN.js';
35
- import { Paper, Stack, Group, ThemeIcon, Text, Code, CopyButton, Tooltip, ActionIcon, Button, Alert, useMantineColorScheme, SimpleGrid, UnstyledButton, Card, Title, Badge, TextInput, Switch, Table, Divider, Select, Textarea, Center, Loader, PasswordInput } from '@mantine/core';
36
- import { IconUser, IconCheck, IconCopy, IconMail, IconRefresh, IconAlertCircle, IconBuilding, IconPalette, IconSun, IconEye, IconSparkles, IconTrendingUp, IconSearch, IconWebhook, IconExclamationMark, IconPencil, IconClock, IconPlayerPause, IconPlayerPlay, IconTrash, IconAlertTriangle, IconPlus, IconActivity, IconUsers, IconSettings, IconBrandDropbox, IconRocket, IconBrandGithub, IconBrandGmail, IconBrandGoogleDrive, IconPlug, IconBrandSlack, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
37
- import { notifications } from '@mantine/notifications';
38
- import { useMutation, useQueryClient } from '@tanstack/react-query';
39
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
40
- import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
41
- import { create } from 'zustand';
42
- import { useForm } from '@mantine/form';
43
-
44
- // ../core/src/integrations/oauth/provider-registry.ts
45
- var OAUTH_PROVIDERS = {
46
- "google-sheets": {
47
- id: "google-sheets",
48
- name: "Google Sheets",
49
- authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
50
- tokenUrl: "https://oauth2.googleapis.com/token",
51
- authParams: {
52
- access_type: "offline",
53
- // Required for refresh token
54
- prompt: "consent"
55
- // Force consent to get refresh token on reconnect
56
- },
57
- tokenExchange: "form-encoded",
58
- scopes: ["https://www.googleapis.com/auth/spreadsheets"]
59
- },
60
- dropbox: {
61
- id: "dropbox",
62
- name: "Dropbox",
63
- authUrl: "https://www.dropbox.com/oauth2/authorize",
64
- tokenUrl: "https://api.dropboxapi.com/oauth2/token",
65
- authParams: {
66
- token_access_type: "offline"
67
- // Required for refresh token
68
- },
69
- tokenExchange: "form-encoded",
70
- scopes: ["files.content.write", "files.content.read", "files.metadata.write"]
71
- }
72
- };
73
- function AccountSettings({
74
- user,
75
- userProfile,
76
- profileLoading,
77
- onRefetchProfile,
78
- onNavigate
79
- }) {
80
- const { apiRequest } = useElevasisServices();
81
- const userConfig = userProfile?.config;
82
- const hasCompletedOnboarding = userConfig?.onboarding?.completed;
83
- const resetWelcomeFlow = useMutation({
84
- mutationFn: async () => {
85
- return apiRequest("/users/me", {
86
- method: "PATCH",
87
- body: JSON.stringify({
88
- config: {
89
- onboarding: {
90
- completed: false,
91
- completedAt: null,
92
- role: null,
93
- primaryUseCase: null,
94
- experienceLevel: null
95
- }
96
- }
97
- })
98
- });
99
- },
100
- onSuccess: async () => {
101
- notifications.show({
102
- title: "Welcome Flow Reset",
103
- message: "Redirecting to the welcome flow...",
104
- color: "teal"
105
- });
106
- await onRefetchProfile();
107
- onNavigate("/welcome");
108
- },
109
- onError: (err) => {
110
- notifications.show({
111
- title: "Error",
112
- message: err instanceof Error ? err.message : "Failed to reset welcome flow",
113
- color: "red"
114
- });
115
- }
116
- });
117
- const { data: memberships = [], isLoading, error } = useUserMemberships(user?.id || "");
118
- const handleEditRole = (_membershipId) => {
119
- notifications.show({
120
- title: "Edit Role",
121
- message: "Role editing functionality coming soon",
122
- color: "blue"
123
- });
124
- };
125
- const handleLeaveOrganization = (_membershipId) => {
126
- notifications.show({
127
- title: "Leave Organization",
128
- message: "Leave organization functionality coming soon",
129
- color: "orange"
130
- });
131
- };
132
- return /* @__PURE__ */ jsxs(Fragment, { children: [
133
- /* @__PURE__ */ jsx(PageTitleCaption, { title: "Account Settings", caption: "Manage your account and organization memberships" }),
134
- user && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
135
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
136
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", children: /* @__PURE__ */ jsx(IconUser, { size: 18 }) }),
137
- /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
138
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Name" }),
139
- /* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
140
- user.firstName,
141
- " ",
142
- user.lastName
143
- ] })
144
- ] }),
145
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
146
- /* @__PURE__ */ jsx(Code, { children: user.id }),
147
- /* @__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(
148
- ActionIcon,
149
- {
150
- color: copied ? "teal" : "gray",
151
- variant: copied ? "filled" : "subtle",
152
- onClick: copy,
153
- size: "sm",
154
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
155
- }
156
- ) }) })
157
- ] })
158
- ] }),
159
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
160
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconMail, { size: 18 }) }),
161
- /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
162
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Email" }),
163
- /* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: user.email })
164
- ] })
165
- ] }),
166
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", children: [
167
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
168
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "teal", children: /* @__PURE__ */ jsx(IconRefresh, { size: 18 }) }),
169
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
170
- /* @__PURE__ */ jsx(Text, { fw: 600, children: "Welcome Flow" }),
171
- /* @__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" })
172
- ] })
173
- ] }),
174
- /* @__PURE__ */ jsx(
175
- Button,
176
- {
177
- variant: "light",
178
- leftSection: profileLoading ? void 0 : /* @__PURE__ */ jsx(IconRefresh, { size: 16 }),
179
- onClick: () => resetWelcomeFlow.mutate(),
180
- loading: profileLoading || resetWelcomeFlow.isPending,
181
- disabled: profileLoading,
182
- children: profileLoading ? "" : hasCompletedOnboarding ? "Reset & Redo" : "Start"
183
- }
184
- )
185
- ] })
186
- ] }) }),
187
- /* @__PURE__ */ jsx(
188
- OrganizationMembershipsList,
189
- {
190
- memberships,
191
- loading: isLoading,
192
- error,
193
- onEditRole: handleEditRole,
194
- onLeaveOrganization: handleLeaveOrganization
195
- }
196
- ),
197
- error && /* @__PURE__ */ jsxs(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), title: "Error", color: "red", children: [
198
- "Failed to load organization memberships: ",
199
- error.message
200
- ] }),
201
- !isLoading && !error && memberships.length === 0 && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(
202
- EmptyState,
203
- {
204
- icon: IconBuilding,
205
- title: "No Organization Memberships",
206
- description: "You are not currently a member of any organizations. Contact an administrator to be added to an organization."
207
- }
208
- ) })
209
- ] });
210
- }
211
- var useInternalThemeStore = create((set) => ({
212
- preset: "default",
213
- colorScheme: "auto",
214
- setPreset: (preset) => set({ preset }),
215
- setColorScheme: (colorScheme) => set({ colorScheme }),
216
- setTheme: (theme) => set(theme)
217
- }));
218
-
219
- // src/hooks/operations/settings/useUpdateThemePreference.ts
220
- var DEBOUNCE_MS = 500;
221
- function useUpdateThemePreference() {
222
- const { apiRequest } = useElevasisServices();
223
- const setTheme = useInternalThemeStore((s) => s.setTheme);
224
- const timerRef = useRef(null);
225
- const updateTheme = useCallback(
226
- (theme) => {
227
- if (theme.preset !== void 0 && theme.colorScheme !== void 0) {
228
- setTheme({ preset: theme.preset, colorScheme: theme.colorScheme });
229
- } else {
230
- const current = useInternalThemeStore.getState();
231
- setTheme({
232
- preset: theme.preset ?? current.preset,
233
- colorScheme: theme.colorScheme ?? current.colorScheme
234
- });
235
- }
236
- if (timerRef.current) clearTimeout(timerRef.current);
237
- timerRef.current = setTimeout(async () => {
238
- try {
239
- await apiRequest("/users/me", {
240
- method: "PATCH",
241
- body: JSON.stringify({ config: { theme } })
242
- });
243
- } catch (error) {
244
- showErrorNotification(error instanceof Error ? error : new Error("Failed to update appearance"));
245
- }
246
- }, DEBOUNCE_MS);
247
- },
248
- [apiRequest, setTheme]
249
- );
250
- return { updateTheme };
251
- }
252
- var COLOR_SCHEMES = [
253
- { value: "light", label: "Light", icon: /* @__PURE__ */ jsx(IconSun, { size: 16 }) },
254
- { value: "dark", label: "Dark", icon: /* @__PURE__ */ jsx(IconMoon, { size: 16 }) },
255
- { value: "auto", label: "System", icon: /* @__PURE__ */ jsx(IconDeviceDesktop, { size: 16 }) }
256
- ];
257
- function AppearanceSettings({ initialTheme, onThemeChange }) {
258
- const preset = useInternalThemeStore((s) => s.preset);
259
- const colorScheme = useInternalThemeStore((s) => s.colorScheme);
260
- const setInternalTheme = useInternalThemeStore((s) => s.setTheme);
261
- const { updateTheme } = useUpdateThemePreference();
262
- const { setColorScheme: setMantineColorScheme } = useMantineColorScheme();
263
- const availablePresets = useAvailablePresets();
264
- const [modalOpen, setModalOpen] = useState(false);
265
- const hydratedRef = useRef(false);
266
- if (!hydratedRef.current) {
267
- setInternalTheme(initialTheme);
268
- hydratedRef.current = true;
269
- }
270
- useEffect(() => {
271
- if (!hydratedRef.current) return;
272
- onThemeChange?.({ preset, colorScheme });
273
- }, [preset, colorScheme]);
274
- return /* @__PURE__ */ jsxs(Fragment, { children: [
275
- /* @__PURE__ */ jsx(PageTitleCaption, { title: "Appearance", caption: "Customize how the command center looks" }),
276
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
277
- /* @__PURE__ */ jsx(
278
- CardHeader,
279
- {
280
- icon: /* @__PURE__ */ jsx(IconPalette, { size: 18 }),
281
- title: "Theme",
282
- subtitle: "Choose a color palette for the command center"
283
- }
284
- ),
285
- /* @__PURE__ */ jsx(Stack, { gap: "md", children: /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 2, sm: 3, md: 4 }, children: availablePresets.map((p) => {
286
- const isActive = p.value === preset;
287
- return /* @__PURE__ */ jsx(
288
- UnstyledButton,
289
- {
290
- onClick: () => {
291
- if (!isActive) {
292
- updateTheme({ preset: p.value, colorScheme });
293
- }
294
- },
295
- children: /* @__PURE__ */ jsx(
296
- Card,
297
- {
298
- style: {
299
- cursor: "pointer",
300
- border: isActive ? "var(--active-border)" : "1px solid var(--color-border)",
301
- backgroundColor: isActive ? "var(--active-background)" : "var(--color-surface)",
302
- transition: `all var(--duration-fast) var(--easing)`
303
- },
304
- children: /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
305
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "center", children: [
306
- /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", children: p.label }),
307
- /* @__PURE__ */ jsx(
308
- "div",
309
- {
310
- style: {
311
- width: 14,
312
- height: 14,
313
- borderRadius: "50%",
314
- backgroundColor: p.colors[0],
315
- border: "1px solid var(--color-border)",
316
- flexShrink: 0
317
- }
318
- }
319
- )
320
- ] }),
321
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: p.description })
322
- ] })
323
- }
324
- )
325
- },
326
- p.value
327
- );
328
- }) }) })
329
- ] }),
330
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
331
- /* @__PURE__ */ jsx(
332
- CardHeader,
333
- {
334
- icon: /* @__PURE__ */ jsx(IconSun, { size: 18 }),
335
- title: "Color Scheme",
336
- subtitle: "Light, dark, or match your system preference"
337
- }
338
- ),
339
- /* @__PURE__ */ jsx(Stack, { gap: "md", children: /* @__PURE__ */ jsx(SimpleGrid, { cols: 3, children: COLOR_SCHEMES.map((s) => {
340
- const isActive = s.value === colorScheme;
341
- return /* @__PURE__ */ jsx(
342
- UnstyledButton,
343
- {
344
- onClick: () => {
345
- if (!isActive) {
346
- setMantineColorScheme(s.value);
347
- updateTheme({ preset, colorScheme: s.value });
348
- }
349
- },
350
- style: { borderRadius: "var(--mantine-radius-md)" },
351
- children: /* @__PURE__ */ jsx(
352
- Card,
353
- {
354
- padding: "md",
355
- style: {
356
- cursor: "pointer",
357
- border: isActive ? "var(--active-border)" : "1px solid var(--color-border)",
358
- backgroundColor: isActive ? "var(--active-background)" : "var(--color-surface)",
359
- transition: `all var(--duration-fast) var(--easing)`
360
- },
361
- children: /* @__PURE__ */ jsxs(Group, { gap: 6, justify: "center", wrap: "nowrap", children: [
362
- s.icon,
363
- /* @__PURE__ */ jsx(Text, { fw: 600, size: "sm", children: s.label }),
364
- isActive && /* @__PURE__ */ jsx(IconCheck, { size: 14, color: "var(--color-primary)", style: { flexShrink: 0 } })
365
- ] })
366
- }
367
- )
368
- },
369
- s.value
370
- );
371
- }) }) })
372
- ] }),
373
- /* @__PURE__ */ jsxs(Paper, { withBorder: true, children: [
374
- /* @__PURE__ */ jsx(
375
- CardHeader,
376
- {
377
- icon: /* @__PURE__ */ jsx(IconEye, { size: 18 }),
378
- title: "Preview",
379
- subtitle: "See how common UI elements look with your current theme"
380
- }
381
- ),
382
- /* @__PURE__ */ jsxs(Stack, { gap: "lg", children: [
383
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
384
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Page Layout" }),
385
- /* @__PURE__ */ jsx(Card, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
386
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", children: [
387
- /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
388
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Operations Overview" }),
389
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Monitor recent activity across your workspace" })
390
- ] }),
391
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
392
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", children: "Export" }),
393
- /* @__PURE__ */ jsx(Button, { size: "xs", leftSection: /* @__PURE__ */ jsx(IconSparkles, { size: 14 }), children: "New Workflow" }),
394
- /* @__PURE__ */ jsx(Button, { variant: "light", size: "xs", onClick: () => setModalOpen(true), children: "Open Modal Preview" })
395
- ] })
396
- ] }),
397
- /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 3 }, spacing: "sm", children: [
398
- { label: "Active Workflows", value: "24", trend: "+12%" },
399
- { label: "Executions Today", value: "1,284", trend: "+8.3%" },
400
- { label: "Success Rate", value: "98.2%", trend: "+0.4%" }
401
- ].map((stat) => /* @__PURE__ */ jsx(
402
- Card,
403
- {
404
- padding: "sm",
405
- withBorder: true,
406
- style: {
407
- transition: `all var(--duration-fast) var(--easing)`
408
- },
409
- children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
410
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: stat.label }),
411
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-end", wrap: "nowrap", children: [
412
- /* @__PURE__ */ jsx(Text, { size: "xl", fw: 700, children: stat.value }),
413
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", color: "green", leftSection: /* @__PURE__ */ jsx(IconTrendingUp, { size: 10 }), children: stat.trend })
414
- ] })
415
- ] })
416
- },
417
- stat.label
418
- )) })
419
- ] }) })
420
- ] }),
421
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: { base: 1, md: 2 }, spacing: "md", children: [
422
- /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
423
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Buttons" }),
424
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
425
- /* @__PURE__ */ jsx(Button, { size: "xs", children: "Primary" }),
426
- /* @__PURE__ */ jsx(Button, { size: "xs", variant: "light", children: "Light" }),
427
- /* @__PURE__ */ jsx(Button, { size: "xs", variant: "outline", children: "Outline" }),
428
- /* @__PURE__ */ jsx(Button, { size: "xs", variant: "default", children: "Default" }),
429
- /* @__PURE__ */ jsx(Button, { size: "xs", variant: "subtle", children: "Subtle" })
430
- ] })
431
- ] }) }),
432
- /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
433
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Status Badges" }),
434
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
435
- /* @__PURE__ */ jsx(Badge, { variant: "light", children: "Active" }),
436
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: "green", children: "Success" }),
437
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: "yellow", children: "Pending" }),
438
- /* @__PURE__ */ jsx(Badge, { variant: "light", color: "red", children: "Failed" }),
439
- /* @__PURE__ */ jsx(Badge, { variant: "outline", children: "Draft" })
440
- ] })
441
- ] }) }),
442
- /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
443
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Inputs" }),
444
- /* @__PURE__ */ jsx(TextInput, { placeholder: "Search workflows...", leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 14 }), size: "xs" }),
445
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", children: [
446
- /* @__PURE__ */ jsx(Text, { size: "sm", children: "Enable notifications" }),
447
- /* @__PURE__ */ jsx(Switch, { defaultChecked: true, size: "sm" })
448
- ] })
449
- ] }) }),
450
- /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
451
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Typography" }),
452
- /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
453
- /* @__PURE__ */ jsx(Title, { order: 4, children: "Section Heading" }),
454
- /* @__PURE__ */ jsx(Text, { size: "sm", children: "The quick brown fox jumps over the lazy dog." }),
455
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Secondary text with dimmed color for less emphasis." }),
456
- /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", children: [
457
- /* @__PURE__ */ jsx(Text, { span: true, c: "var(--color-primary)", fw: 600, inherit: true, children: "Link" }),
458
- " ",
459
- "styled with primary color token."
460
- ] })
461
- ] })
462
- ] }) })
463
- ] }),
464
- /* @__PURE__ */ jsx(Card, { padding: "md", withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
465
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Table" }),
466
- /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 400, children: /* @__PURE__ */ jsxs(Table, { verticalSpacing: "xs", horizontalSpacing: "sm", children: [
467
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
468
- /* @__PURE__ */ jsx(Table.Th, { children: "Workflow" }),
469
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
470
- /* @__PURE__ */ jsx(Table.Th, { children: "Runs" }),
471
- /* @__PURE__ */ jsx(Table.Th, { children: "Last Run" })
472
- ] }) }),
473
- /* @__PURE__ */ jsx(Table.Tbody, { children: [
474
- { name: "deal-enrichment", status: "Active", color: "green", runs: "482", last: "2m ago" },
475
- { name: "ist-campaign-review", status: "Active", color: "green", runs: "156", last: "14m ago" },
476
- { name: "tomba-email-finder", status: "Paused", color: "yellow", runs: "89", last: "3h ago" }
477
- ].map((row) => /* @__PURE__ */ jsxs(Table.Tr, { children: [
478
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", children: row.name }) }),
479
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: row.color, size: "xs", children: row.status }) }),
480
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: row.runs }) }),
481
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: row.last }) })
482
- ] }, row.name)) })
483
- ] }) })
484
- ] }) }),
485
- /* @__PURE__ */ jsx(Divider, {}),
486
- /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
487
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", tt: "uppercase", fw: 600, children: "Alerts" }),
488
- /* @__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." }),
489
- /* @__PURE__ */ jsx(Alert, { variant: "light", color: "green", icon: /* @__PURE__ */ jsx(IconCheck, { size: 16 }), title: "Success", children: "Operation completed successfully." })
490
- ] })
491
- ] })
492
- ] }),
493
- /* @__PURE__ */ jsx(CustomModal, { opened: modalOpen, onClose: () => setModalOpen(false), size: "md", children: /* @__PURE__ */ jsxs(Stack, { gap: "md", p: "md", children: [
494
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Modal Preview" }),
495
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
496
- "Modals should sit visibly above the page background. Elevation comes from the",
497
- " ",
498
- /* @__PURE__ */ jsx(Text, { span: true, ff: "monospace", size: "sm", children: "--color-elevated" }),
499
- " ",
500
- "token, which every preset defines as an opaque surface."
501
- ] }),
502
- /* @__PURE__ */ jsx(TextInput, { label: "Example field", placeholder: "Type something...", size: "xs" }),
503
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", children: [
504
- /* @__PURE__ */ jsx(Button, { variant: "default", size: "xs", onClick: () => setModalOpen(false), children: "Cancel" }),
505
- /* @__PURE__ */ jsx(Button, { size: "xs", onClick: () => setModalOpen(false), children: "Confirm" })
506
- ] })
507
- ] }) })
508
- ] });
509
- }
510
- function EditWebhookEndpointModal({ opened, endpoint, onClose }) {
511
- const updateEndpoint = useUpdateWebhookEndpoint();
512
- const { data: resourcesData } = useResources();
513
- const resourceOptions = useMemo(() => {
514
- if (!resourcesData) return [];
515
- const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
516
- value: w.resourceId,
517
- label: `Workflow: ${w.resourceId}`
518
- }));
519
- const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
520
- value: a.resourceId,
521
- label: `Agent: ${a.resourceId}`
522
- }));
523
- return [...workflowOptions, ...agentOptions];
524
- }, [resourcesData]);
525
- const form = useForm({
526
- initialValues: {
527
- name: "",
528
- resourceId: ""
529
- },
530
- validate: {
531
- name: (value) => {
532
- if (!value.trim()) return "Name is required";
533
- if (value.trim().length < 2) return "Name must be at least 2 characters";
534
- if (value.trim().length > 100) return "Name must be less than 100 characters";
535
- return null;
536
- }
537
- }
538
- });
539
- useEffect(() => {
540
- if (endpoint && opened) {
541
- form.setValues({
542
- name: endpoint.name,
543
- resourceId: endpoint.resourceId ?? ""
544
- });
545
- }
546
- }, [endpoint, opened]);
547
- const handleSubmit = async (values) => {
548
- if (!endpoint) return;
549
- try {
550
- await updateEndpoint.mutateAsync({
551
- endpointId: endpoint.id,
552
- data: {
553
- name: values.name.trim(),
554
- resourceId: values.resourceId || void 0
555
- }
556
- });
557
- onClose();
558
- } catch (error) {
559
- console.error("Failed to update webhook endpoint:", error);
560
- }
561
- };
562
- const handleClose = () => {
563
- if (!updateEndpoint.isPending) {
564
- onClose();
565
- }
566
- };
567
- 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: [
568
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Edit Webhook Endpoint" }),
569
- /* @__PURE__ */ jsx(
570
- TextInput,
571
- {
572
- label: "Name",
573
- description: "A descriptive label for this endpoint",
574
- leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
575
- required: true,
576
- ...form.getInputProps("name"),
577
- disabled: updateEndpoint.isPending
578
- }
579
- ),
580
- /* @__PURE__ */ jsx(
581
- Select,
582
- {
583
- label: "Target Resource",
584
- description: "The workflow or agent to trigger on each inbound request",
585
- placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
586
- data: resourceOptions,
587
- searchable: true,
588
- clearable: true,
589
- ...form.getInputProps("resourceId"),
590
- disabled: !resourcesData || updateEndpoint.isPending
591
- }
592
- ),
593
- 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" }),
594
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
595
- /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: updateEndpoint.isPending, children: "Cancel" }),
596
- /* @__PURE__ */ jsx(Button, { type: "submit", loading: updateEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconPencil, { size: 16 }), children: "Save Changes" })
597
- ] })
598
- ] }) }) });
599
- }
600
- function getWebhookUrl(apiUrl, key) {
601
- return `${apiUrl}/api/webhooks/inbound/${key}`;
602
- }
603
- function WebhookEndpointList({ endpoints, isLoading, apiUrl }) {
604
- const [deleteTarget, setDeleteTarget] = useState(null);
605
- const [editTarget, setEditTarget] = useState(null);
606
- const deleteMutation = useDeleteWebhookEndpoint();
607
- const updateMutation = useUpdateWebhookEndpoint();
608
- const handleDelete = () => {
609
- if (deleteTarget) {
610
- deleteMutation.mutate(deleteTarget.id);
611
- setDeleteTarget(null);
612
- }
613
- };
614
- const handleCloseDelete = () => {
615
- if (!deleteMutation.isPending) {
616
- setDeleteTarget(null);
617
- }
618
- };
619
- const handleToggleStatus = (endpoint) => {
620
- const newStatus = endpoint.status === "active" ? "paused" : "active";
621
- updateMutation.mutate({ endpointId: endpoint.id, data: { status: newStatus } });
622
- };
623
- if (isLoading) {
624
- return /* @__PURE__ */ jsx(ListSkeleton, { rows: 3, rowHeight: 50 });
625
- }
626
- if (endpoints.length === 0) {
627
- return /* @__PURE__ */ jsx(
628
- EmptyState,
629
- {
630
- icon: IconWebhook,
631
- title: "No webhook endpoints yet",
632
- description: "Create your first webhook endpoint to let third-party apps trigger your resources"
633
- }
634
- );
635
- }
636
- return /* @__PURE__ */ jsxs(Fragment, { children: [
637
- /* @__PURE__ */ jsxs(Table, { children: [
638
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
639
- /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
640
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
641
- /* @__PURE__ */ jsx(Table.Th, { children: "Target Resource" }),
642
- /* @__PURE__ */ jsx(Table.Th, { children: "URL" }),
643
- /* @__PURE__ */ jsx(Table.Th, { children: "Last Triggered" }),
644
- /* @__PURE__ */ jsx(Table.Th, { children: "Requests" }),
645
- /* @__PURE__ */ jsx(Table.Th, { w: 120 })
646
- ] }) }),
647
- /* @__PURE__ */ jsx(Table.Tbody, { children: endpoints.map((endpoint) => {
648
- const webhookUrl = getWebhookUrl(apiUrl, endpoint.key);
649
- const truncatedUrl = webhookUrl.length > 50 ? `...${endpoint.key.slice(-16)}` : webhookUrl;
650
- const isToggling = updateMutation.isPending;
651
- return /* @__PURE__ */ jsxs(Table.Tr, { children: [
652
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
653
- /* @__PURE__ */ jsx(IconWebhook, { size: 16, style: { opacity: 0.6 } }),
654
- /* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
655
- /* @__PURE__ */ jsx(Text, { fw: 500, children: endpoint.name }),
656
- endpoint.description && /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: endpoint.description })
657
- ] })
658
- ] }) }),
659
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { color: endpoint.status === "active" ? "teal" : "gray", variant: "light", children: endpoint.status }) }),
660
- /* @__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" }) }),
661
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Paper, { withBorder: true, style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
662
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", ff: "monospace", style: { userSelect: "all" }, children: truncatedUrl }),
663
- /* @__PURE__ */ jsx(CopyButton, { value: webhookUrl, timeout: 2e3, children: ({ copied, copy }) => /* @__PURE__ */ jsx(Tooltip, { label: copied ? "Copied!" : "Copy URL", position: "top", children: /* @__PURE__ */ jsx(
664
- ActionIcon,
665
- {
666
- color: copied ? "teal" : "gray",
667
- variant: copied ? "filled" : "subtle",
668
- onClick: copy,
669
- size: "sm",
670
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 12 }) : /* @__PURE__ */ jsx(IconCopy, { size: 12 })
671
- }
672
- ) }) })
673
- ] }) }),
674
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
675
- /* @__PURE__ */ jsx(IconClock, { size: 14, style: { opacity: 0.5 } }),
676
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: formatDateTime(endpoint.lastTriggeredAt) })
677
- ] }) }),
678
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", children: endpoint.requestCount.toLocaleString() }) }),
679
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsxs(Group, { gap: "xs", justify: "flex-end", children: [
680
- /* @__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 }) }) }),
681
- /* @__PURE__ */ jsx(
682
- Tooltip,
683
- {
684
- label: endpoint.status === "active" ? "Pause endpoint" : "Resume endpoint",
685
- position: "left",
686
- children: /* @__PURE__ */ jsx(
687
- ActionIcon,
688
- {
689
- variant: "subtle",
690
- color: endpoint.status === "active" ? "yellow" : "teal",
691
- onClick: () => handleToggleStatus(endpoint),
692
- disabled: isToggling,
693
- children: endpoint.status === "active" ? /* @__PURE__ */ jsx(IconPlayerPause, { size: 16 }) : /* @__PURE__ */ jsx(IconPlayerPlay, { size: 16 })
694
- }
695
- )
696
- }
697
- ),
698
- /* @__PURE__ */ jsx(Tooltip, { label: "Delete endpoint", position: "left", children: /* @__PURE__ */ jsx(
699
- ActionIcon,
700
- {
701
- variant: "subtle",
702
- color: "red",
703
- onClick: () => setDeleteTarget(endpoint),
704
- disabled: deleteMutation.isPending,
705
- children: /* @__PURE__ */ jsx(IconTrash, { size: 16 })
706
- }
707
- ) })
708
- ] }) })
709
- ] }, endpoint.id);
710
- }) })
711
- ] }),
712
- /* @__PURE__ */ jsx(EditWebhookEndpointModal, { opened: !!editTarget, endpoint: editTarget, onClose: () => setEditTarget(null) }),
713
- /* @__PURE__ */ jsx(CustomModal, { opened: !!deleteTarget, onClose: handleCloseDelete, size: "md", loading: deleteMutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
714
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
715
- /* @__PURE__ */ jsx(IconAlertTriangle, { size: 24, color: "var(--color-error)" }),
716
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Delete Webhook Endpoint" })
717
- ] }),
718
- /* @__PURE__ */ jsxs(Text, { size: "sm", children: [
719
- "Are you sure you want to delete",
720
- " ",
721
- /* @__PURE__ */ jsxs(Text, { span: true, fw: 600, children: [
722
- '"',
723
- deleteTarget?.name,
724
- '"'
725
- ] }),
726
- "?"
727
- ] }),
728
- /* @__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." }),
729
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
730
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleCloseDelete, disabled: deleteMutation.isPending, children: "Cancel" }),
731
- /* @__PURE__ */ jsx(
732
- Button,
733
- {
734
- color: "red",
735
- onClick: handleDelete,
736
- loading: deleteMutation.isPending,
737
- leftSection: /* @__PURE__ */ jsx(IconTrash, { size: 16 }),
738
- children: "Delete Endpoint"
739
- }
740
- )
741
- ] })
742
- ] }) })
743
- ] });
744
- }
745
- function CreateWebhookEndpointModal({ opened, onClose, onSuccess }) {
746
- const createEndpoint = useCreateWebhookEndpoint();
747
- const { data: resourcesData } = useResources();
748
- const resourceOptions = useMemo(() => {
749
- if (!resourcesData) return [];
750
- const workflowOptions = (resourcesData.workflows || []).filter((w) => w?.resourceId).map((w) => ({
751
- value: w.resourceId,
752
- label: `Workflow: ${w.resourceId}`
753
- }));
754
- const agentOptions = (resourcesData.agents || []).filter((a) => a?.resourceId).map((a) => ({
755
- value: a.resourceId,
756
- label: `Agent: ${a.resourceId}`
757
- }));
758
- return [...workflowOptions, ...agentOptions];
759
- }, [resourcesData]);
760
- const form = useForm({
761
- initialValues: {
762
- name: "",
763
- resourceId: "",
764
- description: ""
765
- },
766
- validate: {
767
- name: (value) => {
768
- if (!value.trim()) return "Name is required";
769
- if (value.trim().length < 2) return "Name must be at least 2 characters";
770
- if (value.trim().length > 100) return "Name must be less than 100 characters";
771
- return null;
772
- }
773
- }
774
- });
775
- const handleSubmit = async (values) => {
776
- try {
777
- const result = await createEndpoint.mutateAsync({
778
- name: values.name.trim(),
779
- ...values.resourceId ? { resourceId: values.resourceId } : {},
780
- ...values.description.trim() ? { description: values.description.trim() } : {}
781
- });
782
- form.reset();
783
- onSuccess(result);
784
- } catch (error) {
785
- console.error("Failed to create webhook endpoint:", error);
786
- }
787
- };
788
- const handleClose = () => {
789
- if (!createEndpoint.isPending) {
790
- form.reset();
791
- onClose();
792
- }
793
- };
794
- 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: [
795
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Create Webhook Endpoint" }),
796
- /* @__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." }),
797
- /* @__PURE__ */ jsx(
798
- TextInput,
799
- {
800
- label: "Name",
801
- description: "A descriptive label for this endpoint",
802
- placeholder: "e.g., Zapier Lead Intake, Typeform Responses",
803
- leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }),
804
- required: true,
805
- ...form.getInputProps("name"),
806
- disabled: createEndpoint.isPending
807
- }
808
- ),
809
- /* @__PURE__ */ jsx(
810
- Select,
811
- {
812
- label: "Target Resource",
813
- description: "The workflow or agent to trigger on each inbound request (can be set later)",
814
- placeholder: resourcesData ? "Select a workflow or agent" : "Loading resources...",
815
- data: resourceOptions,
816
- searchable: true,
817
- clearable: true,
818
- ...form.getInputProps("resourceId"),
819
- disabled: !resourcesData || createEndpoint.isPending
820
- }
821
- ),
822
- /* @__PURE__ */ jsx(
823
- Textarea,
824
- {
825
- label: "Description",
826
- description: "Optional notes about what this endpoint is for",
827
- placeholder: "e.g., Receives new lead submissions from Zapier",
828
- rows: 3,
829
- ...form.getInputProps("description"),
830
- disabled: createEndpoint.isPending
831
- }
832
- ),
833
- 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" }),
834
- /* @__PURE__ */ jsxs(Group, { justify: "flex-end", gap: "sm", children: [
835
- /* @__PURE__ */ jsx(Button, { variant: "subtle", onClick: handleClose, disabled: createEndpoint.isPending, children: "Cancel" }),
836
- /* @__PURE__ */ jsx(Button, { type: "submit", loading: createEndpoint.isPending, leftSection: /* @__PURE__ */ jsx(IconWebhook, { size: 16 }), children: "Create Endpoint" })
837
- ] })
838
- ] }) }) });
839
- }
840
- function getWebhookUrl2(apiUrl, key) {
841
- return `${apiUrl}/api/webhooks/inbound/${key}`;
842
- }
843
- function WebhookEndpointSettings({ apiUrl }) {
844
- const { organizationReady } = useInitialization();
845
- const [showCreateModal, setShowCreateModal] = useState(false);
846
- const [newEndpoint, setNewEndpoint] = useState(null);
847
- const { data: endpoints = [], isLoading } = useListWebhookEndpoints();
848
- if (!organizationReady) return /* @__PURE__ */ jsx(AppShellLoader, {});
849
- const handleEndpointCreated = (endpoint) => {
850
- setNewEndpoint(endpoint);
851
- setShowCreateModal(false);
852
- };
853
- const activeCount = endpoints.filter((e) => e.status === "active").length;
854
- const totalRequests = endpoints.reduce((sum, e) => sum + e.requestCount, 0);
855
- return /* @__PURE__ */ jsxs(Fragment, { children: [
856
- /* @__PURE__ */ jsx(
857
- PageTitleCaption,
858
- {
859
- title: "Webhooks",
860
- caption: "Create webhook URLs that third-party apps can send data to, triggering your resources.",
861
- rightSection: /* @__PURE__ */ jsx(Button, { leftSection: /* @__PURE__ */ jsx(IconPlus, { size: 16 }), onClick: () => setShowCreateModal(true), variant: "light", children: "Create Endpoint" })
862
- }
863
- ),
864
- /* @__PURE__ */ jsxs(SimpleGrid, { cols: 2, children: [
865
- /* @__PURE__ */ jsx(
866
- StatCard,
867
- {
868
- variant: "hero",
869
- icon: IconWebhook,
870
- value: activeCount,
871
- label: "Active Endpoints",
872
- isLoading
873
- }
874
- ),
875
- /* @__PURE__ */ jsx(
876
- StatCard,
877
- {
878
- variant: "hero",
879
- icon: IconActivity,
880
- value: totalRequests,
881
- label: "Total Requests",
882
- isLoading
883
- }
884
- )
885
- ] }),
886
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsx(WebhookEndpointList, { endpoints, isLoading, apiUrl }) }),
887
- /* @__PURE__ */ jsx(
888
- CreateWebhookEndpointModal,
889
- {
890
- opened: showCreateModal,
891
- onClose: () => setShowCreateModal(false),
892
- onSuccess: handleEndpointCreated
893
- }
894
- ),
895
- newEndpoint && /* @__PURE__ */ jsx(
896
- WebhookUrlDisplayModal,
897
- {
898
- opened: true,
899
- endpoint: newEndpoint,
900
- webhookUrl: getWebhookUrl2(apiUrl, newEndpoint.key),
901
- onClose: () => setNewEndpoint(null)
902
- }
903
- )
904
- ] });
905
- }
906
- function MemberConfigModal({ opened, onClose, member }) {
907
- const [localConfig, setLocalConfig] = useState(void 0);
908
- const mutation = useUpdateMemberConfig();
909
- useEffect(() => {
910
- if (member) {
911
- setLocalConfig(member.config);
912
- }
913
- }, [member]);
914
- useEffect(() => {
915
- if (!opened) {
916
- setLocalConfig(void 0);
917
- }
918
- }, [opened]);
919
- if (!member) return null;
920
- const handleSave = async () => {
921
- if (!localConfig) {
922
- onClose();
923
- return;
924
- }
925
- try {
926
- await mutation.mutateAsync({
927
- membershipId: member.id,
928
- config: localConfig
929
- });
930
- onClose();
931
- } catch {
932
- }
933
- };
934
- const handleClose = () => {
935
- if (!mutation.isPending) {
936
- onClose();
937
- }
938
- };
939
- return /* @__PURE__ */ jsx(CustomModal, { opened, onClose: handleClose, size: "md", loading: mutation.isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
940
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Configure Member Access" }),
941
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
942
- /* @__PURE__ */ jsx(Text, { fw: 500, children: member.name }),
943
- /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: member.role })
944
- ] }),
945
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.email }),
946
- /* @__PURE__ */ jsx(
947
- MembershipFeaturePanel,
948
- {
949
- currentConfig: localConfig ?? member.config,
950
- onConfigChange: (newConfig) => setLocalConfig(newConfig),
951
- disabled: mutation.isPending
952
- }
953
- ),
954
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", mt: "md", children: [
955
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: mutation.isPending, children: "Cancel" }),
956
- /* @__PURE__ */ jsx(Button, { onClick: handleSave, loading: mutation.isPending, children: "Save Changes" })
957
- ] })
958
- ] }) });
959
- }
960
- function transformMemberForModal(member) {
961
- const firstName = member.user?.firstName || "";
962
- const lastName = member.user?.lastName || "";
963
- const name = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
964
- return {
965
- id: member.id,
966
- name,
967
- email: member.user?.email || "Unknown",
968
- role: member.role.slug,
969
- config: member.config
970
- };
971
- }
972
- function OrgMembersList({ orgId }) {
973
- const { data: members, isLoading, error } = useOrganizationMembers(orgId);
974
- const [selectedMember, setSelectedMember] = useState(null);
975
- const [modalOpened, setModalOpened] = useState(false);
976
- const handleConfigClick = (member) => {
977
- setSelectedMember(member);
978
- setModalOpened(true);
979
- };
980
- const handleModalClose = () => {
981
- setModalOpened(false);
982
- setSelectedMember(null);
983
- };
984
- if (isLoading) {
985
- return /* @__PURE__ */ jsx(Center, { py: "xl", children: /* @__PURE__ */ jsxs(Stack, { align: "center", gap: "sm", children: [
986
- /* @__PURE__ */ jsx(Loader, { size: "md" }),
987
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Loading members..." })
988
- ] }) });
989
- }
990
- if (error) {
991
- 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." });
992
- }
993
- if (!members || members.length === 0) {
994
- return /* @__PURE__ */ jsx(EmptyState, { icon: IconUsers, title: "No members found" });
995
- }
996
- return /* @__PURE__ */ jsxs(Fragment, { children: [
997
- /* @__PURE__ */ jsx(Table.ScrollContainer, { minWidth: 500, children: /* @__PURE__ */ jsxs(Table, { children: [
998
- /* @__PURE__ */ jsx(Table.Thead, { children: /* @__PURE__ */ jsxs(Table.Tr, { children: [
999
- /* @__PURE__ */ jsx(Table.Th, { children: "Name" }),
1000
- /* @__PURE__ */ jsx(Table.Th, { children: "Email" }),
1001
- /* @__PURE__ */ jsx(Table.Th, { children: "Role" }),
1002
- /* @__PURE__ */ jsx(Table.Th, { children: "Status" }),
1003
- /* @__PURE__ */ jsx(Table.Th, { children: "Actions" })
1004
- ] }) }),
1005
- /* @__PURE__ */ jsx(Table.Tbody, { children: members.map((member) => {
1006
- const firstName = member.user?.firstName || "";
1007
- const lastName = member.user?.lastName || "";
1008
- const displayName = firstName && lastName ? `${firstName} ${lastName}` : member.user?.email || "Unknown User";
1009
- return /* @__PURE__ */ jsxs(Table.Tr, { children: [
1010
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { fw: 500, size: "sm", children: displayName }) }),
1011
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: member.user?.email || "Unknown" }) }),
1012
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Badge, { variant: "light", color: "blue", size: "sm", children: member.role.slug }) }),
1013
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(MembershipStatusBadge, { status: member.status }) }),
1014
- /* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx(Group, { gap: "xs", children: /* @__PURE__ */ jsx(
1015
- ActionIcon,
1016
- {
1017
- variant: "light",
1018
- size: "sm",
1019
- color: "gray",
1020
- onClick: () => handleConfigClick(member),
1021
- title: "Configure member access",
1022
- children: /* @__PURE__ */ jsx(IconSettings, { size: 14 })
1023
- }
1024
- ) }) })
1025
- ] }, member.id);
1026
- }) })
1027
- ] }) }),
1028
- /* @__PURE__ */ jsx(
1029
- MemberConfigModal,
1030
- {
1031
- opened: modalOpened,
1032
- onClose: handleModalClose,
1033
- member: selectedMember ? transformMemberForModal(selectedMember) : null
1034
- }
1035
- )
1036
- ] });
1037
- }
1038
- function OrganizationSettings({ user, currentMembership, isOrgAdmin }) {
1039
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1040
- /* @__PURE__ */ jsx(PageTitleCaption, { title: "Organization Settings", caption: "Manage your current organization" }),
1041
- currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1042
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1043
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "green", children: /* @__PURE__ */ jsx(IconBuilding, { size: 18 }) }),
1044
- /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1045
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Current Organization" }),
1046
- /* @__PURE__ */ jsx(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: currentMembership.organization.name })
1047
- ] }),
1048
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1049
- /* @__PURE__ */ jsx(Code, { children: currentMembership.organization.id }),
1050
- /* @__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(
1051
- ActionIcon,
1052
- {
1053
- color: copied ? "teal" : "gray",
1054
- variant: copied ? "filled" : "subtle",
1055
- onClick: copy,
1056
- size: "sm",
1057
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1058
- }
1059
- ) }) })
1060
- ] })
1061
- ] }),
1062
- user && /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1063
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: "blue", children: /* @__PURE__ */ jsx(IconUsers, { size: 18 }) }),
1064
- /* @__PURE__ */ jsxs(Stack, { gap: 2, style: { flex: 1 }, children: [
1065
- /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", children: "Signed in as" }),
1066
- /* @__PURE__ */ jsxs(Text, { size: "lg", fw: 600, style: { fontFamily: "var(--elevasis-font-family-subtitle)" }, children: [
1067
- user.firstName,
1068
- " ",
1069
- user.lastName,
1070
- " \u2022 ",
1071
- user.email
1072
- ] })
1073
- ] }),
1074
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1075
- /* @__PURE__ */ jsx(Code, { children: currentMembership.userId }),
1076
- /* @__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(
1077
- ActionIcon,
1078
- {
1079
- color: copied ? "teal" : "gray",
1080
- variant: copied ? "filled" : "subtle",
1081
- onClick: copy,
1082
- size: "sm",
1083
- children: copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
1084
- }
1085
- ) }) })
1086
- ] })
1087
- ] })
1088
- ] }) }),
1089
- isOrgAdmin && currentMembership?.organization && /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { gap: "md", children: [
1090
- /* @__PURE__ */ jsx(Title, { order: 3, children: "Manage Members" }),
1091
- /* @__PURE__ */ jsx(Text, { c: "dimmed", size: "sm", children: "Configure feature access for organization members." }),
1092
- /* @__PURE__ */ jsx(OrgMembersList, { orgId: currentMembership.organization.id })
1093
- ] }) })
1094
- ] });
1095
- }
1096
- function useOAuthFlow({ apiUrl }) {
1097
- const [isAuthorizing, setIsAuthorizing] = useState(false);
1098
- const [error, setError] = useState(null);
1099
- const { organizationId, isReady } = useElevasisServices();
1100
- const authorize = useCallback(
1101
- async (provider, credentialName) => {
1102
- if (!isReady || !organizationId) {
1103
- setError("Organization context not ready");
1104
- return;
1105
- }
1106
- setIsAuthorizing(true);
1107
- setError(null);
1108
- try {
1109
- const authorizeUrl = new URL(`${apiUrl}/api/oauth/authorize/${provider}`);
1110
- authorizeUrl.searchParams.set("credentialName", credentialName);
1111
- authorizeUrl.searchParams.set("organizationId", organizationId);
1112
- const width = 600;
1113
- const height = 700;
1114
- const left = Math.round(window.screen.width / 2 - width / 2);
1115
- const top = Math.round(window.screen.height / 2 - height / 2);
1116
- const popup = window.open(
1117
- authorizeUrl.toString(),
1118
- `oauth-${provider}`,
1119
- `width=${width},height=${height},left=${left},top=${top}`
1120
- );
1121
- if (!popup) {
1122
- throw new Error("popup_blocked");
1123
- }
1124
- await waitForOAuthCallback(popup);
1125
- } catch (err) {
1126
- const rawError = err instanceof Error ? err.message : "unknown";
1127
- if (rawError.includes("popup_blocked")) {
1128
- setError("Please enable popups and try again");
1129
- } else if (rawError.includes("cancelled")) {
1130
- setError("Authorization cancelled");
1131
- } else if (rawError.includes("timeout")) {
1132
- setError("Authorization timed out. Please try again.");
1133
- } else if (rawError.includes("token_exchange_failed")) {
1134
- setError("Failed to obtain access token. Please try again.");
1135
- } else if (rawError.includes("invalid_state")) {
1136
- setError("Security validation failed. Please try again.");
1137
- } else if (rawError.includes("state_expired")) {
1138
- setError("Authorization expired. Please try again.");
1139
- } else {
1140
- setError(`Failed to connect: ${rawError}`);
1141
- }
1142
- throw err;
1143
- } finally {
1144
- setIsAuthorizing(false);
1145
- }
1146
- },
1147
- [isReady, organizationId, apiUrl]
1148
- );
1149
- const waitForOAuthCallback = (popup) => {
1150
- return new Promise((resolve, reject) => {
1151
- const messageHandler = (event) => {
1152
- const expectedOrigin = window.location.origin;
1153
- if (event.origin !== expectedOrigin) {
1154
- return;
1155
- }
1156
- if (event.data.type === "oauth-complete") {
1157
- cleanup();
1158
- if (event.data.status === "success") {
1159
- resolve();
1160
- } else {
1161
- reject(new Error(event.data.error || "oauth_failed"));
1162
- }
1163
- }
1164
- };
1165
- window.addEventListener("message", messageHandler);
1166
- const checkClosed = setInterval(() => {
1167
- if (popup.closed) {
1168
- cleanup();
1169
- reject(new Error("cancelled"));
1170
- }
1171
- }, OAUTH_POPUP_CHECK_INTERVAL);
1172
- const timeout = setTimeout(() => {
1173
- cleanup();
1174
- popup.close();
1175
- reject(new Error("timeout"));
1176
- }, OAUTH_FLOW_TIMEOUT);
1177
- const cleanup = () => {
1178
- clearInterval(checkClosed);
1179
- clearTimeout(timeout);
1180
- window.removeEventListener("message", messageHandler);
1181
- };
1182
- });
1183
- };
1184
- return { authorize, isAuthorizing, error };
1185
- }
1186
- function EditCredentialModal({ apiUrl, credential, onClose }) {
1187
- const [credentialName, setCredentialName] = useState("");
1188
- const [nameError, setNameError] = useState(null);
1189
- const [fieldValues, setFieldValues] = useState({});
1190
- const { authorize, isAuthorizing, error: oauthError } = useOAuthFlow({ apiUrl });
1191
- const updateMutation = useUpdateCredential();
1192
- useEffect(() => {
1193
- if (credential) {
1194
- setCredentialName(credential.name);
1195
- }
1196
- }, [credential]);
1197
- if (!credential) return null;
1198
- const credentialType = credential.type || "api-key";
1199
- const schema = getCredentialSchema(credentialType);
1200
- const provider = credential.provider;
1201
- const isOAuth = !!provider || schema.type === "oauth";
1202
- const hasMetadataChanged = credentialName !== credential.name;
1203
- const isPending = isAuthorizing || updateMutation.isPending;
1204
- const handleClose = () => {
1205
- if (!isPending) {
1206
- onClose();
1207
- }
1208
- };
1209
- const handleOAuthReconnect = async () => {
1210
- if (!provider) return;
1211
- try {
1212
- await authorize(provider, credential.name);
1213
- onClose();
1214
- } catch (err) {
1215
- console.error("Reconnect failed:", err);
1216
- }
1217
- };
1218
- const handleSaveChanges = async () => {
1219
- const nameValidation = CredentialSchemas.UpdateRequest.shape.name.safeParse(credentialName);
1220
- if (!nameValidation.success) {
1221
- setNameError(nameValidation.error.issues[0]?.message || "Invalid credential name");
1222
- return;
1223
- }
1224
- setNameError(null);
1225
- const updateData = { name: credentialName };
1226
- const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
1227
- if (!validationResult.success) {
1228
- console.error("Validation failed:", validationResult.error);
1229
- return;
1230
- }
1231
- try {
1232
- await updateMutation.mutateAsync({
1233
- credentialId: credential.id,
1234
- updates: updateData
1235
- });
1236
- onClose();
1237
- } catch (err) {
1238
- console.error("Save changes failed:", err);
1239
- }
1240
- };
1241
- const handleCredentialUpdate = async () => {
1242
- const hasValues = Object.values(fieldValues).some((v) => v.trim());
1243
- if (!hasValues) return;
1244
- try {
1245
- const credentialValue = buildCredentialValue(credentialType, fieldValues);
1246
- const updateData = { value: credentialValue };
1247
- const validationResult = CredentialSchemas.UpdateRequest.safeParse(updateData);
1248
- if (!validationResult.success) {
1249
- console.error("Validation failed:", validationResult.error);
1250
- return;
1251
- }
1252
- await updateMutation.mutateAsync({
1253
- credentialId: credential.id,
1254
- updates: updateData
1255
- });
1256
- onClose();
1257
- } catch (err) {
1258
- console.error("Update failed:", err);
1259
- }
1260
- };
1261
- const handleFieldChange = (key, value) => {
1262
- setFieldValues((prev) => ({ ...prev, [key]: value }));
1263
- };
1264
- const handleNameChange = (value) => {
1265
- setCredentialName(value);
1266
- if (nameError) setNameError(null);
1267
- };
1268
- const hasFieldValues = Object.values(fieldValues).some((v) => v.trim());
1269
- return /* @__PURE__ */ jsx(CustomModal, { opened: true, onClose: handleClose, loading: isPending, children: /* @__PURE__ */ jsxs(Stack, { children: [
1270
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1271
- /* @__PURE__ */ jsx(IconSettings, { size: 24 }),
1272
- /* @__PURE__ */ jsx(Title, { order: 3, children: isOAuth ? "Manage OAuth Credential" : "Edit API Key" })
1273
- ] }),
1274
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
1275
- /* @__PURE__ */ jsx(Text, { size: "sm", fw: 500, children: "Type:" }),
1276
- /* @__PURE__ */ jsx(Badge, { variant: "light", size: "sm", children: schema.label })
1277
- ] }),
1278
- /* @__PURE__ */ jsx(
1279
- TextInput,
1280
- {
1281
- label: "Name",
1282
- value: credentialName,
1283
- onChange: (e) => handleNameChange(e.target.value),
1284
- disabled: isPending,
1285
- maxLength: 100,
1286
- description: "Lowercase letters, numbers, and hyphens only (e.g., gmail-dev, notion-prod)",
1287
- error: nameError
1288
- }
1289
- ),
1290
- isOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
1291
- /* @__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.' }),
1292
- oauthError && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Connection Failed", children: oauthError }),
1293
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1294
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
1295
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1296
- /* @__PURE__ */ jsx(
1297
- Button,
1298
- {
1299
- variant: "light",
1300
- onClick: handleSaveChanges,
1301
- loading: updateMutation.isPending,
1302
- disabled: !hasMetadataChanged || isAuthorizing,
1303
- children: "Save Changes"
1304
- }
1305
- ),
1306
- /* @__PURE__ */ jsx(
1307
- Button,
1308
- {
1309
- color: "blue",
1310
- onClick: handleOAuthReconnect,
1311
- loading: isAuthorizing,
1312
- leftSection: /* @__PURE__ */ jsx(IconRefresh, { size: 16 }),
1313
- disabled: updateMutation.isPending,
1314
- children: "Reconnect OAuth"
1315
- }
1316
- )
1317
- ] })
1318
- ] })
1319
- ] }),
1320
- !isOAuth && /* @__PURE__ */ jsxs(Fragment, { children: [
1321
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", children: "Current values are encrypted and cannot be displayed. Enter new values below to replace them." }),
1322
- schema.fields?.map((field) => /* @__PURE__ */ jsx(
1323
- PasswordInput,
1324
- {
1325
- label: `New ${field.label}`,
1326
- placeholder: field.placeholder || `Enter new ${field.label.toLowerCase()}`,
1327
- value: fieldValues[field.key] || "",
1328
- onChange: (e) => handleFieldChange(field.key, e.target.value),
1329
- description: field.description,
1330
- disabled: isPending
1331
- },
1332
- field.key
1333
- )),
1334
- updateMutation.error && /* @__PURE__ */ jsx(Alert, { icon: /* @__PURE__ */ jsx(IconAlertCircle, { size: 16 }), color: "red", title: "Update Failed", children: "Failed to update credential" }),
1335
- /* @__PURE__ */ jsxs(Group, { justify: "space-between", gap: "sm", children: [
1336
- /* @__PURE__ */ jsx(Button, { variant: "light", onClick: handleClose, disabled: isPending, children: "Cancel" }),
1337
- /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1338
- /* @__PURE__ */ jsx(
1339
- Button,
1340
- {
1341
- variant: "light",
1342
- onClick: handleSaveChanges,
1343
- loading: updateMutation.isPending,
1344
- disabled: !hasMetadataChanged,
1345
- children: "Save Name"
1346
- }
1347
- ),
1348
- /* @__PURE__ */ jsx(Button, { onClick: handleCredentialUpdate, loading: updateMutation.isPending, disabled: !hasFieldValues, children: "Save Credential" })
1349
- ] })
1350
- ] })
1351
- ] })
1352
- ] }) });
1353
- }
1354
- var PROVIDER_ICONS = {
1355
- slack: IconBrandSlack,
1356
- attio: IconPlug,
1357
- // Attio doesn't have a brand icon
1358
- "google-sheets": IconBrandGoogleDrive,
1359
- gmail: IconBrandGmail,
1360
- github: IconBrandGithub,
1361
- linear: IconRocket,
1362
- dropbox: IconBrandDropbox
1363
- };
1364
- var MOCK_PROVIDERS = [
1365
- { id: "google-sheets", name: "Google Sheets", available: false },
1366
- { id: "gmail", name: "Gmail", available: false },
1367
- { id: "github", name: "GitHub", available: false },
1368
- { id: "linear", name: "Linear", available: false }
1369
- ];
1370
- function OAuthIntegrationsCard({ apiUrl }) {
1371
- const [selectedProviderId, setSelectedProviderId] = useState(null);
1372
- const [modalOpened, setModalOpened] = useState(false);
1373
- const { authorize, isAuthorizing, error } = useOAuthFlow({ apiUrl });
1374
- const queryClient = useQueryClient();
1375
- const handleConnect = async (credentialName) => {
1376
- if (!selectedProviderId) return;
1377
- try {
1378
- await authorize(selectedProviderId, credentialName);
1379
- setModalOpened(false);
1380
- setSelectedProviderId(null);
1381
- queryClient.invalidateQueries({ queryKey: ["credentials"] });
1382
- } catch (err) {
1383
- console.error("OAuth flow failed:", err);
1384
- }
1385
- };
1386
- const handleOpenModal = (providerId) => {
1387
- setSelectedProviderId(providerId);
1388
- setModalOpened(true);
1389
- };
1390
- const realProviders = Object.values(OAUTH_PROVIDERS).map((p) => ({
1391
- ...p,
1392
- available: true
1393
- }));
1394
- const allProviders = [...realProviders, ...MOCK_PROVIDERS];
1395
- const selectedProvider = selectedProviderId ? OAUTH_PROVIDERS[selectedProviderId] : null;
1396
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1397
- /* @__PURE__ */ jsx(Paper, { withBorder: true, children: /* @__PURE__ */ jsxs(Stack, { children: [
1398
- /* @__PURE__ */ jsx(Group, { justify: "space-between", children: /* @__PURE__ */ jsxs("div", { children: [
1399
- /* @__PURE__ */ jsx(Title, { order: 3, children: "OAuth Integrations" }),
1400
- /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", mt: 4, children: "Connect third-party services using OAuth 2.0" })
1401
- ] }) }),
1402
- /* @__PURE__ */ jsx(SimpleGrid, { cols: { base: 1, sm: 2, md: 3 }, children: allProviders.map((provider) => {
1403
- const Icon = PROVIDER_ICONS[provider.id] || IconPlug;
1404
- return /* @__PURE__ */ jsx(
1405
- Card,
1406
- {
1407
- withBorder: true,
1408
- style: {
1409
- opacity: provider.available ? 1 : 0.6
1410
- },
1411
- children: /* @__PURE__ */ jsxs(Stack, { children: [
1412
- /* @__PURE__ */ jsx(Group, { justify: "space-between", align: "flex-start", children: /* @__PURE__ */ jsxs(Group, { gap: "sm", children: [
1413
- /* @__PURE__ */ jsx(ThemeIcon, { size: "lg", variant: "light", color: provider.available ? "blue" : "gray", children: /* @__PURE__ */ jsx(Icon, { size: 20 }) }),
1414
- /* @__PURE__ */ jsxs("div", { children: [
1415
- /* @__PURE__ */ jsx(Text, { fw: 600, style: { fontFamily: "var(--mantine-font-family-headings)" }, children: provider.name }),
1416
- !provider.available && /* @__PURE__ */ jsx(Badge, { color: "gray", variant: "light", size: "xs", mt: 4, children: "Coming Soon" })
1417
- ] })
1418
- ] }) }),
1419
- /* @__PURE__ */ jsx(
1420
- Button,
1421
- {
1422
- variant: "filled",
1423
- size: "sm",
1424
- onClick: () => handleOpenModal(provider.id),
1425
- disabled: !provider.available,
1426
- fullWidth: true,
1427
- children: "Connect"
1428
- }
1429
- )
1430
- ] })
1431
- },
1432
- provider.id
1433
- );
1434
- }) })
1435
- ] }) }),
1436
- /* @__PURE__ */ jsx(
1437
- OAuthConnectModal,
1438
- {
1439
- opened: modalOpened,
1440
- onClose: () => {
1441
- setModalOpened(false);
1442
- setSelectedProviderId(null);
1443
- },
1444
- provider: selectedProvider,
1445
- onConnect: handleConnect,
1446
- error,
1447
- isLoading: isAuthorizing
1448
- }
1449
- )
1450
- ] });
1451
- }
1452
-
1453
- export { AccountSettings, AppearanceSettings, CreateWebhookEndpointModal, EditCredentialModal, EditWebhookEndpointModal, MemberConfigModal, OAuthIntegrationsCard, OrgMembersList, OrganizationSettings, WebhookEndpointList, WebhookEndpointSettings };