@tangle-network/sandbox-ui 0.116.8 → 0.116.9
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/README.md +3 -0
- package/dist/{WorkflowGraph-B-NsdYz_.js → WorkflowGraph-CzWs8P7y.js} +1 -1
- package/dist/{dashboard-DJQFB7iZ.js → dashboard-DwdEQoqK.js} +457 -381
- package/dist/dashboard.js +1 -1
- package/dist/globals.css +56 -33
- package/dist/{index-l96iy93N.d.ts → index-z_zNuZHm.d.ts} +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/integrations.d.ts +192 -52
- package/dist/integrations.js +874 -366
- package/dist/{provider-logo-DjqmConF.js → provider-logo-BiuiO_c2.js} +2 -1
- package/dist/styles.css +56 -33
- package/dist/{workbench-DVOVERx0.js → workbench-DXTqeu9h.js} +17 -3
- package/dist/workbench.d.ts +1 -1
- package/dist/workbench.js +1 -1
- package/dist/workflows.js +1 -1
- package/package.json +1 -1
package/dist/integrations.js
CHANGED
|
@@ -1,9 +1,363 @@
|
|
|
1
|
-
import { n as normalizeProviderId, r as providerLogoCandidates, t as ProviderIcon } from "./provider-logo-
|
|
2
|
-
import { Button,
|
|
3
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { n as normalizeProviderId, r as providerLogoCandidates, t as ProviderIcon } from "./provider-logo-BiuiO_c2.js";
|
|
2
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState } from "@tangle-network/ui/primitives";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import * as React$1 from "react";
|
|
5
5
|
import { cn, focusFieldWithin, focusRing } from "@tangle-network/ui/utils";
|
|
6
6
|
import { Check, ExternalLink, MoreVertical, Search, Unplug } from "lucide-react";
|
|
7
|
+
//#region src/integrations/integrations-catalog.tsx
|
|
8
|
+
const TONES = {
|
|
9
|
+
neutral: "text-muted-foreground",
|
|
10
|
+
success: "text-[var(--surface-success-text)]",
|
|
11
|
+
warning: "text-[var(--surface-warning-text)]",
|
|
12
|
+
error: "text-destructive"
|
|
13
|
+
};
|
|
14
|
+
function catalogRowActive(row) {
|
|
15
|
+
return row.kind === "app" ? row.installedCount > 0 : row.connections.length > 0;
|
|
16
|
+
}
|
|
17
|
+
/** A controlled selector shared by the catalog and detail; never picks an account. */
|
|
18
|
+
function ConnectionSelector({ connections, value, onChange, label }) {
|
|
19
|
+
const selected = connections.some((connection) => connection.id === value) ? value : "";
|
|
20
|
+
return /* @__PURE__ */ jsxs("select", {
|
|
21
|
+
"aria-label": label,
|
|
22
|
+
value: selected ?? "",
|
|
23
|
+
onChange: (event) => onChange(event.target.value || null),
|
|
24
|
+
className: cn("h-8 w-full min-w-0 truncate rounded-md border border-border bg-background px-2 text-sm text-foreground", focusRing),
|
|
25
|
+
children: [/* @__PURE__ */ jsx("option", {
|
|
26
|
+
value: "",
|
|
27
|
+
children: "Select an account"
|
|
28
|
+
}), connections.map((connection) => /* @__PURE__ */ jsx("option", {
|
|
29
|
+
value: connection.id,
|
|
30
|
+
children: connection.accountDisplay ? `${connection.accountDisplay} · ${connection.id}` : connection.id
|
|
31
|
+
}, connection.id))]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function ConnectionMenu({ row, connection, onManage, onDisconnect, compact }) {
|
|
35
|
+
const manage = connection.capabilities.manage && (connection.manageHref || onManage);
|
|
36
|
+
const disconnect = connection.capabilities.disconnect && onDisconnect;
|
|
37
|
+
const actions = connection.actions ?? [];
|
|
38
|
+
if (!manage && !disconnect && actions.length === 0) return null;
|
|
39
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
40
|
+
asChild: true,
|
|
41
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
42
|
+
type: "button",
|
|
43
|
+
"data-testid": `menu-${row.providerId}`,
|
|
44
|
+
"aria-label": `More actions for ${row.title}`,
|
|
45
|
+
title: "More actions",
|
|
46
|
+
className: cn("relative z-20 flex shrink-0 items-center justify-center rounded-md transition-colors", compact ? "h-6 w-6 text-muted-foreground/70 hover:bg-background hover:text-foreground focus-visible:bg-background focus-visible:text-foreground" : "h-7 w-7 text-muted-foreground hover:bg-accent hover:text-foreground", focusRing),
|
|
47
|
+
children: /* @__PURE__ */ jsx(MoreVertical, { className: compact ? "h-3.5 w-3.5" : "h-4 w-4" })
|
|
48
|
+
})
|
|
49
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
50
|
+
side: "right",
|
|
51
|
+
align: "start",
|
|
52
|
+
className: "min-w-[160px] rounded-md",
|
|
53
|
+
children: [
|
|
54
|
+
manage && (connection.manageHref ? /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
55
|
+
asChild: true,
|
|
56
|
+
children: /* @__PURE__ */ jsxs("a", {
|
|
57
|
+
href: connection.manageHref,
|
|
58
|
+
target: connection.manageInNewWindow ? "_blank" : void 0,
|
|
59
|
+
rel: connection.manageInNewWindow ? "noopener noreferrer" : void 0,
|
|
60
|
+
"data-testid": `manage-item-${row.providerId}`,
|
|
61
|
+
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Manage"]
|
|
62
|
+
})
|
|
63
|
+
}) : /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
64
|
+
"data-testid": `manage-item-${row.providerId}`,
|
|
65
|
+
onSelect: () => onManage?.(connection, row),
|
|
66
|
+
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Manage"]
|
|
67
|
+
})),
|
|
68
|
+
actions.map((action) => /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
69
|
+
disabled: action.disabled,
|
|
70
|
+
onSelect: action.onSelect,
|
|
71
|
+
children: action.label
|
|
72
|
+
}, action.id)),
|
|
73
|
+
disconnect && (manage || actions.length > 0) ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null,
|
|
74
|
+
disconnect ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
75
|
+
"data-testid": `disconnect-${row.providerId}`,
|
|
76
|
+
className: "text-destructive focus:text-destructive",
|
|
77
|
+
onSelect: () => onDisconnect?.(connection, row),
|
|
78
|
+
children: [/* @__PURE__ */ jsx(Unplug, { className: "mr-2 h-4 w-4" }), " Disconnect"]
|
|
79
|
+
}) : null
|
|
80
|
+
]
|
|
81
|
+
})] });
|
|
82
|
+
}
|
|
83
|
+
/** Platform's catalog presentation, with app-owned state, destinations and effects. */
|
|
84
|
+
function IntegrationsCatalog({ rows, query, onQueryChange, categoryFilter = "", onCategoryFilterChange, sort, onSortChange, onSelectConnection, onConnect, onManage, onDisconnect, onRequestIntegration, onRetry, loading = false, error, actionError, connectError, busyProviderId, reserveConnectionContext = false, skeletonCount, emptyCatalogLabel = "No integrations are available to connect yet.", title, description, layout = "cards", className }) {
|
|
85
|
+
const compact = layout === "tiles";
|
|
86
|
+
const categories = [...new Set(rows.flatMap((row) => row.category ? [row.category] : []))].sort();
|
|
87
|
+
const q = query.trim().toLowerCase();
|
|
88
|
+
const filtered = rows.filter((row) => (!categoryFilter || row.category === categoryFilter) && (!q || `${row.title} ${row.providerId} ${row.description ?? ""}`.toLowerCase().includes(q)));
|
|
89
|
+
const hiddenActive = rows.filter((row) => catalogRowActive(row) && !filtered.includes(row)).length;
|
|
90
|
+
const state = error ? "error" : loading && rows.length === 0 ? "loading" : rows.length === 0 ? "empty" : "loaded";
|
|
91
|
+
const gridClass = compact ? "grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6" : "grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3";
|
|
92
|
+
const boxClass = cn(compact ? "flex min-w-0 aspect-square flex-col items-center justify-center gap-2 rounded-xl border p-3 text-center" : "flex min-w-0 min-h-[200px] flex-col gap-3 rounded-xl border p-4 text-left", compact && reserveConnectionContext && "w-full min-h-[136px]");
|
|
93
|
+
const headerClass = cn("flex w-full min-w-0 gap-2", compact ? "flex-col items-center" : "items-start gap-3");
|
|
94
|
+
const clearFilters = () => {
|
|
95
|
+
onQueryChange("");
|
|
96
|
+
onCategoryFilterChange?.("");
|
|
97
|
+
};
|
|
98
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
99
|
+
className: cn("w-full min-w-0 space-y-4", className),
|
|
100
|
+
"aria-label": title ?? "Integrations",
|
|
101
|
+
"aria-busy": loading,
|
|
102
|
+
"data-testid": state === "loading" ? "integrations-skeleton" : "integrations-panel",
|
|
103
|
+
"data-catalog-state": state,
|
|
104
|
+
children: [
|
|
105
|
+
title ? /* @__PURE__ */ jsxs("header", { children: [/* @__PURE__ */ jsx("h2", {
|
|
106
|
+
className: "text-lg font-semibold",
|
|
107
|
+
children: title
|
|
108
|
+
}), description ? /* @__PURE__ */ jsx("p", {
|
|
109
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
110
|
+
children: description
|
|
111
|
+
}) : null] }) : null,
|
|
112
|
+
/* @__PURE__ */ jsxs("div", {
|
|
113
|
+
className: "flex min-w-0 flex-col gap-2 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between",
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsxs("div", {
|
|
116
|
+
className: cn("flex min-w-0 flex-1 items-center gap-2 rounded-lg border bg-card px-3 py-2", focusFieldWithin),
|
|
117
|
+
children: [/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("input", {
|
|
118
|
+
type: "text",
|
|
119
|
+
value: query,
|
|
120
|
+
onChange: (event) => onQueryChange(event.target.value),
|
|
121
|
+
"aria-label": "Search integrations",
|
|
122
|
+
placeholder: "Search integrations...",
|
|
123
|
+
autoFocus: compact,
|
|
124
|
+
"data-testid": "integration-search",
|
|
125
|
+
className: "min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
126
|
+
})]
|
|
127
|
+
}),
|
|
128
|
+
onCategoryFilterChange ? /* @__PURE__ */ jsxs("select", {
|
|
129
|
+
"aria-label": "Filter by category",
|
|
130
|
+
value: categoryFilter,
|
|
131
|
+
onChange: (event) => onCategoryFilterChange(event.target.value),
|
|
132
|
+
className: cn("h-10 min-w-0 rounded-lg border bg-card px-3 text-sm", focusRing),
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ jsx("option", {
|
|
135
|
+
value: "",
|
|
136
|
+
children: "All categories"
|
|
137
|
+
}),
|
|
138
|
+
categoryFilter && !categories.includes(categoryFilter) ? /* @__PURE__ */ jsx("option", {
|
|
139
|
+
value: categoryFilter,
|
|
140
|
+
children: categoryFilter
|
|
141
|
+
}) : null,
|
|
142
|
+
categories.map((category) => /* @__PURE__ */ jsx("option", {
|
|
143
|
+
value: category,
|
|
144
|
+
children: category
|
|
145
|
+
}, category))
|
|
146
|
+
]
|
|
147
|
+
}) : null,
|
|
148
|
+
sort && onSortChange ? /* @__PURE__ */ jsx("div", {
|
|
149
|
+
role: "group",
|
|
150
|
+
"aria-label": "Sort integrations",
|
|
151
|
+
className: "flex shrink-0 items-center gap-1 rounded-lg border border-border bg-card p-1",
|
|
152
|
+
children: [["featured", "Featured"], ["alpha", "A–Z"]].map(([value, label]) => /* @__PURE__ */ jsx("button", {
|
|
153
|
+
type: "button",
|
|
154
|
+
"aria-pressed": sort === value,
|
|
155
|
+
onClick: () => onSortChange(value),
|
|
156
|
+
"data-testid": `sort-${value}`,
|
|
157
|
+
className: cn("rounded-md px-2.5 py-1 text-xs font-medium transition-colors", focusRing, sort === value ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground"),
|
|
158
|
+
children: label
|
|
159
|
+
}, value))
|
|
160
|
+
}) : null,
|
|
161
|
+
onRequestIntegration ? /* @__PURE__ */ jsx(Button, {
|
|
162
|
+
variant: "outline",
|
|
163
|
+
onClick: () => onRequestIntegration(query.trim()),
|
|
164
|
+
children: "Request an integration"
|
|
165
|
+
}) : null
|
|
166
|
+
]
|
|
167
|
+
}),
|
|
168
|
+
actionError ? /* @__PURE__ */ jsx("p", {
|
|
169
|
+
role: "alert",
|
|
170
|
+
className: "text-sm text-destructive",
|
|
171
|
+
children: actionError
|
|
172
|
+
}) : null,
|
|
173
|
+
connectError ? /* @__PURE__ */ jsx("p", {
|
|
174
|
+
role: "alert",
|
|
175
|
+
"data-testid": "integration-connect-error",
|
|
176
|
+
className: "rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive",
|
|
177
|
+
children: connectError
|
|
178
|
+
}) : null,
|
|
179
|
+
(q || categoryFilter) && state === "loaded" ? /* @__PURE__ */ jsxs("span", {
|
|
180
|
+
role: "status",
|
|
181
|
+
className: "sr-only",
|
|
182
|
+
children: [filtered.length, " matching integrations"]
|
|
183
|
+
}) : null,
|
|
184
|
+
/* @__PURE__ */ jsx("div", {
|
|
185
|
+
className: gridClass,
|
|
186
|
+
"data-testid": "integrations-catalog-grid",
|
|
187
|
+
children: state === "loading" ? Array.from({ length: skeletonCount ?? (compact ? 12 : 6) }, (_, i) => /* @__PURE__ */ jsxs("div", {
|
|
188
|
+
"aria-hidden": "true",
|
|
189
|
+
className: cn(boxClass, "animate-pulse border-border bg-muted/40"),
|
|
190
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
191
|
+
className: headerClass,
|
|
192
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
193
|
+
className: "shrink-0 rounded-2xl bg-muted",
|
|
194
|
+
style: {
|
|
195
|
+
width: 48,
|
|
196
|
+
height: 48
|
|
197
|
+
}
|
|
198
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
199
|
+
className: "flex h-8 w-full items-center justify-center",
|
|
200
|
+
children: /* @__PURE__ */ jsx("div", { className: "h-2.5 w-3/4 rounded bg-muted" })
|
|
201
|
+
})]
|
|
202
|
+
}), !compact ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", { className: "h-8 w-full rounded bg-muted" }), /* @__PURE__ */ jsx("div", { className: "mt-auto h-9 border-t border-border" })] }) : null]
|
|
203
|
+
}, i)) : state === "error" ? /* @__PURE__ */ jsxs("div", {
|
|
204
|
+
role: "alert",
|
|
205
|
+
className: "col-span-full flex min-h-[200px] min-w-0 flex-col items-center justify-center gap-3 rounded-xl border border-destructive/40 bg-card p-6",
|
|
206
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
207
|
+
className: "break-words text-sm text-destructive",
|
|
208
|
+
children: error
|
|
209
|
+
}), onRetry ? /* @__PURE__ */ jsx(Button, {
|
|
210
|
+
variant: "outline",
|
|
211
|
+
onClick: onRetry,
|
|
212
|
+
children: "Retry"
|
|
213
|
+
}) : null]
|
|
214
|
+
}) : filtered.length === 0 ? /* @__PURE__ */ jsxs("div", {
|
|
215
|
+
className: "col-span-full min-h-[200px] min-w-0 rounded-xl border border-border bg-card p-4",
|
|
216
|
+
children: [/* @__PURE__ */ jsx(EmptyState, {
|
|
217
|
+
title: state === "empty" ? "No integrations" : "No matches",
|
|
218
|
+
description: state === "empty" ? emptyCatalogLabel : `No integrations match "${query.trim()}".`
|
|
219
|
+
}), q || categoryFilter ? /* @__PURE__ */ jsxs("div", {
|
|
220
|
+
className: "flex flex-col items-center gap-2",
|
|
221
|
+
children: [hiddenActive > 0 ? /* @__PURE__ */ jsxs("p", {
|
|
222
|
+
className: "text-sm text-muted-foreground",
|
|
223
|
+
children: [hiddenActive, " active integrations are hidden by your filters."]
|
|
224
|
+
}) : null, /* @__PURE__ */ jsx(Button, {
|
|
225
|
+
variant: "outline",
|
|
226
|
+
onClick: clearFilters,
|
|
227
|
+
children: "Clear filters"
|
|
228
|
+
})]
|
|
229
|
+
}) : null]
|
|
230
|
+
}) : filtered.map((row) => {
|
|
231
|
+
const provider = row.kind === "provider" ? row : void 0;
|
|
232
|
+
const connection = provider?.connections.find((item) => item.id === provider.selectedConnectionId);
|
|
233
|
+
const active = catalogRowActive(row);
|
|
234
|
+
const canConnect = provider?.canConnect && onConnect;
|
|
235
|
+
const connecting = busyProviderId === row.providerId;
|
|
236
|
+
const wholeButton = !active && !!canConnect && !row.to;
|
|
237
|
+
const Box = wholeButton ? "button" : "div";
|
|
238
|
+
const manageHref = connection?.capabilities.manage ? connection.manageHref : void 0;
|
|
239
|
+
const header = /* @__PURE__ */ jsxs("div", {
|
|
240
|
+
className: headerClass,
|
|
241
|
+
children: [/* @__PURE__ */ jsx(ProviderIcon, {
|
|
242
|
+
id: row.kind === "app" ? row.logoProviderId : row.providerId,
|
|
243
|
+
iconUrl: row.iconUrl,
|
|
244
|
+
displayName: row.title,
|
|
245
|
+
size: 48,
|
|
246
|
+
className: "rounded-2xl"
|
|
247
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
248
|
+
className: "flex h-8 w-full min-w-0 flex-col justify-center leading-4",
|
|
249
|
+
children: [row.to ? /* @__PURE__ */ jsx("a", {
|
|
250
|
+
href: row.to,
|
|
251
|
+
className: cn("relative z-20 truncate text-sm font-medium hover:underline", focusRing),
|
|
252
|
+
children: row.title
|
|
253
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
254
|
+
className: cn("w-full font-medium text-foreground", compact ? "text-xs" : "text-sm", connection?.accountDisplay ? "truncate" : "line-clamp-2"),
|
|
255
|
+
children: row.title
|
|
256
|
+
}), connection?.accountDisplay ? /* @__PURE__ */ jsx("span", {
|
|
257
|
+
className: "w-full truncate text-[11px] leading-4 text-muted-foreground",
|
|
258
|
+
"data-testid": `account-${row.providerId}`,
|
|
259
|
+
children: connection.accountDisplay
|
|
260
|
+
}) : null]
|
|
261
|
+
})]
|
|
262
|
+
});
|
|
263
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
264
|
+
type: wholeButton ? "button" : void 0,
|
|
265
|
+
"data-testid": `integration-${row.providerId}`,
|
|
266
|
+
"data-connected": active ? "true" : "false",
|
|
267
|
+
"data-connecting": connecting ? "true" : void 0,
|
|
268
|
+
"aria-busy": connecting || void 0,
|
|
269
|
+
disabled: wholeButton ? connecting : void 0,
|
|
270
|
+
onClick: wholeButton ? () => provider && onConnect?.(provider) : void 0,
|
|
271
|
+
title: connection?.accountDisplay ? `${row.title} — ${connection.accountDisplay}` : row.description ?? row.title,
|
|
272
|
+
className: cn(boxClass, "group relative", compact && active ? "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]" : "border-border bg-card", wholeButton && cn("transition-all hover:border-[var(--border-strong)] hover:bg-accent/40 hover:shadow-sm", focusRing), connection?.detail && compact && "w-full min-h-[136px]"),
|
|
273
|
+
children: [
|
|
274
|
+
compact && manageHref ? /* @__PURE__ */ jsx("a", {
|
|
275
|
+
href: manageHref,
|
|
276
|
+
target: connection?.manageInNewWindow ? "_blank" : void 0,
|
|
277
|
+
rel: connection?.manageInNewWindow ? "noopener noreferrer" : void 0,
|
|
278
|
+
"aria-label": `Manage ${row.title}${connection?.manageInNewWindow ? " (opens in new window)" : ""}`,
|
|
279
|
+
"data-testid": `manage-${row.providerId}`,
|
|
280
|
+
className: cn("absolute inset-0 z-10 rounded-xl", focusRing)
|
|
281
|
+
}) : null,
|
|
282
|
+
compact && active ? /* @__PURE__ */ jsx("span", {
|
|
283
|
+
className: "pointer-events-none absolute left-2 top-2 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--surface-success-text)] text-white",
|
|
284
|
+
children: /* @__PURE__ */ jsx(Check, {
|
|
285
|
+
className: "h-3 w-3",
|
|
286
|
+
strokeWidth: 3
|
|
287
|
+
})
|
|
288
|
+
}) : null,
|
|
289
|
+
header,
|
|
290
|
+
provider && (provider.connections.length > 1 || active && !connection) ? /* @__PURE__ */ jsx("div", {
|
|
291
|
+
className: "relative z-20 w-full min-w-0",
|
|
292
|
+
children: /* @__PURE__ */ jsx(ConnectionSelector, {
|
|
293
|
+
connections: provider.connections,
|
|
294
|
+
value: provider.selectedConnectionId,
|
|
295
|
+
onChange: (id) => onSelectConnection(row.providerId, id),
|
|
296
|
+
label: `Account for ${row.title}`
|
|
297
|
+
})
|
|
298
|
+
}) : null,
|
|
299
|
+
connection?.detail ? /* @__PURE__ */ jsx("span", {
|
|
300
|
+
className: "w-full truncate text-[11px] leading-4 text-muted-foreground",
|
|
301
|
+
title: connection.detail,
|
|
302
|
+
children: connection.detail
|
|
303
|
+
}) : null,
|
|
304
|
+
!compact ? /* @__PURE__ */ jsx("p", {
|
|
305
|
+
className: cn("min-h-8 text-sm", TONES[connection?.statusTone ?? "neutral"]),
|
|
306
|
+
children: row.kind === "app" ? row.installedCount ? `Installed · ${row.installedCount} ${row.installedCount === 1 ? "account" : "accounts"}` : "Not installed" : connection?.statusLabel ?? (active ? `${provider?.connections.length} accounts · select one to manage` : "Not connected")
|
|
307
|
+
}) : null,
|
|
308
|
+
!compact ? /* @__PURE__ */ jsxs("div", {
|
|
309
|
+
className: "mt-auto flex min-h-9 items-center gap-2 border-t border-border pt-3",
|
|
310
|
+
children: [row.kind === "app" ? /* @__PURE__ */ jsx("a", {
|
|
311
|
+
href: row.to,
|
|
312
|
+
className: cn("text-sm font-medium hover:underline", focusRing),
|
|
313
|
+
children: row.installedCount ? "Manage" : "Set up"
|
|
314
|
+
}) : !active && canConnect ? wholeButton ? /* @__PURE__ */ jsx("span", {
|
|
315
|
+
className: "text-sm font-medium",
|
|
316
|
+
children: connecting ? "Connecting…" : "Connect"
|
|
317
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
318
|
+
variant: "outline",
|
|
319
|
+
disabled: connecting,
|
|
320
|
+
onClick: () => onConnect?.(row),
|
|
321
|
+
children: connecting ? "Connecting…" : "Connect"
|
|
322
|
+
}) : manageHref ? /* @__PURE__ */ jsx("a", {
|
|
323
|
+
href: manageHref,
|
|
324
|
+
target: connection?.manageInNewWindow ? "_blank" : void 0,
|
|
325
|
+
rel: connection?.manageInNewWindow ? "noopener noreferrer" : void 0,
|
|
326
|
+
"data-testid": `manage-${row.providerId}`,
|
|
327
|
+
className: cn("text-sm font-medium hover:underline", focusRing),
|
|
328
|
+
children: "Manage"
|
|
329
|
+
}) : connection?.capabilities.manage && onManage ? /* @__PURE__ */ jsx(Button, {
|
|
330
|
+
variant: "outline",
|
|
331
|
+
onClick: () => onManage(connection, row),
|
|
332
|
+
children: "Manage"
|
|
333
|
+
}) : null, provider && connection ? /* @__PURE__ */ jsx("div", {
|
|
334
|
+
className: "ml-auto",
|
|
335
|
+
children: /* @__PURE__ */ jsx(ConnectionMenu, {
|
|
336
|
+
compact,
|
|
337
|
+
row: provider,
|
|
338
|
+
connection,
|
|
339
|
+
onManage,
|
|
340
|
+
onDisconnect
|
|
341
|
+
}, connection.id)
|
|
342
|
+
}) : null]
|
|
343
|
+
}) : provider && connection ? /* @__PURE__ */ jsx("div", {
|
|
344
|
+
className: "absolute right-1.5 top-1.5 z-20",
|
|
345
|
+
children: /* @__PURE__ */ jsx(ConnectionMenu, {
|
|
346
|
+
compact,
|
|
347
|
+
row: provider,
|
|
348
|
+
connection,
|
|
349
|
+
onManage,
|
|
350
|
+
onDisconnect
|
|
351
|
+
}, connection.id)
|
|
352
|
+
}) : null
|
|
353
|
+
]
|
|
354
|
+
}, `${row.kind}:${row.providerId}`);
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
]
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
//#endregion
|
|
7
361
|
//#region src/integrations/integrations-panel.tsx
|
|
8
362
|
const DEFAULT_FEATURED_IDS = [
|
|
9
363
|
"gmail",
|
|
@@ -43,419 +397,573 @@ const DEFAULT_FEATURED_IDS = [
|
|
|
43
397
|
function defaultConnectorOf(provider) {
|
|
44
398
|
return provider.connectors?.[0]?.connectorId ?? provider.providerId;
|
|
45
399
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const TILE_BOX_CLASS = "flex min-w-0 aspect-square flex-col items-center justify-center gap-2 rounded-xl border p-3 text-center";
|
|
49
|
-
/**
|
|
50
|
-
* Search + sort row. Rendered by the skeleton AND the loaded panel from this
|
|
51
|
-
* one component, which is the whole point: the row is ~74px tall with a 16px
|
|
52
|
-
* gap under it, and a skeleton that omitted it under-reserved the page by that
|
|
53
|
-
* much on every single load — the panel visibly dropped when data arrived.
|
|
54
|
-
* Height parity here is structural, not a matched pair of magic numbers.
|
|
55
|
-
*
|
|
56
|
-
* The controls stay live while loading. They drive local state only (`query`,
|
|
57
|
-
* `sort`), so typing during the fetch is applied the moment the catalog lands
|
|
58
|
-
* instead of being thrown away — and keeping the same input element mounted
|
|
59
|
-
* across the transition is what preserves its autofocus.
|
|
60
|
-
*/
|
|
61
|
-
function IntegrationsToolbar({ query, onQueryChange, sort, onSortChange }) {
|
|
62
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
63
|
-
className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between",
|
|
64
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
65
|
-
className: `flex flex-1 items-center gap-2 rounded-lg border bg-card px-3 py-2 ${focusFieldWithin}`,
|
|
66
|
-
children: [/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ jsx("input", {
|
|
67
|
-
type: "text",
|
|
68
|
-
value: query,
|
|
69
|
-
onChange: (e) => onQueryChange(e.target.value),
|
|
70
|
-
"aria-label": "Search integrations",
|
|
71
|
-
placeholder: "Search integrations...",
|
|
72
|
-
autoFocus: true,
|
|
73
|
-
"data-testid": "integration-search",
|
|
74
|
-
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
75
|
-
})]
|
|
76
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
77
|
-
role: "group",
|
|
78
|
-
"aria-label": "Sort integrations",
|
|
79
|
-
className: "flex shrink-0 items-center gap-1 rounded-lg border border-border bg-card p-1",
|
|
80
|
-
children: [["featured", "Featured"], ["alpha", "A–Z"]].map(([value, label]) => /* @__PURE__ */ jsx("button", {
|
|
81
|
-
type: "button",
|
|
82
|
-
"aria-pressed": sort === value,
|
|
83
|
-
onClick: () => onSortChange(value),
|
|
84
|
-
"data-testid": `sort-${value}`,
|
|
85
|
-
className: cn("rounded-md px-2.5 py-1 text-xs font-medium transition-colors", focusRing, sort === value ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground"),
|
|
86
|
-
children: label
|
|
87
|
-
}, value))
|
|
88
|
-
})]
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
const LOGO_SIZE = 48;
|
|
92
|
-
function buildConnectionIndex(connections) {
|
|
93
|
-
const index = /* @__PURE__ */ new Map();
|
|
94
|
-
for (const conn of connections) {
|
|
95
|
-
if (conn.status === "revoked") continue;
|
|
96
|
-
index.set(`${conn.providerId}:${conn.connectorId ?? conn.providerId}`, conn);
|
|
97
|
-
}
|
|
98
|
-
return index;
|
|
400
|
+
function displayNameOf(provider) {
|
|
401
|
+
return provider.displayName ?? provider.title ?? provider.providerId.replace(/[-_]/g, " ");
|
|
99
402
|
}
|
|
100
|
-
function makeFeaturedRank(
|
|
403
|
+
function makeFeaturedRank(ids) {
|
|
101
404
|
const rank = /* @__PURE__ */ new Map();
|
|
102
|
-
|
|
103
|
-
const norm = normalizeProviderId(id);
|
|
405
|
+
ids.forEach((id, i) => {
|
|
104
406
|
if (!rank.has(id)) rank.set(id, i);
|
|
105
|
-
|
|
407
|
+
const normalized = normalizeProviderId(id);
|
|
408
|
+
if (!rank.has(normalized)) rank.set(normalized, i);
|
|
106
409
|
});
|
|
107
410
|
return rank;
|
|
108
411
|
}
|
|
109
412
|
function rankOf(provider, rank) {
|
|
110
413
|
const raw = provider.providerId.toLowerCase();
|
|
111
|
-
|
|
112
|
-
if (direct !== void 0) return direct;
|
|
113
|
-
return rank.get(normalizeProviderId(raw)) ?? Number.MAX_SAFE_INTEGER;
|
|
114
|
-
}
|
|
115
|
-
function displayNameOf(provider) {
|
|
116
|
-
return provider.displayName ?? provider.title ?? provider.providerId.replace(/[-_]/g, " ");
|
|
117
|
-
}
|
|
118
|
-
function matchesQuery(provider, q) {
|
|
119
|
-
return `${displayNameOf(provider)} ${provider.providerId} ${provider.description ?? ""}`.toLowerCase().includes(q);
|
|
414
|
+
return rank.get(raw) ?? rank.get(normalizeProviderId(raw)) ?? Number.MAX_SAFE_INTEGER;
|
|
120
415
|
}
|
|
121
|
-
|
|
122
|
-
|
|
416
|
+
/** Compatibility facade: legacy effects/sorting, one controlled catalog renderer. */
|
|
417
|
+
function IntegrationsPanel({ catalog, connections, healthByConnectionId, isLoading, error, onConnect, onDisconnect, getManageHref, onManage, getConnectionContext, getConnectionActions, emptyCatalogLabel = "No integrations available yet.", featuredIds = DEFAULT_FEATURED_IDS, defaultSort = "featured", skeletonCount = 12, className }) {
|
|
418
|
+
const [query, setQuery] = React$1.useState("");
|
|
419
|
+
const [sort, setSort] = React$1.useState(defaultSort);
|
|
420
|
+
const [selected, setSelected] = React$1.useState({});
|
|
123
421
|
const [actionError, setActionError] = React$1.useState(null);
|
|
124
422
|
const [pendingAction, setPendingAction] = React$1.useState(false);
|
|
423
|
+
const [connectingId, setConnectingId] = React$1.useState(null);
|
|
424
|
+
const [connectError, setConnectError] = React$1.useState(null);
|
|
425
|
+
const activeConnect = React$1.useRef(0);
|
|
426
|
+
const [disconnectTarget, setDisconnectTarget] = React$1.useState(null);
|
|
427
|
+
const [isDisconnecting, setIsDisconnecting] = React$1.useState(false);
|
|
428
|
+
const [disconnectError, setDisconnectError] = React$1.useState(null);
|
|
429
|
+
const activeDisconnect = React$1.useRef(0);
|
|
430
|
+
React$1.useEffect(() => () => {
|
|
431
|
+
activeConnect.current += 1;
|
|
432
|
+
activeDisconnect.current += 1;
|
|
433
|
+
}, []);
|
|
125
434
|
const runConnectionAction = async (action) => {
|
|
126
435
|
setActionError(null);
|
|
127
436
|
setPendingAction(true);
|
|
128
437
|
try {
|
|
129
438
|
await action.onSelect();
|
|
130
|
-
} catch (
|
|
131
|
-
setActionError(
|
|
439
|
+
} catch (failure) {
|
|
440
|
+
setActionError(failure instanceof Error ? failure.message : "Could not update the connection.");
|
|
132
441
|
} finally {
|
|
133
442
|
setPendingAction(false);
|
|
134
443
|
}
|
|
135
444
|
};
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
const [disconnectTarget, setDisconnectTarget] = React$1.useState(null);
|
|
139
|
-
const [isDisconnecting, setIsDisconnecting] = React$1.useState(false);
|
|
140
|
-
const [disconnectError, setDisconnectError] = React$1.useState(null);
|
|
141
|
-
const activeDisconnect = React$1.useRef(0);
|
|
142
|
-
const connectionIndex = React$1.useMemo(() => buildConnectionIndex(connections), [connections]);
|
|
143
|
-
const featuredRank = React$1.useMemo(() => makeFeaturedRank(featuredIds), [featuredIds]);
|
|
144
|
-
const visible = React$1.useMemo(() => {
|
|
145
|
-
const q = query.trim().toLowerCase();
|
|
146
|
-
const sorted = [...q ? catalog.filter((p) => matchesQuery(p, q)) : catalog];
|
|
147
|
-
sorted.sort((a, b) => {
|
|
148
|
-
if (sort === "featured") {
|
|
149
|
-
const ra = rankOf(a, featuredRank);
|
|
150
|
-
const rb = rankOf(b, featuredRank);
|
|
151
|
-
if (ra !== rb) return ra - rb;
|
|
152
|
-
}
|
|
153
|
-
return displayNameOf(a).localeCompare(displayNameOf(b));
|
|
154
|
-
});
|
|
155
|
-
return sorted;
|
|
156
|
-
}, [
|
|
157
|
-
catalog,
|
|
158
|
-
query,
|
|
159
|
-
sort,
|
|
160
|
-
featuredRank
|
|
161
|
-
]);
|
|
162
|
-
const closeDisconnect = React$1.useCallback(() => {
|
|
163
|
-
activeDisconnect.current += 1;
|
|
164
|
-
setIsDisconnecting(false);
|
|
165
|
-
setDisconnectTarget(null);
|
|
166
|
-
setDisconnectError(null);
|
|
167
|
-
}, []);
|
|
168
|
-
const [connectingId, setConnectingId] = React$1.useState(null);
|
|
169
|
-
const [connectError, setConnectError] = React$1.useState(null);
|
|
170
|
-
const activeConnect = React$1.useRef(0);
|
|
171
|
-
const startConnect = React$1.useCallback(async (input) => {
|
|
172
|
-
const reqId = activeConnect.current += 1;
|
|
445
|
+
const startConnect = async (provider) => {
|
|
446
|
+
const request = ++activeConnect.current;
|
|
173
447
|
setConnectError(null);
|
|
174
448
|
let pending;
|
|
175
449
|
try {
|
|
176
|
-
pending = onConnect(
|
|
177
|
-
|
|
178
|
-
|
|
450
|
+
pending = onConnect({
|
|
451
|
+
providerId: provider.providerId,
|
|
452
|
+
connectorId: defaultConnectorOf(provider)
|
|
453
|
+
});
|
|
454
|
+
} catch (failure) {
|
|
455
|
+
setConnectError(failure instanceof Error ? failure.message : "Failed to start the connection.");
|
|
179
456
|
return;
|
|
180
457
|
}
|
|
181
458
|
if (typeof pending?.then !== "function") return;
|
|
182
|
-
setConnectingId(
|
|
459
|
+
setConnectingId(provider.providerId);
|
|
183
460
|
try {
|
|
184
461
|
await pending;
|
|
185
|
-
} catch (
|
|
186
|
-
if (activeConnect.current
|
|
187
|
-
setConnectError(e instanceof Error ? e.message : "Failed to start the connection.");
|
|
462
|
+
} catch (failure) {
|
|
463
|
+
if (activeConnect.current === request) setConnectError(failure instanceof Error ? failure.message : "Failed to start the connection.");
|
|
188
464
|
} finally {
|
|
189
|
-
if (activeConnect.current ===
|
|
465
|
+
if (activeConnect.current === request) setConnectingId(null);
|
|
190
466
|
}
|
|
191
|
-
}
|
|
192
|
-
const
|
|
467
|
+
};
|
|
468
|
+
const closeDisconnect = React$1.useCallback(() => {
|
|
469
|
+
activeDisconnect.current += 1;
|
|
470
|
+
setIsDisconnecting(false);
|
|
471
|
+
setDisconnectTarget(null);
|
|
472
|
+
setDisconnectError(null);
|
|
473
|
+
}, []);
|
|
474
|
+
const confirmDisconnect = async () => {
|
|
193
475
|
if (!disconnectTarget) return;
|
|
194
|
-
const
|
|
476
|
+
const request = ++activeDisconnect.current;
|
|
195
477
|
setIsDisconnecting(true);
|
|
196
478
|
setDisconnectError(null);
|
|
197
479
|
try {
|
|
198
|
-
await
|
|
199
|
-
if (activeDisconnect.current
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (activeDisconnect.current !== reqId) return;
|
|
203
|
-
setDisconnectError(e instanceof Error ? e.message : "Failed to disconnect.");
|
|
480
|
+
await onDisconnect(disconnectTarget.connection.id);
|
|
481
|
+
if (activeDisconnect.current === request) setDisconnectTarget(null);
|
|
482
|
+
} catch (failure) {
|
|
483
|
+
if (activeDisconnect.current === request) setDisconnectError(failure instanceof Error ? failure.message : "Failed to disconnect.");
|
|
204
484
|
} finally {
|
|
205
|
-
if (activeDisconnect.current ===
|
|
485
|
+
if (activeDisconnect.current === request) setIsDisconnecting(false);
|
|
206
486
|
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
487
|
+
};
|
|
488
|
+
const rank = makeFeaturedRank(featuredIds);
|
|
489
|
+
const rows = [...catalog].sort((a, b) => {
|
|
490
|
+
if (sort === "featured") {
|
|
491
|
+
const delta = rankOf(a, rank) - rankOf(b, rank);
|
|
492
|
+
if (delta) return delta;
|
|
493
|
+
}
|
|
494
|
+
return displayNameOf(a).localeCompare(displayNameOf(b));
|
|
495
|
+
}).map((provider) => {
|
|
496
|
+
const live = connections.filter((connection) => connection.providerId === provider.providerId && connection.status !== "revoked");
|
|
497
|
+
const selectedId = Object.hasOwn(selected, provider.providerId) ? selected[provider.providerId] ?? null : live.length === 1 ? live[0].id : null;
|
|
498
|
+
return {
|
|
499
|
+
kind: "provider",
|
|
500
|
+
providerId: provider.providerId,
|
|
501
|
+
title: displayNameOf(provider),
|
|
502
|
+
description: provider.description,
|
|
503
|
+
iconUrl: provider.iconUrl,
|
|
504
|
+
canConnect: true,
|
|
505
|
+
selectedConnectionId: live.some((connection) => connection.id === selectedId) ? selectedId : null,
|
|
506
|
+
connections: live.map((connection) => {
|
|
507
|
+
const health = healthByConnectionId?.[connection.id];
|
|
508
|
+
const manageHref = getManageHref?.(connection);
|
|
509
|
+
return {
|
|
510
|
+
id: connection.id,
|
|
511
|
+
accountDisplay: connection.accountDisplay,
|
|
512
|
+
statusLabel: health?.message ?? connection.status.replace(/_/g, " "),
|
|
513
|
+
detail: getConnectionContext?.(connection),
|
|
514
|
+
manageHref,
|
|
515
|
+
manageInNewWindow: true,
|
|
516
|
+
capabilities: {
|
|
517
|
+
manage: Boolean(manageHref || onManage),
|
|
518
|
+
disconnect: true
|
|
519
|
+
},
|
|
520
|
+
actions: (getConnectionActions?.(connection) ?? []).map((action) => ({
|
|
521
|
+
id: action.id,
|
|
522
|
+
label: action.label,
|
|
523
|
+
disabled: action.disabled || pendingAction,
|
|
524
|
+
onSelect: () => {
|
|
525
|
+
runConnectionAction(action);
|
|
526
|
+
}
|
|
527
|
+
}))
|
|
528
|
+
};
|
|
215
529
|
})
|
|
216
|
-
}
|
|
530
|
+
};
|
|
217
531
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
532
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IntegrationsCatalog, {
|
|
533
|
+
rows,
|
|
534
|
+
query,
|
|
535
|
+
onQueryChange: setQuery,
|
|
536
|
+
sort,
|
|
537
|
+
onSortChange: setSort,
|
|
538
|
+
layout: "tiles",
|
|
539
|
+
loading: isLoading,
|
|
540
|
+
reserveConnectionContext: Boolean(getConnectionContext),
|
|
541
|
+
error: error ? `Failed to load integrations: ${error.message}` : null,
|
|
542
|
+
actionError,
|
|
543
|
+
connectError,
|
|
544
|
+
busyProviderId: connectingId,
|
|
545
|
+
skeletonCount,
|
|
546
|
+
emptyCatalogLabel,
|
|
547
|
+
className,
|
|
548
|
+
onSelectConnection: (providerId, id) => setSelected((current) => ({
|
|
549
|
+
...current,
|
|
550
|
+
[providerId]: id
|
|
551
|
+
})),
|
|
552
|
+
onConnect: (row) => {
|
|
553
|
+
const provider = catalog.find((item) => item.providerId === row.providerId);
|
|
554
|
+
if (provider) startConnect(provider);
|
|
555
|
+
},
|
|
556
|
+
onManage: onManage ? (connection, row) => {
|
|
557
|
+
runConnectionAction({
|
|
558
|
+
id: "manage",
|
|
559
|
+
label: "Manage",
|
|
560
|
+
onSelect: () => onManage({
|
|
561
|
+
connectionId: connection.id,
|
|
562
|
+
providerId: row.providerId
|
|
563
|
+
})
|
|
564
|
+
});
|
|
565
|
+
} : void 0,
|
|
566
|
+
onDisconnect: (display, row) => {
|
|
567
|
+
const connection = connections.find((item) => item.id === display.id && item.providerId === row.providerId && item.status !== "revoked");
|
|
568
|
+
if (connection) setDisconnectTarget({
|
|
569
|
+
connection,
|
|
570
|
+
name: row.title
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}), /* @__PURE__ */ jsx(Dialog, {
|
|
574
|
+
open: !!disconnectTarget,
|
|
575
|
+
onOpenChange: (open) => {
|
|
576
|
+
if (!open) closeDisconnect();
|
|
577
|
+
},
|
|
578
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
579
|
+
className: "max-w-sm",
|
|
580
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
581
|
+
children: [
|
|
582
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, { children: [
|
|
583
|
+
"Disconnect ",
|
|
584
|
+
disconnectTarget?.name ?? "integration",
|
|
585
|
+
"?"
|
|
586
|
+
] }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
587
|
+
"This removes Sandbox's access to your ",
|
|
588
|
+
disconnectTarget?.name ?? "this",
|
|
589
|
+
" account",
|
|
590
|
+
disconnectTarget?.connection.accountDisplay ? ` (${disconnectTarget.connection.accountDisplay})` : "",
|
|
591
|
+
". You can reconnect anytime."
|
|
592
|
+
] })] }),
|
|
593
|
+
disconnectError ? /* @__PURE__ */ jsx("p", {
|
|
594
|
+
className: "text-sm text-destructive",
|
|
595
|
+
role: "alert",
|
|
596
|
+
children: disconnectError
|
|
597
|
+
}) : null,
|
|
598
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
599
|
+
variant: "outline",
|
|
600
|
+
onClick: closeDisconnect,
|
|
601
|
+
"data-testid": "cancel-disconnect",
|
|
602
|
+
children: "Cancel"
|
|
603
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
604
|
+
variant: "destructive",
|
|
605
|
+
loading: isDisconnecting,
|
|
606
|
+
onClick: confirmDisconnect,
|
|
607
|
+
"data-testid": "confirm-disconnect",
|
|
608
|
+
children: "Disconnect"
|
|
609
|
+
})] })
|
|
610
|
+
]
|
|
611
|
+
})
|
|
612
|
+
})] });
|
|
613
|
+
}
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/integrations/connection-detail.tsx
|
|
616
|
+
/** Platform's permission layout, without its fetching, defaults or persistence. */
|
|
617
|
+
function IntegrationConnectionDetail({ provider, connections, selectedConnectionId, onSelectConnection, detailsByConnectionId, loading, error, backHref, description, onRetry, onTestConnection, onDisconnect, onDecisionChange, onResetDecision, className }) {
|
|
618
|
+
const connection = connections.find((item) => item.id === selectedConnectionId);
|
|
619
|
+
const details = connection && Object.hasOwn(detailsByConnectionId, connection.id) ? detailsByConnectionId[connection.id] : void 0;
|
|
620
|
+
const busy = !!details?.busy;
|
|
621
|
+
const groups = details?.permissionGroups;
|
|
622
|
+
const failure = error ?? details?.error;
|
|
623
|
+
const renderRows = (group) => /* @__PURE__ */ jsx("ul", {
|
|
624
|
+
className: "divide-y divide-border",
|
|
625
|
+
children: group.rows.map((row) => {
|
|
626
|
+
const known = row.decision !== null && row.decisionOptions.some((option) => option.value === row.decision);
|
|
627
|
+
const editable = connection?.capabilities.editPermissions && onDecisionChange;
|
|
628
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
629
|
+
className: "flex min-w-0 flex-col gap-3 p-4 sm:flex-row sm:items-center sm:justify-between",
|
|
630
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
631
|
+
className: "min-w-0",
|
|
632
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
633
|
+
className: "flex min-w-0 flex-wrap items-center gap-2",
|
|
634
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
635
|
+
className: cn("rounded border border-border px-2 py-0.5 text-xs", row.riskTone === "error" ? "text-destructive" : "text-muted-foreground"),
|
|
636
|
+
children: row.riskLabel
|
|
637
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
638
|
+
className: "min-w-0 break-words text-sm font-medium",
|
|
639
|
+
children: row.title
|
|
640
|
+
})]
|
|
641
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
642
|
+
className: "mt-1 break-all font-mono text-xs text-muted-foreground",
|
|
643
|
+
children: row.actionPath
|
|
644
|
+
})]
|
|
645
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
646
|
+
className: "flex flex-wrap items-center gap-3 sm:shrink-0",
|
|
647
|
+
children: [
|
|
648
|
+
/* @__PURE__ */ jsx("span", {
|
|
649
|
+
className: "text-xs text-muted-foreground",
|
|
650
|
+
children: row.sourceLabel
|
|
651
|
+
}),
|
|
652
|
+
editable ? /* @__PURE__ */ jsxs("select", {
|
|
653
|
+
"aria-label": `Decision for ${row.actionPath}`,
|
|
654
|
+
value: known ? row.decision : "",
|
|
655
|
+
disabled: busy || row.disabled,
|
|
656
|
+
onChange: (event) => {
|
|
657
|
+
if (connection && row.decisionOptions.some((option) => option.value === event.target.value)) onDecisionChange?.(connection.id, row.actionPath, event.target.value);
|
|
658
|
+
},
|
|
659
|
+
className: cn("h-9 rounded-md border border-border bg-background px-2 text-sm", focusRing),
|
|
660
|
+
children: [!known ? /* @__PURE__ */ jsx("option", {
|
|
661
|
+
value: "",
|
|
662
|
+
disabled: true,
|
|
663
|
+
children: "Not available"
|
|
664
|
+
}) : null, row.decisionOptions.map((option) => /* @__PURE__ */ jsx("option", {
|
|
665
|
+
value: option.value,
|
|
666
|
+
children: option.label
|
|
667
|
+
}, option.value))]
|
|
668
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
669
|
+
className: "text-sm",
|
|
670
|
+
children: known ? row.decisionOptions.find((option) => option.value === row.decision)?.label : "Not available"
|
|
671
|
+
}),
|
|
672
|
+
row.canReset && connection?.capabilities.resetPermissions && onResetDecision ? /* @__PURE__ */ jsx(Button, {
|
|
673
|
+
variant: "outline",
|
|
674
|
+
disabled: busy || row.disabled,
|
|
675
|
+
"aria-label": `Reset ${row.actionPath}`,
|
|
676
|
+
onClick: () => onResetDecision(connection.id, row.actionPath),
|
|
677
|
+
children: "Reset"
|
|
678
|
+
}) : null
|
|
679
|
+
]
|
|
240
680
|
})]
|
|
241
|
-
},
|
|
242
|
-
})
|
|
243
|
-
});
|
|
244
|
-
if (catalog.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
|
|
245
|
-
title: "No integrations",
|
|
246
|
-
description: emptyCatalogLabel,
|
|
247
|
-
className
|
|
681
|
+
}, row.actionPath);
|
|
682
|
+
})
|
|
248
683
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
"data-testid": "
|
|
684
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
685
|
+
className: cn("w-full min-w-0 space-y-4", className),
|
|
686
|
+
"aria-label": `${provider.title} connection settings`,
|
|
687
|
+
"data-testid": "integration-connection-detail",
|
|
688
|
+
"aria-busy": loading || details?.loading || void 0,
|
|
253
689
|
children: [
|
|
254
|
-
/* @__PURE__ */ jsx(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
onSortChange: setSort
|
|
259
|
-
}),
|
|
260
|
-
actionError ? /* @__PURE__ */ jsx("p", {
|
|
261
|
-
role: "alert",
|
|
262
|
-
className: "text-sm text-destructive",
|
|
263
|
-
children: actionError
|
|
690
|
+
backHref ? /* @__PURE__ */ jsx("a", {
|
|
691
|
+
href: backHref,
|
|
692
|
+
className: cn("inline-flex text-sm text-muted-foreground hover:text-foreground", focusRing),
|
|
693
|
+
children: "← Integrations"
|
|
264
694
|
}) : null,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
695
|
+
/* @__PURE__ */ jsxs("header", {
|
|
696
|
+
className: "flex min-w-0 items-start gap-3",
|
|
697
|
+
children: [/* @__PURE__ */ jsx(ProviderIcon, {
|
|
698
|
+
id: provider.providerId,
|
|
699
|
+
iconUrl: provider.iconUrl,
|
|
700
|
+
displayName: provider.title,
|
|
701
|
+
size: 48
|
|
702
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
703
|
+
className: "min-w-0",
|
|
704
|
+
children: [/* @__PURE__ */ jsxs("h2", {
|
|
705
|
+
className: "break-words text-xl font-semibold",
|
|
706
|
+
children: [provider.title, " connection settings"]
|
|
707
|
+
}), description ? /* @__PURE__ */ jsx("p", {
|
|
708
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
709
|
+
children: description
|
|
710
|
+
}) : null]
|
|
711
|
+
})]
|
|
712
|
+
}),
|
|
713
|
+
/* @__PURE__ */ jsx(ConnectionSelector, {
|
|
714
|
+
connections,
|
|
715
|
+
value: selectedConnectionId,
|
|
716
|
+
onChange: onSelectConnection,
|
|
717
|
+
label: `Account for ${provider.title}`
|
|
718
|
+
}),
|
|
719
|
+
/* @__PURE__ */ jsxs("div", {
|
|
720
|
+
className: "min-h-[200px] min-w-0 space-y-4",
|
|
721
|
+
children: [failure ? /* @__PURE__ */ jsxs("div", {
|
|
722
|
+
role: "alert",
|
|
723
|
+
className: "rounded-xl border border-destructive/40 bg-card p-4 text-sm text-destructive",
|
|
724
|
+
children: [/* @__PURE__ */ jsx("p", { children: failure }), onRetry ? /* @__PURE__ */ jsx(Button, {
|
|
725
|
+
variant: "outline",
|
|
726
|
+
onClick: () => onRetry(connection?.id ?? null),
|
|
727
|
+
children: "Retry"
|
|
728
|
+
}) : null]
|
|
729
|
+
}) : null, loading && !connection ? /* @__PURE__ */ jsx("p", {
|
|
730
|
+
role: "status",
|
|
731
|
+
className: "rounded-xl border border-border bg-card p-6 text-sm text-muted-foreground",
|
|
732
|
+
children: "Loading connections…"
|
|
733
|
+
}) : !connection ? /* @__PURE__ */ jsx("p", {
|
|
734
|
+
className: "rounded-xl border border-border bg-card p-6 text-sm text-muted-foreground",
|
|
735
|
+
children: connections.length ? "Select a connected account to view its settings." : "No connected accounts are available."
|
|
736
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
737
|
+
/* @__PURE__ */ jsxs("div", {
|
|
738
|
+
className: "flex flex-wrap items-start justify-between gap-3 rounded-xl border border-border bg-card p-4",
|
|
739
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
740
|
+
className: "min-w-0",
|
|
290
741
|
children: [
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
rel: "noopener noreferrer",
|
|
295
|
-
"aria-label": `Manage ${name} (opens in new window)`,
|
|
296
|
-
"data-testid": `manage-${provider.providerId}`,
|
|
297
|
-
className: `absolute inset-0 z-10 rounded-xl ${focusRing}`
|
|
298
|
-
}) : null,
|
|
299
|
-
/* @__PURE__ */ jsx("span", {
|
|
300
|
-
className: "pointer-events-none absolute left-2 top-2 flex h-4 w-4 items-center justify-center rounded-full bg-[var(--surface-success-text)] text-white",
|
|
301
|
-
children: /* @__PURE__ */ jsx(Check, {
|
|
302
|
-
className: "h-3 w-3",
|
|
303
|
-
strokeWidth: 3
|
|
304
|
-
})
|
|
742
|
+
/* @__PURE__ */ jsx("p", {
|
|
743
|
+
className: "break-words text-sm font-medium",
|
|
744
|
+
children: connection.accountDisplay ?? connection.id
|
|
305
745
|
}),
|
|
306
|
-
/* @__PURE__ */
|
|
307
|
-
className: "
|
|
308
|
-
children:
|
|
309
|
-
id: provider.providerId,
|
|
310
|
-
iconUrl: provider.iconUrl,
|
|
311
|
-
displayName: displayNameOf(provider),
|
|
312
|
-
size: LOGO_SIZE,
|
|
313
|
-
className: "rounded-2xl"
|
|
314
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
315
|
-
className: "flex h-8 w-full flex-col justify-center leading-4",
|
|
316
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
317
|
-
className: cn("w-full text-xs font-medium text-foreground", live.accountDisplay ? "truncate" : "line-clamp-2"),
|
|
318
|
-
children: name
|
|
319
|
-
}), live.accountDisplay ? /* @__PURE__ */ jsx("span", {
|
|
320
|
-
className: "w-full truncate text-[11px] leading-4 text-muted-foreground",
|
|
321
|
-
"data-testid": `account-${provider.providerId}`,
|
|
322
|
-
children: live.accountDisplay
|
|
323
|
-
}) : null]
|
|
324
|
-
})]
|
|
746
|
+
/* @__PURE__ */ jsx("p", {
|
|
747
|
+
className: "text-sm text-muted-foreground",
|
|
748
|
+
children: connection.statusLabel
|
|
325
749
|
}),
|
|
326
|
-
|
|
327
|
-
className: "
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}) : null,
|
|
331
|
-
/* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
332
|
-
asChild: true,
|
|
333
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
334
|
-
type: "button",
|
|
335
|
-
"data-testid": `menu-${provider.providerId}`,
|
|
336
|
-
"aria-label": `More actions for ${name}`,
|
|
337
|
-
title: "More actions",
|
|
338
|
-
className: `absolute right-1.5 top-1.5 z-20 flex h-6 w-6 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-background hover:text-foreground focus-visible:bg-background focus-visible:text-foreground ${focusRing}`,
|
|
339
|
-
children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-3.5 w-3.5" })
|
|
340
|
-
})
|
|
341
|
-
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
342
|
-
side: "right",
|
|
343
|
-
align: "start",
|
|
344
|
-
className: "min-w-[160px] rounded-md",
|
|
345
|
-
children: [
|
|
346
|
-
manageHref ? /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
347
|
-
asChild: true,
|
|
348
|
-
children: /* @__PURE__ */ jsxs("a", {
|
|
349
|
-
href: manageHref,
|
|
350
|
-
target: "_blank",
|
|
351
|
-
rel: "noopener noreferrer",
|
|
352
|
-
"data-testid": `manage-item-${provider.providerId}`,
|
|
353
|
-
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Manage"]
|
|
354
|
-
})
|
|
355
|
-
}) : onManage ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
356
|
-
"data-testid": `manage-item-${provider.providerId}`,
|
|
357
|
-
onClick: () => onManage({
|
|
358
|
-
connectionId: live.id,
|
|
359
|
-
providerId: provider.providerId
|
|
360
|
-
}),
|
|
361
|
-
children: [/* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Manage"]
|
|
362
|
-
}) : null,
|
|
363
|
-
connectionActions.map((action) => /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
364
|
-
disabled: action.disabled || pendingAction,
|
|
365
|
-
onSelect: () => {
|
|
366
|
-
runConnectionAction(action);
|
|
367
|
-
},
|
|
368
|
-
children: action.label
|
|
369
|
-
}, action.id)),
|
|
370
|
-
canManage || connectionActions.length > 0 ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null,
|
|
371
|
-
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
372
|
-
"data-testid": `disconnect-${provider.providerId}`,
|
|
373
|
-
className: "text-destructive focus:text-destructive",
|
|
374
|
-
onClick: () => setDisconnectTarget({
|
|
375
|
-
connectionId: live.id,
|
|
376
|
-
name,
|
|
377
|
-
accountDisplay: live.accountDisplay
|
|
378
|
-
}),
|
|
379
|
-
children: [/* @__PURE__ */ jsx(Unplug, { className: "mr-2 h-4 w-4" }), " Disconnect"]
|
|
380
|
-
})
|
|
381
|
-
]
|
|
382
|
-
})] })
|
|
750
|
+
connection.detail ? /* @__PURE__ */ jsx("p", {
|
|
751
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
752
|
+
children: connection.detail
|
|
753
|
+
}) : null
|
|
383
754
|
]
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
});
|
|
398
|
-
},
|
|
399
|
-
title: provider.description ? provider.description : `Connect ${name}`,
|
|
400
|
-
className: cn(tileBoxClass, "group border-border bg-card transition-all", "hover:border-[var(--border-strong)] hover:bg-accent/40 hover:shadow-sm", focusRing),
|
|
401
|
-
children: [/* @__PURE__ */ jsx(ProviderIcon, {
|
|
402
|
-
id: provider.providerId,
|
|
403
|
-
iconUrl: provider.iconUrl,
|
|
404
|
-
displayName: displayNameOf(provider),
|
|
405
|
-
size: LOGO_SIZE,
|
|
406
|
-
className: "rounded-2xl"
|
|
407
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
408
|
-
className: "line-clamp-2 h-8 w-full text-xs font-medium leading-4 text-foreground",
|
|
409
|
-
children: name
|
|
755
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
756
|
+
className: "flex flex-wrap gap-2",
|
|
757
|
+
children: [connection.capabilities.test && onTestConnection ? /* @__PURE__ */ jsx(Button, {
|
|
758
|
+
variant: "outline",
|
|
759
|
+
disabled: busy,
|
|
760
|
+
onClick: () => onTestConnection(connection.id),
|
|
761
|
+
children: "Test connection"
|
|
762
|
+
}) : null, connection.capabilities.disconnect && onDisconnect ? /* @__PURE__ */ jsx(Button, {
|
|
763
|
+
variant: "destructive",
|
|
764
|
+
disabled: busy,
|
|
765
|
+
onClick: () => onDisconnect(connection.id),
|
|
766
|
+
children: "Disconnect"
|
|
767
|
+
}) : null]
|
|
410
768
|
})]
|
|
411
|
-
},
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
769
|
+
}),
|
|
770
|
+
details?.testResult ? /* @__PURE__ */ jsx("p", {
|
|
771
|
+
role: details.testResult.tone === "error" ? "alert" : "status",
|
|
772
|
+
className: cn("text-sm", details.testResult.tone === "error" ? "text-destructive" : "text-muted-foreground"),
|
|
773
|
+
children: details.testResult.message
|
|
774
|
+
}) : null,
|
|
775
|
+
details?.loading ? /* @__PURE__ */ jsx("p", {
|
|
776
|
+
role: "status",
|
|
777
|
+
className: "rounded-xl border border-border bg-card p-6 text-sm text-muted-foreground",
|
|
778
|
+
children: "Loading connection settings…"
|
|
779
|
+
}) : !groups ? /* @__PURE__ */ jsx("p", {
|
|
780
|
+
className: "rounded-xl border border-border bg-card p-6 text-sm text-muted-foreground",
|
|
781
|
+
children: "Connection settings are not available yet."
|
|
782
|
+
}) : groups.every((group) => group.rows.length === 0) ? /* @__PURE__ */ jsx("p", {
|
|
783
|
+
className: "rounded-xl border border-border bg-card p-6 text-sm text-muted-foreground",
|
|
784
|
+
children: "No actions are available for this connection."
|
|
785
|
+
}) : groups.map((group) => group.collapsed ? /* @__PURE__ */ jsxs("details", {
|
|
786
|
+
className: "min-w-0 rounded-xl border border-border bg-card",
|
|
787
|
+
children: [
|
|
788
|
+
/* @__PURE__ */ jsxs("summary", {
|
|
789
|
+
className: cn("cursor-pointer rounded-xl p-4 font-medium", focusRing),
|
|
790
|
+
children: [
|
|
791
|
+
group.title,
|
|
792
|
+
" (",
|
|
793
|
+
group.rows.length,
|
|
794
|
+
")"
|
|
795
|
+
]
|
|
796
|
+
}),
|
|
797
|
+
group.description ? /* @__PURE__ */ jsx("p", {
|
|
798
|
+
className: "px-4 pb-4 text-sm text-muted-foreground",
|
|
799
|
+
children: group.description
|
|
800
|
+
}) : null,
|
|
801
|
+
renderRows(group)
|
|
802
|
+
]
|
|
803
|
+
}, group.id) : /* @__PURE__ */ jsxs("section", {
|
|
804
|
+
className: "min-w-0 rounded-xl border border-border bg-card",
|
|
805
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
806
|
+
className: "border-b border-border p-4",
|
|
807
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
808
|
+
className: "font-medium",
|
|
809
|
+
children: group.title
|
|
810
|
+
}), group.description ? /* @__PURE__ */ jsx("p", {
|
|
811
|
+
className: "mt-1 text-sm text-muted-foreground",
|
|
812
|
+
children: group.description
|
|
813
|
+
}) : null]
|
|
814
|
+
}), renderRows(group)]
|
|
815
|
+
}, group.id))
|
|
816
|
+
] })]
|
|
817
|
+
}, connection?.id ?? "unselected")
|
|
455
818
|
]
|
|
456
819
|
});
|
|
457
820
|
}
|
|
458
821
|
//#endregion
|
|
822
|
+
//#region src/integrations/connection-dialogs.tsx
|
|
823
|
+
const fieldClass = cn("h-10 w-full min-w-0 rounded-md border border-border bg-background px-3 text-sm disabled:opacity-50", focusRing);
|
|
824
|
+
function ConnectionDialog({ children, complete, submitLabel, ...props }) {
|
|
825
|
+
const opener = React$1.useRef(null);
|
|
826
|
+
const errorId = React$1.useId();
|
|
827
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
828
|
+
open: props.open,
|
|
829
|
+
onOpenChange: props.onOpenChange,
|
|
830
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
831
|
+
className: "max-h-[90dvh] max-w-md overflow-y-auto",
|
|
832
|
+
onOpenAutoFocus: () => {
|
|
833
|
+
opener.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
834
|
+
},
|
|
835
|
+
onCloseAutoFocus: (event) => {
|
|
836
|
+
event.preventDefault();
|
|
837
|
+
if (opener.current?.isConnected) opener.current.focus();
|
|
838
|
+
},
|
|
839
|
+
onEscapeKeyDown: (event) => event.stopPropagation(),
|
|
840
|
+
children: [/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, { children: ["Connect ", props.title] }), /* @__PURE__ */ jsx(DialogDescription, { children: props.description ?? "Enter the connection details requested by this application." })] }), /* @__PURE__ */ jsxs("form", {
|
|
841
|
+
className: "min-w-0 space-y-4",
|
|
842
|
+
"aria-busy": props.busy || void 0,
|
|
843
|
+
"aria-describedby": props.error ? errorId : void 0,
|
|
844
|
+
onSubmit: (event) => {
|
|
845
|
+
event.preventDefault();
|
|
846
|
+
if (complete && !props.busy && props.canSubmit !== false) props.onSubmit();
|
|
847
|
+
},
|
|
848
|
+
children: [
|
|
849
|
+
children,
|
|
850
|
+
props.error ? /* @__PURE__ */ jsx("p", {
|
|
851
|
+
id: errorId,
|
|
852
|
+
role: "alert",
|
|
853
|
+
className: "text-sm text-destructive",
|
|
854
|
+
children: props.error
|
|
855
|
+
}) : null,
|
|
856
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
857
|
+
type: "button",
|
|
858
|
+
variant: "outline",
|
|
859
|
+
onClick: () => props.onOpenChange(false),
|
|
860
|
+
children: "Cancel"
|
|
861
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
862
|
+
type: "submit",
|
|
863
|
+
disabled: !complete || props.busy || props.canSubmit === false,
|
|
864
|
+
children: props.busy ? "Connecting…" : submitLabel
|
|
865
|
+
})] })
|
|
866
|
+
]
|
|
867
|
+
})]
|
|
868
|
+
})
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
function ApiKeyField({ value, onValueChange, busy, error }) {
|
|
872
|
+
const id = React$1.useId();
|
|
873
|
+
const [revealed, setRevealed] = React$1.useState(false);
|
|
874
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
875
|
+
className: "space-y-2",
|
|
876
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
877
|
+
htmlFor: id,
|
|
878
|
+
className: "block text-sm font-medium",
|
|
879
|
+
children: "API key"
|
|
880
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
881
|
+
className: "flex min-w-0 gap-2",
|
|
882
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
883
|
+
id,
|
|
884
|
+
type: revealed ? "text" : "password",
|
|
885
|
+
value,
|
|
886
|
+
required: true,
|
|
887
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
888
|
+
disabled: busy,
|
|
889
|
+
autoComplete: "off",
|
|
890
|
+
autoCapitalize: "none",
|
|
891
|
+
spellCheck: false,
|
|
892
|
+
"aria-invalid": !!error || void 0,
|
|
893
|
+
className: cn(fieldClass, "font-mono")
|
|
894
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
895
|
+
type: "button",
|
|
896
|
+
variant: "outline",
|
|
897
|
+
"aria-label": revealed ? "Hide API key" : "Show API key",
|
|
898
|
+
"aria-pressed": revealed,
|
|
899
|
+
onClick: () => setRevealed((current) => !current),
|
|
900
|
+
disabled: busy,
|
|
901
|
+
children: revealed ? "Hide" : "Show"
|
|
902
|
+
})]
|
|
903
|
+
})]
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
/** Key bytes remain in owner-controlled value/onValueChange; no request or storage. */
|
|
907
|
+
function ApiKeyConnectDialog(props) {
|
|
908
|
+
return /* @__PURE__ */ jsxs(ConnectionDialog, {
|
|
909
|
+
...props,
|
|
910
|
+
complete: props.value.trim().length > 0,
|
|
911
|
+
submitLabel: "Connect",
|
|
912
|
+
children: [/* @__PURE__ */ jsx(ApiKeyField, {
|
|
913
|
+
value: props.value,
|
|
914
|
+
onValueChange: props.onValueChange,
|
|
915
|
+
busy: props.busy,
|
|
916
|
+
error: props.error
|
|
917
|
+
}, props.providerId), props.children]
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
function ParameterFields({ parameters, values, onValueChange, busy, error }) {
|
|
921
|
+
const prefix = React$1.useId();
|
|
922
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parameters.map((parameter) => {
|
|
923
|
+
const id = `${prefix}-${parameter.key}`;
|
|
924
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
925
|
+
className: "space-y-2",
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsx("label", {
|
|
928
|
+
htmlFor: id,
|
|
929
|
+
className: "block text-sm font-medium",
|
|
930
|
+
children: parameter.label
|
|
931
|
+
}),
|
|
932
|
+
/* @__PURE__ */ jsx("input", {
|
|
933
|
+
id,
|
|
934
|
+
type: "text",
|
|
935
|
+
value: values[parameter.key] ?? "",
|
|
936
|
+
onChange: (event) => onValueChange(parameter.key, event.target.value),
|
|
937
|
+
required: parameter.required,
|
|
938
|
+
disabled: busy,
|
|
939
|
+
autoComplete: "off",
|
|
940
|
+
autoCapitalize: "none",
|
|
941
|
+
spellCheck: false,
|
|
942
|
+
placeholder: parameter.placeholder,
|
|
943
|
+
"aria-invalid": !!error || void 0,
|
|
944
|
+
"aria-describedby": parameter.description ? `${id}-hint` : void 0,
|
|
945
|
+
className: fieldClass
|
|
946
|
+
}),
|
|
947
|
+
parameter.description ? /* @__PURE__ */ jsx("p", {
|
|
948
|
+
id: `${id}-hint`,
|
|
949
|
+
className: "text-xs text-muted-foreground",
|
|
950
|
+
children: parameter.description
|
|
951
|
+
}) : null
|
|
952
|
+
]
|
|
953
|
+
}, parameter.key);
|
|
954
|
+
}) });
|
|
955
|
+
}
|
|
956
|
+
/** Parameter schema/values and all auth behavior belong to the owner. */
|
|
957
|
+
function OAuthConnectionParameterDialog(props) {
|
|
958
|
+
const complete = props.parameters.every((parameter) => !parameter.required || !!props.values[parameter.key]?.trim());
|
|
959
|
+
return /* @__PURE__ */ jsxs(ConnectionDialog, {
|
|
960
|
+
...props,
|
|
961
|
+
complete,
|
|
962
|
+
submitLabel: "Continue",
|
|
963
|
+
children: [/* @__PURE__ */ jsx(ParameterFields, { ...props }, props.providerId), props.children]
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
//#endregion
|
|
459
967
|
//#region src/integrations/use-integrations.ts
|
|
460
968
|
function unwrap(json) {
|
|
461
969
|
if (json && typeof json === "object" && "data" in json && json.data !== void 0) return json.data;
|
|
@@ -652,4 +1160,4 @@ function useIntegrations({ apiBaseUrl, fetchImpl, autoLoad = true, refreshOnRetu
|
|
|
652
1160
|
};
|
|
653
1161
|
}
|
|
654
1162
|
//#endregion
|
|
655
|
-
export { IntegrationsPanel, ProviderIcon, providerLogoCandidates, useIntegrations };
|
|
1163
|
+
export { ApiKeyConnectDialog, ApiKeyConnectDialog as ApiKeyConnectModal, IntegrationConnectionDetail, IntegrationsCatalog, IntegrationsPanel, OAuthConnectionParameterDialog, OAuthConnectionParameterDialog as OAuthConnectionParameterModal, ProviderIcon, catalogRowActive, providerLogoCandidates, useIntegrations };
|