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