@tangle-network/sandbox-ui 0.113.7 → 0.114.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
  2. package/dist/assets.d.ts +214 -206
  3. package/dist/assets.js +1057 -908
  4. package/dist/auth-CWe17Hly.js +37 -0
  5. package/dist/auth.d.ts +2 -30
  6. package/dist/auth.js +2 -15
  7. package/dist/chat-BOvFAe6p.js +903 -0
  8. package/dist/chat.d.ts +2 -230
  9. package/dist/chat.js +2 -34
  10. package/dist/connectors.d.ts +52 -49
  11. package/dist/connectors.js +595 -643
  12. package/dist/dashboard-CZ3VgNPR.js +4969 -0
  13. package/dist/dashboard.d.ts +4 -1297
  14. package/dist/dashboard.js +4 -152
  15. package/dist/editor.d.ts +2 -1
  16. package/dist/editor.js +2 -31
  17. package/dist/files.d.ts +2 -6
  18. package/dist/files.js +2 -33
  19. package/dist/globals.css +63 -3
  20. package/dist/hooks.d.ts +146 -148
  21. package/dist/hooks.js +222 -55
  22. package/dist/index-CKRaj1pM.d.ts +776 -0
  23. package/dist/index-CXX5IpH2.d.ts +2 -0
  24. package/dist/index-DPePq7c8.d.ts +1340 -0
  25. package/dist/index-DiyLLZuJ.d.ts +230 -0
  26. package/dist/index-DvmZLMT1.d.ts +30 -0
  27. package/dist/index-I8ZLVtHd.d.ts +330 -0
  28. package/dist/index-PRM636Ex.d.ts +86 -0
  29. package/dist/index.d.ts +21 -33
  30. package/dist/index.js +21 -701
  31. package/dist/info-panel-C9cqDzOL.js +594 -0
  32. package/dist/integrations.d.ts +133 -141
  33. package/dist/integrations.js +491 -673
  34. package/dist/markdown.d.ts +2 -1
  35. package/dist/markdown.js +2 -13
  36. package/dist/model-I-NIxeM-.js +1250 -0
  37. package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
  38. package/dist/model-brand-hMyDTUqB.js +624 -0
  39. package/dist/motion-DkyBRJb9.js +37 -0
  40. package/dist/openui.d.ts +2 -1
  41. package/dist/openui.js +2 -7
  42. package/dist/pages.d.ts +307 -300
  43. package/dist/pages.js +4404 -4181
  44. package/dist/primitives-Dh1vG6J3.js +113 -0
  45. package/dist/primitives.d.ts +2 -74
  46. package/dist/primitives.js +2 -204
  47. package/dist/provider-logo-DjqmConF.js +311 -0
  48. package/dist/run.d.ts +2 -1
  49. package/dist/run.js +2 -23
  50. package/dist/sdk-hooks.d.ts +3 -4
  51. package/dist/sdk-hooks.js +3 -31
  52. package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
  53. package/dist/stores.d.ts +14 -15
  54. package/dist/stores.js +13 -81
  55. package/dist/styles.css +63 -3
  56. package/dist/template-card-L-4gmjx2.d.ts +90 -0
  57. package/dist/terminal-CN9ZgHRt.js +197 -0
  58. package/dist/terminal.d.ts +60 -60
  59. package/dist/terminal.js +2 -10
  60. package/dist/tokens.css +51 -0
  61. package/dist/types.d.ts +4 -11
  62. package/dist/types.js +0 -1
  63. package/dist/use-pty-session-C8cE0adT.js +669 -0
  64. package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
  65. package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
  66. package/dist/utils-DzWbktWG.js +2 -0
  67. package/dist/utils.d.ts +2 -1
  68. package/dist/utils.js +2 -37
  69. package/dist/workbench-BCaCKl-J.js +8347 -0
  70. package/dist/workbench.d.ts +2 -310
  71. package/dist/workbench.js +2 -44
  72. package/dist/workflows.d.ts +341 -339
  73. package/dist/workflows.js +74 -109
  74. package/dist/workspace-B6AtI6ld.js +2510 -0
  75. package/dist/workspace.d.ts +2 -761
  76. package/dist/workspace.js +2 -51
  77. package/package.json +46 -40
  78. package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
  79. package/dist/chunk-2BUPSB7O.js +0 -0
  80. package/dist/chunk-3KBNPQQF.js +0 -2697
  81. package/dist/chunk-3LIXVEEE.js +0 -279
  82. package/dist/chunk-72UF2H5H.js +0 -707
  83. package/dist/chunk-AI25G5MX.js +0 -944
  84. package/dist/chunk-ANSC5AFC.js +0 -314
  85. package/dist/chunk-AZ3AWMTM.js +0 -8
  86. package/dist/chunk-BGVEHENC.js +0 -10
  87. package/dist/chunk-CZ7CSFO4.js +0 -39
  88. package/dist/chunk-DYXXYGS4.js +0 -4700
  89. package/dist/chunk-EI44GEQ5.js +0 -6
  90. package/dist/chunk-IACRDXER.js +0 -219
  91. package/dist/chunk-IOB2PW5Z.js +0 -55
  92. package/dist/chunk-M4K7ZGR2.js +0 -24
  93. package/dist/chunk-MYZAW4RX.js +0 -665
  94. package/dist/chunk-OMUIPJQZ.js +0 -555
  95. package/dist/chunk-RQZRXKMR.js +0 -398
  96. package/dist/chunk-SOKKTB7W.js +0 -230
  97. package/dist/chunk-TADGS4Q7.js +0 -1010
  98. package/dist/chunk-UCQNOUN3.js +0 -7964
  99. package/dist/chunk-ULPLRBMF.js +0 -34
  100. package/dist/chunk-WID73FPH.js +0 -89
  101. package/dist/chunk-X3YXP7FG.js +0 -38
  102. package/dist/logo-DiAjq759.d.ts +0 -15
  103. package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
  104. package/dist/ports-list-CDIvTNnX.d.ts +0 -17
  105. package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
  106. package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
@@ -1,692 +1,510 @@
1
- import {
2
- ProviderIcon,
3
- normalizeProviderId,
4
- providerLogoCandidates
5
- } from "./chunk-3LIXVEEE.js";
6
- import "./chunk-CZ7CSFO4.js";
7
-
8
- // src/integrations/integrations-panel.tsx
9
- import * as React from "react";
10
- import {
11
- Button,
12
- Card,
13
- CardContent,
14
- Dialog,
15
- DialogContent,
16
- DialogDescription,
17
- DialogFooter,
18
- DialogHeader,
19
- DialogTitle,
20
- DropdownMenu,
21
- DropdownMenuContent,
22
- DropdownMenuItem,
23
- DropdownMenuSeparator,
24
- DropdownMenuTrigger,
25
- EmptyState
26
- } from "@tangle-network/ui/primitives";
27
- import { cn } from "@tangle-network/ui/utils";
28
- import { Check, ExternalLink, MoreVertical, Search, Unplug } from "lucide-react";
29
- import { focusFieldWithin, focusRing } from "@tangle-network/ui/utils";
1
+ import { n as normalizeProviderId, r as providerLogoCandidates, t as ProviderIcon } from "./provider-logo-DjqmConF.js";
2
+ import { Button, Card, CardContent, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState } from "@tangle-network/ui/primitives";
30
3
  import { jsx, jsxs } from "react/jsx-runtime";
31
- var DEFAULT_FEATURED_IDS = [
32
- "gmail",
33
- "google-sheets",
34
- "google-drive",
35
- "google-docs",
36
- "google-calendar",
37
- "outlook",
38
- "outlook-mail",
39
- "microsoft-calendar",
40
- "microsoft-excel",
41
- "microsoft-teams",
42
- "slack",
43
- "discord",
44
- "hubspot",
45
- "salesforce",
46
- "notion",
47
- "airtable",
48
- "github",
49
- "gitlab",
50
- "linear",
51
- "jira",
52
- "asana",
53
- "stripe",
54
- "stripe-pack",
55
- "twilio",
56
- "twilio-sms",
57
- "linkedin",
58
- "zoom",
59
- "shopify",
60
- "mailchimp",
61
- "zendesk",
62
- "intercom",
63
- "dropbox",
64
- "webhook"
4
+ import * as React$1 from "react";
5
+ import { cn, focusFieldWithin, focusRing } from "@tangle-network/ui/utils";
6
+ import { Check, ExternalLink, MoreVertical, Search, Unplug } from "lucide-react";
7
+ //#region src/integrations/integrations-panel.tsx
8
+ const DEFAULT_FEATURED_IDS = [
9
+ "gmail",
10
+ "google-sheets",
11
+ "google-drive",
12
+ "google-docs",
13
+ "google-calendar",
14
+ "outlook",
15
+ "outlook-mail",
16
+ "microsoft-calendar",
17
+ "microsoft-excel",
18
+ "microsoft-teams",
19
+ "slack",
20
+ "discord",
21
+ "hubspot",
22
+ "salesforce",
23
+ "notion",
24
+ "airtable",
25
+ "github",
26
+ "gitlab",
27
+ "linear",
28
+ "jira",
29
+ "asana",
30
+ "stripe",
31
+ "stripe-pack",
32
+ "twilio",
33
+ "twilio-sms",
34
+ "linkedin",
35
+ "zoom",
36
+ "shopify",
37
+ "mailchimp",
38
+ "zendesk",
39
+ "intercom",
40
+ "dropbox",
41
+ "webhook"
65
42
  ];
66
43
  function defaultConnectorOf(provider) {
67
- return provider.connectors?.[0]?.connectorId ?? provider.providerId;
44
+ return provider.connectors?.[0]?.connectorId ?? provider.providerId;
68
45
  }
69
- var TILE_GRID_CLASS = "grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6";
70
- var DEFAULT_SKELETON_COUNT = 12;
71
- var TILE_BOX_CLASS = "flex min-w-0 aspect-square flex-col items-center justify-center gap-2 rounded-xl border p-3 text-center";
72
- function IntegrationsToolbar({
73
- query,
74
- onQueryChange,
75
- sort,
76
- onSortChange
77
- }) {
78
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between", children: [
79
- /* @__PURE__ */ jsxs("div", { className: `flex flex-1 items-center gap-2 rounded-lg border bg-card px-3 py-2 ${focusFieldWithin}`, children: [
80
- /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
81
- /* @__PURE__ */ jsx(
82
- "input",
83
- {
84
- type: "text",
85
- value: query,
86
- onChange: (e) => onQueryChange(e.target.value),
87
- "aria-label": "Search integrations",
88
- placeholder: "Search integrations...",
89
- autoFocus: true,
90
- "data-testid": "integration-search",
91
- className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
92
- }
93
- )
94
- ] }),
95
- /* @__PURE__ */ jsx(
96
- "div",
97
- {
98
- role: "group",
99
- "aria-label": "Sort integrations",
100
- className: "flex shrink-0 items-center gap-1 rounded-lg border border-border bg-card p-1",
101
- children: [
102
- ["featured", "Featured"],
103
- ["alpha", "A\u2013Z"]
104
- ].map(([value, label]) => /* @__PURE__ */ jsx(
105
- "button",
106
- {
107
- type: "button",
108
- "aria-pressed": sort === value,
109
- onClick: () => onSortChange(value),
110
- "data-testid": `sort-${value}`,
111
- className: cn(
112
- "rounded-md px-2.5 py-1 text-xs font-medium transition-colors",
113
- focusRing,
114
- sort === value ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground"
115
- ),
116
- children: label
117
- },
118
- value
119
- ))
120
- }
121
- )
122
- ] });
46
+ const TILE_GRID_CLASS = "grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6";
47
+ const DEFAULT_SKELETON_COUNT = 12;
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
+ });
123
90
  }
124
- var LOGO_SIZE = 48;
91
+ const LOGO_SIZE = 48;
125
92
  function buildConnectionIndex(connections) {
126
- const index = /* @__PURE__ */ new Map();
127
- for (const conn of connections) {
128
- if (conn.status === "revoked") continue;
129
- index.set(`${conn.providerId}:${conn.connectorId ?? conn.providerId}`, conn);
130
- }
131
- return index;
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;
132
99
  }
133
100
  function makeFeaturedRank(featuredIds) {
134
- const rank = /* @__PURE__ */ new Map();
135
- featuredIds.forEach((id, i) => {
136
- const norm = normalizeProviderId(id);
137
- if (!rank.has(id)) rank.set(id, i);
138
- if (!rank.has(norm)) rank.set(norm, i);
139
- });
140
- return rank;
101
+ const rank = /* @__PURE__ */ new Map();
102
+ featuredIds.forEach((id, i) => {
103
+ const norm = normalizeProviderId(id);
104
+ if (!rank.has(id)) rank.set(id, i);
105
+ if (!rank.has(norm)) rank.set(norm, i);
106
+ });
107
+ return rank;
141
108
  }
142
109
  function rankOf(provider, rank) {
143
- const raw = provider.providerId.toLowerCase();
144
- const direct = rank.get(raw);
145
- if (direct !== void 0) return direct;
146
- const norm = rank.get(normalizeProviderId(raw));
147
- return norm ?? Number.MAX_SAFE_INTEGER;
110
+ const raw = provider.providerId.toLowerCase();
111
+ const direct = rank.get(raw);
112
+ if (direct !== void 0) return direct;
113
+ return rank.get(normalizeProviderId(raw)) ?? Number.MAX_SAFE_INTEGER;
148
114
  }
149
115
  function displayNameOf(provider) {
150
- return provider.displayName ?? provider.title ?? provider.providerId.replace(/[-_]/g, " ");
116
+ return provider.displayName ?? provider.title ?? provider.providerId.replace(/[-_]/g, " ");
151
117
  }
152
118
  function matchesQuery(provider, q) {
153
- const hay = `${displayNameOf(provider)} ${provider.providerId} ${provider.description ?? ""}`.toLowerCase();
154
- return hay.includes(q);
119
+ return `${displayNameOf(provider)} ${provider.providerId} ${provider.description ?? ""}`.toLowerCase().includes(q);
155
120
  }
156
- function IntegrationsPanel({
157
- catalog,
158
- connections,
159
- healthByConnectionId,
160
- isLoading,
161
- error,
162
- onConnect,
163
- onDisconnect,
164
- getManageHref,
165
- onManage,
166
- emptyCatalogLabel = "No integrations available yet.",
167
- featuredIds = DEFAULT_FEATURED_IDS,
168
- defaultSort = "featured",
169
- skeletonCount = DEFAULT_SKELETON_COUNT,
170
- className
171
- }) {
172
- const [query, setQuery] = React.useState("");
173
- const [sort, setSort] = React.useState(defaultSort);
174
- const [disconnectTarget, setDisconnectTarget] = React.useState(null);
175
- const [isDisconnecting, setIsDisconnecting] = React.useState(false);
176
- const [disconnectError, setDisconnectError] = React.useState(
177
- null
178
- );
179
- const activeDisconnect = React.useRef(0);
180
- const connectionIndex = React.useMemo(
181
- () => buildConnectionIndex(connections),
182
- [connections]
183
- );
184
- const featuredRank = React.useMemo(
185
- () => makeFeaturedRank(featuredIds),
186
- [featuredIds]
187
- );
188
- const visible = React.useMemo(() => {
189
- const q = query.trim().toLowerCase();
190
- const filtered = q ? catalog.filter((p) => matchesQuery(p, q)) : catalog;
191
- const sorted = [...filtered];
192
- sorted.sort((a, b) => {
193
- if (sort === "featured") {
194
- const ra = rankOf(a, featuredRank);
195
- const rb = rankOf(b, featuredRank);
196
- if (ra !== rb) return ra - rb;
197
- }
198
- return displayNameOf(a).localeCompare(displayNameOf(b));
199
- });
200
- return sorted;
201
- }, [catalog, query, sort, featuredRank]);
202
- const closeDisconnect = React.useCallback(() => {
203
- activeDisconnect.current += 1;
204
- setIsDisconnecting(false);
205
- setDisconnectTarget(null);
206
- setDisconnectError(null);
207
- }, []);
208
- const [connectingId, setConnectingId] = React.useState(null);
209
- const [connectError, setConnectError] = React.useState(null);
210
- const activeConnect = React.useRef(0);
211
- const startConnect = React.useCallback(
212
- async (input) => {
213
- const reqId = activeConnect.current += 1;
214
- setConnectError(null);
215
- let pending;
216
- try {
217
- pending = onConnect(input);
218
- } catch (e) {
219
- setConnectError(
220
- e instanceof Error ? e.message : "Failed to start the connection."
221
- );
222
- return;
223
- }
224
- if (typeof pending?.then !== "function") {
225
- return;
226
- }
227
- setConnectingId(input.providerId);
228
- try {
229
- await pending;
230
- } catch (e) {
231
- if (activeConnect.current !== reqId) return;
232
- setConnectError(
233
- e instanceof Error ? e.message : "Failed to start the connection."
234
- );
235
- } finally {
236
- if (activeConnect.current === reqId) setConnectingId(null);
237
- }
238
- },
239
- [onConnect]
240
- );
241
- const confirmDisconnect = React.useCallback(async () => {
242
- if (!disconnectTarget) return;
243
- const reqId = activeDisconnect.current += 1;
244
- setIsDisconnecting(true);
245
- setDisconnectError(null);
246
- try {
247
- await Promise.resolve(onDisconnect(disconnectTarget.connectionId));
248
- if (activeDisconnect.current !== reqId) return;
249
- setDisconnectTarget(null);
250
- } catch (e) {
251
- if (activeDisconnect.current !== reqId) return;
252
- setDisconnectError(
253
- e instanceof Error ? e.message : "Failed to disconnect."
254
- );
255
- } finally {
256
- if (activeDisconnect.current === reqId) setIsDisconnecting(false);
257
- }
258
- }, [disconnectTarget, onDisconnect]);
259
- if (error) {
260
- return /* @__PURE__ */ jsx(Card, { className: cn("border-destructive/50", className), children: /* @__PURE__ */ jsx(CardContent, { className: "py-6", children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
261
- "Failed to load integrations: ",
262
- error.message
263
- ] }) }) });
264
- }
265
- if (isLoading && catalog.length === 0) {
266
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), "data-testid": "integrations-skeleton", children: [
267
- /* @__PURE__ */ jsx(
268
- IntegrationsToolbar,
269
- {
270
- query,
271
- onQueryChange: setQuery,
272
- sort,
273
- onSortChange: setSort
274
- }
275
- ),
276
- /* @__PURE__ */ jsx("div", { className: TILE_GRID_CLASS, children: Array.from({ length: skeletonCount }).map((_, i) => /* @__PURE__ */ jsxs(
277
- "div",
278
- {
279
- "aria-hidden": true,
280
- className: cn(
281
- TILE_BOX_CLASS,
282
- "animate-pulse border-border bg-muted/40"
283
- ),
284
- children: [
285
- /* @__PURE__ */ jsx(
286
- "div",
287
- {
288
- className: "shrink-0 rounded-2xl bg-muted",
289
- style: { width: LOGO_SIZE, height: LOGO_SIZE }
290
- }
291
- ),
292
- /* @__PURE__ */ jsx("div", { className: "flex h-8 w-full items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "h-2.5 w-3/4 rounded bg-muted" }) })
293
- ]
294
- },
295
- i
296
- )) })
297
- ] });
298
- }
299
- if (catalog.length === 0) {
300
- return /* @__PURE__ */ jsx(
301
- EmptyState,
302
- {
303
- title: "No integrations",
304
- description: emptyCatalogLabel,
305
- className
306
- }
307
- );
308
- }
309
- const disconnectAccountLabel = disconnectTarget?.accountDisplay;
310
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), "data-testid": "integrations-panel", children: [
311
- /* @__PURE__ */ jsx(
312
- IntegrationsToolbar,
313
- {
314
- query,
315
- onQueryChange: setQuery,
316
- sort,
317
- onSortChange: setSort
318
- }
319
- ),
320
- connectError ? /* @__PURE__ */ jsx(
321
- "div",
322
- {
323
- role: "alert",
324
- "data-testid": "integration-connect-error",
325
- className: "mb-3 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive",
326
- children: connectError
327
- }
328
- ) : null,
329
- visible.length === 0 ? /* @__PURE__ */ jsx(
330
- EmptyState,
331
- {
332
- title: "No matches",
333
- description: `No integrations match "${query.trim()}".`
334
- }
335
- ) : /* @__PURE__ */ jsx("div", { className: TILE_GRID_CLASS, children: visible.map((provider) => {
336
- const connectorId = defaultConnectorOf(provider);
337
- const live = connectionIndex.get(
338
- `${provider.providerId}:${connectorId}`
339
- );
340
- const name = displayNameOf(provider);
341
- const connected = Boolean(live);
342
- if (connected && live) {
343
- const manageHref = getManageHref?.(live);
344
- const canManage = Boolean(manageHref) || Boolean(onManage);
345
- return /* @__PURE__ */ jsxs(
346
- "div",
347
- {
348
- "data-testid": `integration-${provider.providerId}`,
349
- "data-connected": "true",
350
- title: live.accountDisplay ? `${name} \u2014 ${live.accountDisplay}` : name,
351
- className: cn(
352
- TILE_BOX_CLASS,
353
- "group relative",
354
- "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]"
355
- ),
356
- children: [
357
- manageHref ? /* @__PURE__ */ jsx(
358
- "a",
359
- {
360
- href: manageHref,
361
- target: "_blank",
362
- rel: "noopener noreferrer",
363
- "aria-label": `Manage ${name} (opens in new window)`,
364
- "data-testid": `manage-${provider.providerId}`,
365
- className: `absolute inset-0 z-10 rounded-xl ${focusRing}`
366
- }
367
- ) : null,
368
- /* @__PURE__ */ jsx("span", { 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", children: /* @__PURE__ */ jsx(Check, { className: "h-3 w-3", strokeWidth: 3 }) }),
369
- /* @__PURE__ */ jsxs("div", { className: "pointer-events-none flex w-full min-w-0 flex-col items-center gap-2", children: [
370
- /* @__PURE__ */ jsx(
371
- ProviderIcon,
372
- {
373
- id: provider.providerId,
374
- iconUrl: provider.iconUrl,
375
- displayName: displayNameOf(provider),
376
- size: LOGO_SIZE,
377
- className: "rounded-2xl"
378
- }
379
- ),
380
- /* @__PURE__ */ jsxs("div", { className: "flex h-8 w-full flex-col justify-center leading-4", children: [
381
- /* @__PURE__ */ jsx(
382
- "span",
383
- {
384
- className: cn(
385
- "w-full text-xs font-medium text-foreground",
386
- live.accountDisplay ? "truncate" : "line-clamp-2"
387
- ),
388
- children: name
389
- }
390
- ),
391
- live.accountDisplay ? /* @__PURE__ */ jsx(
392
- "span",
393
- {
394
- className: "w-full truncate text-[11px] leading-4 text-muted-foreground",
395
- "data-testid": `account-${provider.providerId}`,
396
- children: live.accountDisplay
397
- }
398
- ) : null
399
- ] })
400
- ] }),
401
- /* @__PURE__ */ jsxs(DropdownMenu, { children: [
402
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
403
- "button",
404
- {
405
- type: "button",
406
- "data-testid": `menu-${provider.providerId}`,
407
- "aria-label": `More actions for ${name}`,
408
- title: "More actions",
409
- 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}`,
410
- children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-3.5 w-3.5" })
411
- }
412
- ) }),
413
- /* @__PURE__ */ jsxs(
414
- DropdownMenuContent,
415
- {
416
- side: "right",
417
- align: "start",
418
- className: "min-w-[160px] rounded-md",
419
- children: [
420
- manageHref ? /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsxs(
421
- "a",
422
- {
423
- href: manageHref,
424
- target: "_blank",
425
- rel: "noopener noreferrer",
426
- "data-testid": `manage-item-${provider.providerId}`,
427
- children: [
428
- /* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }),
429
- " Manage"
430
- ]
431
- }
432
- ) }) : onManage ? /* @__PURE__ */ jsxs(
433
- DropdownMenuItem,
434
- {
435
- "data-testid": `manage-item-${provider.providerId}`,
436
- onClick: () => onManage({
437
- connectionId: live.id,
438
- providerId: provider.providerId
439
- }),
440
- children: [
441
- /* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }),
442
- " Manage"
443
- ]
444
- }
445
- ) : null,
446
- canManage ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null,
447
- /* @__PURE__ */ jsxs(
448
- DropdownMenuItem,
449
- {
450
- "data-testid": `disconnect-${provider.providerId}`,
451
- className: "text-destructive focus:text-destructive",
452
- onClick: () => setDisconnectTarget({
453
- connectionId: live.id,
454
- name,
455
- accountDisplay: live.accountDisplay
456
- }),
457
- children: [
458
- /* @__PURE__ */ jsx(Unplug, { className: "mr-2 h-4 w-4" }),
459
- " Disconnect"
460
- ]
461
- }
462
- )
463
- ]
464
- }
465
- )
466
- ] })
467
- ]
468
- },
469
- `${provider.providerId}:${connectorId}`
470
- );
471
- }
472
- return /* @__PURE__ */ jsxs(
473
- "button",
474
- {
475
- type: "button",
476
- "data-testid": `integration-${provider.providerId}`,
477
- "data-connected": "false",
478
- "data-connecting": connectingId === provider.providerId ? "true" : void 0,
479
- disabled: connectingId === provider.providerId,
480
- "aria-busy": connectingId === provider.providerId,
481
- onClick: () => {
482
- void startConnect({
483
- providerId: provider.providerId,
484
- connectorId
485
- });
486
- },
487
- title: provider.description ? provider.description : `Connect ${name}`,
488
- className: cn(
489
- TILE_BOX_CLASS,
490
- "group border-border bg-card transition-all",
491
- "hover:border-[var(--border-strong)] hover:bg-accent/40 hover:shadow-sm",
492
- focusRing
493
- ),
494
- children: [
495
- /* @__PURE__ */ jsx(
496
- ProviderIcon,
497
- {
498
- id: provider.providerId,
499
- iconUrl: provider.iconUrl,
500
- displayName: displayNameOf(provider),
501
- size: LOGO_SIZE,
502
- className: "rounded-2xl"
503
- }
504
- ),
505
- /* @__PURE__ */ jsx("span", { className: "line-clamp-2 h-8 w-full text-xs font-medium leading-4 text-foreground", children: name })
506
- ]
507
- },
508
- `${provider.providerId}:${connectorId}`
509
- );
510
- }) }),
511
- /* @__PURE__ */ jsx(
512
- Dialog,
513
- {
514
- open: !!disconnectTarget,
515
- onOpenChange: (open) => {
516
- if (!open) closeDisconnect();
517
- },
518
- children: /* @__PURE__ */ jsxs(
519
- DialogContent,
520
- {
521
- className: "max-w-sm",
522
- onCloseAutoFocus: (e) => e.preventDefault(),
523
- children: [
524
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
525
- /* @__PURE__ */ jsxs(DialogTitle, { children: [
526
- "Disconnect ",
527
- disconnectTarget?.name ?? "integration",
528
- "?"
529
- ] }),
530
- /* @__PURE__ */ jsxs(DialogDescription, { children: [
531
- "This removes Sandbox's access to your",
532
- " ",
533
- disconnectTarget?.name ?? "this",
534
- " account",
535
- disconnectAccountLabel ? ` (${disconnectAccountLabel})` : "",
536
- ". You can reconnect anytime."
537
- ] })
538
- ] }),
539
- disconnectError ? /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", role: "alert", children: disconnectError }) : null,
540
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
541
- /* @__PURE__ */ jsx(
542
- Button,
543
- {
544
- variant: "outline",
545
- onClick: closeDisconnect,
546
- "data-testid": "cancel-disconnect",
547
- children: "Cancel"
548
- }
549
- ),
550
- /* @__PURE__ */ jsx(
551
- Button,
552
- {
553
- variant: "destructive",
554
- loading: isDisconnecting,
555
- onClick: confirmDisconnect,
556
- "data-testid": "confirm-disconnect",
557
- children: "Disconnect"
558
- }
559
- )
560
- ] })
561
- ]
562
- }
563
- )
564
- }
565
- )
566
- ] });
121
+ function IntegrationsPanel({ catalog, connections, healthByConnectionId, isLoading, error, onConnect, onDisconnect, getManageHref, onManage, emptyCatalogLabel = "No integrations available yet.", featuredIds = DEFAULT_FEATURED_IDS, defaultSort = "featured", skeletonCount = DEFAULT_SKELETON_COUNT, className }) {
122
+ const [query, setQuery] = React$1.useState("");
123
+ const [sort, setSort] = React$1.useState(defaultSort);
124
+ const [disconnectTarget, setDisconnectTarget] = React$1.useState(null);
125
+ const [isDisconnecting, setIsDisconnecting] = React$1.useState(false);
126
+ const [disconnectError, setDisconnectError] = React$1.useState(null);
127
+ const activeDisconnect = React$1.useRef(0);
128
+ const connectionIndex = React$1.useMemo(() => buildConnectionIndex(connections), [connections]);
129
+ const featuredRank = React$1.useMemo(() => makeFeaturedRank(featuredIds), [featuredIds]);
130
+ const visible = React$1.useMemo(() => {
131
+ const q = query.trim().toLowerCase();
132
+ const sorted = [...q ? catalog.filter((p) => matchesQuery(p, q)) : catalog];
133
+ sorted.sort((a, b) => {
134
+ if (sort === "featured") {
135
+ const ra = rankOf(a, featuredRank);
136
+ const rb = rankOf(b, featuredRank);
137
+ if (ra !== rb) return ra - rb;
138
+ }
139
+ return displayNameOf(a).localeCompare(displayNameOf(b));
140
+ });
141
+ return sorted;
142
+ }, [
143
+ catalog,
144
+ query,
145
+ sort,
146
+ featuredRank
147
+ ]);
148
+ const closeDisconnect = React$1.useCallback(() => {
149
+ activeDisconnect.current += 1;
150
+ setIsDisconnecting(false);
151
+ setDisconnectTarget(null);
152
+ setDisconnectError(null);
153
+ }, []);
154
+ const [connectingId, setConnectingId] = React$1.useState(null);
155
+ const [connectError, setConnectError] = React$1.useState(null);
156
+ const activeConnect = React$1.useRef(0);
157
+ const startConnect = React$1.useCallback(async (input) => {
158
+ const reqId = activeConnect.current += 1;
159
+ setConnectError(null);
160
+ let pending;
161
+ try {
162
+ pending = onConnect(input);
163
+ } catch (e) {
164
+ setConnectError(e instanceof Error ? e.message : "Failed to start the connection.");
165
+ return;
166
+ }
167
+ if (typeof pending?.then !== "function") return;
168
+ setConnectingId(input.providerId);
169
+ try {
170
+ await pending;
171
+ } catch (e) {
172
+ if (activeConnect.current !== reqId) return;
173
+ setConnectError(e instanceof Error ? e.message : "Failed to start the connection.");
174
+ } finally {
175
+ if (activeConnect.current === reqId) setConnectingId(null);
176
+ }
177
+ }, [onConnect]);
178
+ const confirmDisconnect = React$1.useCallback(async () => {
179
+ if (!disconnectTarget) return;
180
+ const reqId = activeDisconnect.current += 1;
181
+ setIsDisconnecting(true);
182
+ setDisconnectError(null);
183
+ try {
184
+ await Promise.resolve(onDisconnect(disconnectTarget.connectionId));
185
+ if (activeDisconnect.current !== reqId) return;
186
+ setDisconnectTarget(null);
187
+ } catch (e) {
188
+ if (activeDisconnect.current !== reqId) return;
189
+ setDisconnectError(e instanceof Error ? e.message : "Failed to disconnect.");
190
+ } finally {
191
+ if (activeDisconnect.current === reqId) setIsDisconnecting(false);
192
+ }
193
+ }, [disconnectTarget, onDisconnect]);
194
+ if (error) return /* @__PURE__ */ jsx(Card, {
195
+ className: cn("border-destructive/50", className),
196
+ children: /* @__PURE__ */ jsx(CardContent, {
197
+ className: "py-6",
198
+ children: /* @__PURE__ */ jsxs("p", {
199
+ className: "text-sm text-destructive",
200
+ children: ["Failed to load integrations: ", error.message]
201
+ })
202
+ })
203
+ });
204
+ if (isLoading && catalog.length === 0) return /* @__PURE__ */ jsxs("div", {
205
+ className: cn("space-y-4", className),
206
+ "data-testid": "integrations-skeleton",
207
+ children: [/* @__PURE__ */ jsx(IntegrationsToolbar, {
208
+ query,
209
+ onQueryChange: setQuery,
210
+ sort,
211
+ onSortChange: setSort
212
+ }), /* @__PURE__ */ jsx("div", {
213
+ className: TILE_GRID_CLASS,
214
+ children: Array.from({ length: skeletonCount }).map((_, i) => /* @__PURE__ */ jsxs("div", {
215
+ "aria-hidden": true,
216
+ className: cn(TILE_BOX_CLASS, "animate-pulse border-border bg-muted/40"),
217
+ children: [/* @__PURE__ */ jsx("div", {
218
+ className: "shrink-0 rounded-2xl bg-muted",
219
+ style: {
220
+ width: LOGO_SIZE,
221
+ height: LOGO_SIZE
222
+ }
223
+ }), /* @__PURE__ */ jsx("div", {
224
+ className: "flex h-8 w-full items-center justify-center",
225
+ children: /* @__PURE__ */ jsx("div", { className: "h-2.5 w-3/4 rounded bg-muted" })
226
+ })]
227
+ }, i))
228
+ })]
229
+ });
230
+ if (catalog.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
231
+ title: "No integrations",
232
+ description: emptyCatalogLabel,
233
+ className
234
+ });
235
+ const disconnectAccountLabel = disconnectTarget?.accountDisplay;
236
+ return /* @__PURE__ */ jsxs("div", {
237
+ className: cn("space-y-4", className),
238
+ "data-testid": "integrations-panel",
239
+ children: [
240
+ /* @__PURE__ */ jsx(IntegrationsToolbar, {
241
+ query,
242
+ onQueryChange: setQuery,
243
+ sort,
244
+ onSortChange: setSort
245
+ }),
246
+ connectError ? /* @__PURE__ */ jsx("div", {
247
+ role: "alert",
248
+ "data-testid": "integration-connect-error",
249
+ className: "mb-3 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive",
250
+ children: connectError
251
+ }) : null,
252
+ visible.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
253
+ title: "No matches",
254
+ description: `No integrations match "${query.trim()}".`
255
+ }) : /* @__PURE__ */ jsx("div", {
256
+ className: TILE_GRID_CLASS,
257
+ children: visible.map((provider) => {
258
+ const connectorId = defaultConnectorOf(provider);
259
+ const live = connectionIndex.get(`${provider.providerId}:${connectorId}`);
260
+ const name = displayNameOf(provider);
261
+ if (Boolean(live) && live) {
262
+ const manageHref = getManageHref?.(live);
263
+ const canManage = Boolean(manageHref) || Boolean(onManage);
264
+ return /* @__PURE__ */ jsxs("div", {
265
+ "data-testid": `integration-${provider.providerId}`,
266
+ "data-connected": "true",
267
+ title: live.accountDisplay ? `${name} — ${live.accountDisplay}` : name,
268
+ className: cn(TILE_BOX_CLASS, "group relative", "border-[var(--surface-success-border)] bg-[var(--surface-success-bg)]"),
269
+ children: [
270
+ manageHref ? /* @__PURE__ */ jsx("a", {
271
+ href: manageHref,
272
+ target: "_blank",
273
+ rel: "noopener noreferrer",
274
+ "aria-label": `Manage ${name} (opens in new window)`,
275
+ "data-testid": `manage-${provider.providerId}`,
276
+ className: `absolute inset-0 z-10 rounded-xl ${focusRing}`
277
+ }) : null,
278
+ /* @__PURE__ */ jsx("span", {
279
+ 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",
280
+ children: /* @__PURE__ */ jsx(Check, {
281
+ className: "h-3 w-3",
282
+ strokeWidth: 3
283
+ })
284
+ }),
285
+ /* @__PURE__ */ jsxs("div", {
286
+ className: "pointer-events-none flex w-full min-w-0 flex-col items-center gap-2",
287
+ children: [/* @__PURE__ */ jsx(ProviderIcon, {
288
+ id: provider.providerId,
289
+ iconUrl: provider.iconUrl,
290
+ displayName: displayNameOf(provider),
291
+ size: LOGO_SIZE,
292
+ className: "rounded-2xl"
293
+ }), /* @__PURE__ */ jsxs("div", {
294
+ className: "flex h-8 w-full flex-col justify-center leading-4",
295
+ children: [/* @__PURE__ */ jsx("span", {
296
+ className: cn("w-full text-xs font-medium text-foreground", live.accountDisplay ? "truncate" : "line-clamp-2"),
297
+ children: name
298
+ }), live.accountDisplay ? /* @__PURE__ */ jsx("span", {
299
+ className: "w-full truncate text-[11px] leading-4 text-muted-foreground",
300
+ "data-testid": `account-${provider.providerId}`,
301
+ children: live.accountDisplay
302
+ }) : null]
303
+ })]
304
+ }),
305
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
306
+ asChild: true,
307
+ children: /* @__PURE__ */ jsx("button", {
308
+ type: "button",
309
+ "data-testid": `menu-${provider.providerId}`,
310
+ "aria-label": `More actions for ${name}`,
311
+ title: "More actions",
312
+ 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}`,
313
+ children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-3.5 w-3.5" })
314
+ })
315
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
316
+ side: "right",
317
+ align: "start",
318
+ className: "min-w-[160px] rounded-md",
319
+ children: [
320
+ manageHref ? /* @__PURE__ */ jsx(DropdownMenuItem, {
321
+ asChild: true,
322
+ children: /* @__PURE__ */ jsxs("a", {
323
+ href: manageHref,
324
+ target: "_blank",
325
+ rel: "noopener noreferrer",
326
+ "data-testid": `manage-item-${provider.providerId}`,
327
+ children: [/* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Manage"]
328
+ })
329
+ }) : onManage ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
330
+ "data-testid": `manage-item-${provider.providerId}`,
331
+ onClick: () => onManage({
332
+ connectionId: live.id,
333
+ providerId: provider.providerId
334
+ }),
335
+ children: [/* @__PURE__ */ jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), " Manage"]
336
+ }) : null,
337
+ canManage ? /* @__PURE__ */ jsx(DropdownMenuSeparator, {}) : null,
338
+ /* @__PURE__ */ jsxs(DropdownMenuItem, {
339
+ "data-testid": `disconnect-${provider.providerId}`,
340
+ className: "text-destructive focus:text-destructive",
341
+ onClick: () => setDisconnectTarget({
342
+ connectionId: live.id,
343
+ name,
344
+ accountDisplay: live.accountDisplay
345
+ }),
346
+ children: [/* @__PURE__ */ jsx(Unplug, { className: "mr-2 h-4 w-4" }), " Disconnect"]
347
+ })
348
+ ]
349
+ })] })
350
+ ]
351
+ }, `${provider.providerId}:${connectorId}`);
352
+ }
353
+ return /* @__PURE__ */ jsxs("button", {
354
+ type: "button",
355
+ "data-testid": `integration-${provider.providerId}`,
356
+ "data-connected": "false",
357
+ "data-connecting": connectingId === provider.providerId ? "true" : void 0,
358
+ disabled: connectingId === provider.providerId,
359
+ "aria-busy": connectingId === provider.providerId,
360
+ onClick: () => {
361
+ startConnect({
362
+ providerId: provider.providerId,
363
+ connectorId
364
+ });
365
+ },
366
+ title: provider.description ? provider.description : `Connect ${name}`,
367
+ className: cn(TILE_BOX_CLASS, "group border-border bg-card transition-all", "hover:border-[var(--border-strong)] hover:bg-accent/40 hover:shadow-sm", focusRing),
368
+ children: [/* @__PURE__ */ jsx(ProviderIcon, {
369
+ id: provider.providerId,
370
+ iconUrl: provider.iconUrl,
371
+ displayName: displayNameOf(provider),
372
+ size: LOGO_SIZE,
373
+ className: "rounded-2xl"
374
+ }), /* @__PURE__ */ jsx("span", {
375
+ className: "line-clamp-2 h-8 w-full text-xs font-medium leading-4 text-foreground",
376
+ children: name
377
+ })]
378
+ }, `${provider.providerId}:${connectorId}`);
379
+ })
380
+ }),
381
+ /* @__PURE__ */ jsx(Dialog, {
382
+ open: !!disconnectTarget,
383
+ onOpenChange: (open) => {
384
+ if (!open) closeDisconnect();
385
+ },
386
+ children: /* @__PURE__ */ jsxs(DialogContent, {
387
+ className: "max-w-sm",
388
+ onCloseAutoFocus: (e) => e.preventDefault(),
389
+ children: [
390
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsxs(DialogTitle, { children: [
391
+ "Disconnect ",
392
+ disconnectTarget?.name ?? "integration",
393
+ "?"
394
+ ] }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
395
+ "This removes Sandbox's access to your",
396
+ " ",
397
+ disconnectTarget?.name ?? "this",
398
+ " account",
399
+ disconnectAccountLabel ? ` (${disconnectAccountLabel})` : "",
400
+ ". You can reconnect anytime."
401
+ ] })] }),
402
+ disconnectError ? /* @__PURE__ */ jsx("p", {
403
+ className: "text-sm text-destructive",
404
+ role: "alert",
405
+ children: disconnectError
406
+ }) : null,
407
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
408
+ variant: "outline",
409
+ onClick: closeDisconnect,
410
+ "data-testid": "cancel-disconnect",
411
+ children: "Cancel"
412
+ }), /* @__PURE__ */ jsx(Button, {
413
+ variant: "destructive",
414
+ loading: isDisconnecting,
415
+ onClick: confirmDisconnect,
416
+ "data-testid": "confirm-disconnect",
417
+ children: "Disconnect"
418
+ })] })
419
+ ]
420
+ })
421
+ })
422
+ ]
423
+ });
567
424
  }
568
-
569
- // src/integrations/use-integrations.ts
570
- import * as React2 from "react";
425
+ //#endregion
426
+ //#region src/integrations/use-integrations.ts
571
427
  function unwrap(json) {
572
- if (json && typeof json === "object" && "data" in json && json.data !== void 0) {
573
- return json.data;
574
- }
575
- return json;
428
+ if (json && typeof json === "object" && "data" in json && json.data !== void 0) return json.data;
429
+ return json;
576
430
  }
577
- function useIntegrations({
578
- apiBaseUrl,
579
- fetchImpl,
580
- autoLoad = true
581
- }) {
582
- const fetcher = fetchImpl ?? (typeof fetch === "function" ? fetch : null);
583
- if (!fetcher) {
584
- throw new Error("useIntegrations: fetch is not available in this environment");
585
- }
586
- const base = apiBaseUrl.replace(/\/+$/, "");
587
- const [catalog, setCatalog] = React2.useState([]);
588
- const [connections, setConnections] = React2.useState([]);
589
- const [healthByConnectionId, setHealthByConnectionId] = React2.useState({});
590
- const [isLoading, setIsLoading] = React2.useState(autoLoad);
591
- const [error, setError] = React2.useState(null);
592
- const refresh = React2.useCallback(async () => {
593
- setIsLoading(true);
594
- setError(null);
595
- try {
596
- const [catalogRes, connectionsRes] = await Promise.all([
597
- fetcher(`${base}/catalog`, { credentials: "include" }),
598
- fetcher(`${base}/connections`, { credentials: "include" })
599
- ]);
600
- if (!catalogRes.ok) {
601
- throw new Error(`Failed to load integration catalog (${catalogRes.status})`);
602
- }
603
- if (!connectionsRes.ok) {
604
- throw new Error(
605
- `Failed to load integration connections (${connectionsRes.status})`
606
- );
607
- }
608
- const catalogJson = unwrap(await catalogRes.json());
609
- const providers = catalogJson?.catalog?.providers ?? catalogJson?.providers ?? [];
610
- setCatalog(providers);
611
- const connectionsJson = unwrap(
612
- await connectionsRes.json()
613
- );
614
- setConnections(connectionsJson?.connections ?? []);
615
- try {
616
- const healthRes = await fetcher(`${base}/healthchecks`, {
617
- credentials: "include"
618
- });
619
- if (healthRes.ok) {
620
- const healthJson = unwrap(
621
- await healthRes.json()
622
- );
623
- const map = {};
624
- for (const h of healthJson?.healthchecks ?? []) {
625
- map[h.connectionId] = h;
626
- }
627
- setHealthByConnectionId(map);
628
- }
629
- } catch {
630
- }
631
- } catch (err) {
632
- setError(err instanceof Error ? err : new Error(String(err)));
633
- } finally {
634
- setIsLoading(false);
635
- }
636
- }, [base, fetcher]);
637
- React2.useEffect(() => {
638
- if (autoLoad) {
639
- void refresh();
640
- }
641
- }, [autoLoad, refresh]);
642
- const connect = React2.useCallback(
643
- async (input) => {
644
- const res = await fetcher(`${base}/auth/start`, {
645
- method: "POST",
646
- credentials: "include",
647
- headers: { "content-type": "application/json" },
648
- body: JSON.stringify(input)
649
- });
650
- if (!res.ok) {
651
- const text = await res.text().catch(() => "");
652
- throw new Error(`Failed to start OAuth (${res.status}): ${text}`);
653
- }
654
- const json = unwrap(await res.json());
655
- if (!json?.authorizationUrl) {
656
- throw new Error("Platform did not return an authorizationUrl");
657
- }
658
- window.location.href = json.authorizationUrl;
659
- },
660
- [base, fetcher]
661
- );
662
- const disconnect = React2.useCallback(
663
- async (connectionId) => {
664
- const res = await fetcher(
665
- `${base}/connections/${encodeURIComponent(connectionId)}`,
666
- { method: "DELETE", credentials: "include" }
667
- );
668
- if (!res.ok) {
669
- const text = await res.text().catch(() => "");
670
- throw new Error(`Failed to revoke connection (${res.status}): ${text}`);
671
- }
672
- await refresh();
673
- },
674
- [base, fetcher, refresh]
675
- );
676
- return {
677
- catalog,
678
- connections,
679
- healthByConnectionId,
680
- isLoading,
681
- error,
682
- refresh,
683
- connect,
684
- disconnect
685
- };
431
+ function useIntegrations({ apiBaseUrl, fetchImpl, autoLoad = true }) {
432
+ const fetcher = fetchImpl ?? (typeof fetch === "function" ? fetch : null);
433
+ if (!fetcher) throw new Error("useIntegrations: fetch is not available in this environment");
434
+ const base = apiBaseUrl.replace(/\/+$/, "");
435
+ const [catalog, setCatalog] = React$1.useState([]);
436
+ const [connections, setConnections] = React$1.useState([]);
437
+ const [healthByConnectionId, setHealthByConnectionId] = React$1.useState({});
438
+ const [isLoading, setIsLoading] = React$1.useState(autoLoad);
439
+ const [error, setError] = React$1.useState(null);
440
+ const refresh = React$1.useCallback(async () => {
441
+ setIsLoading(true);
442
+ setError(null);
443
+ try {
444
+ const [catalogRes, connectionsRes] = await Promise.all([fetcher(`${base}/catalog`, { credentials: "include" }), fetcher(`${base}/connections`, { credentials: "include" })]);
445
+ if (!catalogRes.ok) throw new Error(`Failed to load integration catalog (${catalogRes.status})`);
446
+ if (!connectionsRes.ok) throw new Error(`Failed to load integration connections (${connectionsRes.status})`);
447
+ const catalogJson = unwrap(await catalogRes.json());
448
+ const providers = catalogJson?.catalog?.providers ?? catalogJson?.providers ?? [];
449
+ setCatalog(providers);
450
+ const connectionsJson = unwrap(await connectionsRes.json());
451
+ setConnections(connectionsJson?.connections ?? []);
452
+ try {
453
+ const healthRes = await fetcher(`${base}/healthchecks`, { credentials: "include" });
454
+ if (healthRes.ok) {
455
+ const healthJson = unwrap(await healthRes.json());
456
+ const map = {};
457
+ for (const h of healthJson?.healthchecks ?? []) map[h.connectionId] = h;
458
+ setHealthByConnectionId(map);
459
+ }
460
+ } catch {}
461
+ } catch (err) {
462
+ setError(err instanceof Error ? err : new Error(String(err)));
463
+ } finally {
464
+ setIsLoading(false);
465
+ }
466
+ }, [base, fetcher]);
467
+ React$1.useEffect(() => {
468
+ if (autoLoad) refresh();
469
+ }, [autoLoad, refresh]);
470
+ return {
471
+ catalog,
472
+ connections,
473
+ healthByConnectionId,
474
+ isLoading,
475
+ error,
476
+ refresh,
477
+ connect: React$1.useCallback(async (input) => {
478
+ const res = await fetcher(`${base}/auth/start`, {
479
+ method: "POST",
480
+ credentials: "include",
481
+ headers: { "content-type": "application/json" },
482
+ body: JSON.stringify(input)
483
+ });
484
+ if (!res.ok) {
485
+ const text = await res.text().catch(() => "");
486
+ throw new Error(`Failed to start OAuth (${res.status}): ${text}`);
487
+ }
488
+ const json = unwrap(await res.json());
489
+ if (!json?.authorizationUrl) throw new Error("Platform did not return an authorizationUrl");
490
+ window.location.href = json.authorizationUrl;
491
+ }, [base, fetcher]),
492
+ disconnect: React$1.useCallback(async (connectionId) => {
493
+ const res = await fetcher(`${base}/connections/${encodeURIComponent(connectionId)}`, {
494
+ method: "DELETE",
495
+ credentials: "include"
496
+ });
497
+ if (!res.ok) {
498
+ const text = await res.text().catch(() => "");
499
+ throw new Error(`Failed to revoke connection (${res.status}): ${text}`);
500
+ }
501
+ await refresh();
502
+ }, [
503
+ base,
504
+ fetcher,
505
+ refresh
506
+ ])
507
+ };
686
508
  }
687
- export {
688
- IntegrationsPanel,
689
- ProviderIcon,
690
- providerLogoCandidates,
691
- useIntegrations
692
- };
509
+ //#endregion
510
+ export { IntegrationsPanel, ProviderIcon, providerLogoCandidates, useIntegrations };