@use-handled/ipaas-react-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1091 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/components/HandledProvider.tsx
7
+
8
+ // src/api/client.ts
9
+ var DEFAULT_API_BASE_URL = "https://api.usehandled.io";
10
+ var HandledApiClient = class {
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+ get baseUrl() {
15
+ return this.config.apiBaseUrl || DEFAULT_API_BASE_URL;
16
+ }
17
+ get headers() {
18
+ return {
19
+ "Content-Type": "application/json",
20
+ "X-Client-Id": this.config.clientId,
21
+ "X-Account-Id": this.config.accountId
22
+ };
23
+ }
24
+ async request(method, path, options = {}) {
25
+ let url = `${this.baseUrl}/api/v1/ipaas${path}`;
26
+ if (options.params) {
27
+ const searchParams = new URLSearchParams();
28
+ Object.entries(options.params).forEach(([key, value]) => {
29
+ if (value !== void 0) {
30
+ searchParams.append(key, String(value));
31
+ }
32
+ });
33
+ const queryString = searchParams.toString();
34
+ if (queryString) {
35
+ url += `?${queryString}`;
36
+ }
37
+ }
38
+ const response = await fetch(url, {
39
+ method,
40
+ headers: this.headers,
41
+ body: options.body ? JSON.stringify(options.body) : void 0
42
+ });
43
+ if (!response.ok) {
44
+ const error = await response.json().catch(() => ({ message: "Request failed" }));
45
+ throw new Error(error.message || error.error || `Request failed with status ${response.status}`);
46
+ }
47
+ if (response.status === 204) {
48
+ return void 0;
49
+ }
50
+ return response.json();
51
+ }
52
+ // ============================================
53
+ // Integrations API
54
+ // ============================================
55
+ /**
56
+ * Get list of available integrations
57
+ */
58
+ async getIntegrations() {
59
+ const response = await this.request("GET", "/integrations");
60
+ return response.integrations;
61
+ }
62
+ // ============================================
63
+ // Connections API
64
+ // ============================================
65
+ /**
66
+ * Get list of connections for the current account
67
+ */
68
+ async getConnections() {
69
+ const response = await this.request(
70
+ "GET",
71
+ `/accounts/${this.config.accountId}/connections`
72
+ );
73
+ return response.connections;
74
+ }
75
+ /**
76
+ * Initiate connection to an integration
77
+ * Returns an auth URL to open in a popup for OAuth, or creates connection directly for other auth types
78
+ */
79
+ async connect(integrationId, options) {
80
+ return this.request(
81
+ "POST",
82
+ `/accounts/${this.config.accountId}/connections`,
83
+ {
84
+ body: {
85
+ integration_id: integrationId,
86
+ redirect_url: options?.redirectUrl || window.location.href,
87
+ credentials: options?.credentials,
88
+ account_integration_name: options?.accountIntegrationName
89
+ }
90
+ }
91
+ );
92
+ }
93
+ /**
94
+ * Disconnect an integration
95
+ */
96
+ async disconnect(connectionId) {
97
+ return this.request(
98
+ "DELETE",
99
+ `/accounts/${this.config.accountId}/connections/${connectionId}`
100
+ );
101
+ }
102
+ /**
103
+ * Get a specific connection
104
+ */
105
+ async getConnection(connectionId) {
106
+ return this.request(
107
+ "GET",
108
+ `/accounts/${this.config.accountId}/connections/${connectionId}`
109
+ );
110
+ }
111
+ /**
112
+ * Check connection status (used after OAuth popup closes)
113
+ */
114
+ async checkConnectionStatus(connectionId) {
115
+ return this.request(
116
+ "GET",
117
+ `/accounts/${this.config.accountId}/connections/${connectionId}/status`
118
+ );
119
+ }
120
+ };
121
+ var HandledContext = react.createContext(null);
122
+ var POPUP_WIDTH = 600;
123
+ var POPUP_HEIGHT = 700;
124
+ function openPopup(url, name) {
125
+ const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
126
+ const top = window.screenY + (window.outerHeight - POPUP_HEIGHT) / 2;
127
+ return window.open(
128
+ url,
129
+ name,
130
+ `width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},toolbar=no,menubar=no,scrollbars=yes,resizable=yes`
131
+ );
132
+ }
133
+ function HandledProvider({
134
+ clientId,
135
+ accountId,
136
+ apiBaseUrl,
137
+ onConnect,
138
+ onDisconnect,
139
+ onError,
140
+ children
141
+ }) {
142
+ const [isReady, setIsReady] = react.useState(false);
143
+ const [isLoading, setIsLoading] = react.useState(false);
144
+ const [integrations, setIntegrations] = react.useState([]);
145
+ const [connections, setConnections] = react.useState([]);
146
+ const [error, setError] = react.useState(null);
147
+ const config = react.useMemo(
148
+ () => ({ clientId, accountId, apiBaseUrl }),
149
+ [clientId, accountId, apiBaseUrl]
150
+ );
151
+ const apiClientRef = react.useRef(null);
152
+ const getApiClient = react.useCallback(() => {
153
+ if (!apiClientRef.current) {
154
+ apiClientRef.current = new HandledApiClient(config);
155
+ }
156
+ return apiClientRef.current;
157
+ }, [config]);
158
+ react.useEffect(() => {
159
+ const initialize = async () => {
160
+ setIsLoading(true);
161
+ try {
162
+ const client = getApiClient();
163
+ const [integrationsData, connectionsData] = await Promise.all([
164
+ client.getIntegrations(),
165
+ client.getConnections()
166
+ ]);
167
+ setIntegrations(integrationsData);
168
+ setConnections(connectionsData);
169
+ setError(null);
170
+ } catch (err) {
171
+ const initError = err instanceof Error ? err : new Error("Failed to initialize");
172
+ setError(initError);
173
+ onError?.(initError);
174
+ } finally {
175
+ setIsLoading(false);
176
+ setIsReady(true);
177
+ }
178
+ };
179
+ initialize();
180
+ }, [getApiClient, onError]);
181
+ const refreshConnections = react.useCallback(async () => {
182
+ try {
183
+ const client = getApiClient();
184
+ const connectionsData = await client.getConnections();
185
+ setConnections(connectionsData);
186
+ } catch (err) {
187
+ const refreshError = err instanceof Error ? err : new Error("Failed to refresh connections");
188
+ setError(refreshError);
189
+ onError?.(refreshError);
190
+ }
191
+ }, [getApiClient, onError]);
192
+ const connect = react.useCallback(
193
+ async (integrationId, options) => {
194
+ setIsLoading(true);
195
+ setError(null);
196
+ try {
197
+ const client = getApiClient();
198
+ const integration = integrations.find(
199
+ (i) => i.id === integrationId || i.type === integrationId
200
+ );
201
+ const isOAuth = integration?.authType === "oauth" || integration?.authType === "oauth2" || integration?.authType === "oauth1";
202
+ const response = await client.connect(integrationId, {
203
+ credentials: options?.credentials,
204
+ accountIntegrationName: options?.accountIntegrationName
205
+ });
206
+ if (!isOAuth) {
207
+ setIsLoading(false);
208
+ if (response.success && (response.connection || response.account_integration)) {
209
+ const connection = response.connection || response.account_integration;
210
+ setConnections((prev) => [...prev, connection]);
211
+ onConnect?.(connection);
212
+ return { success: true, connection };
213
+ } else {
214
+ const errorMsg = response.message || "Failed to connect";
215
+ const connError = new Error(errorMsg);
216
+ setError(connError);
217
+ onError?.(connError);
218
+ return { success: false, error: errorMsg };
219
+ }
220
+ }
221
+ const authUrl = response.authUrl || response.redirect_url;
222
+ if (!authUrl) {
223
+ throw new Error("No authorization URL received");
224
+ }
225
+ const popup = openPopup(authUrl, "handled-connect");
226
+ if (!popup) {
227
+ throw new Error("Popup blocked. Please allow popups for this site.");
228
+ }
229
+ return new Promise((resolve) => {
230
+ const pollInterval = setInterval(async () => {
231
+ if (popup.closed) {
232
+ clearInterval(pollInterval);
233
+ setIsLoading(false);
234
+ try {
235
+ if (response.connectionId) {
236
+ const connection = await client.checkConnectionStatus(response.connectionId);
237
+ if (connection.status === "active") {
238
+ setConnections((prev) => [...prev, connection]);
239
+ onConnect?.(connection);
240
+ resolve({ success: true, connection });
241
+ } else if (connection.status === "error") {
242
+ const connError = new Error(connection.error || "Connection failed");
243
+ setError(connError);
244
+ onError?.(connError);
245
+ resolve({ success: false, error: connection.error });
246
+ } else {
247
+ await refreshConnections();
248
+ resolve({ success: true, connection });
249
+ }
250
+ } else {
251
+ await refreshConnections();
252
+ resolve({ success: true });
253
+ }
254
+ } catch (err) {
255
+ resolve({ success: false, error: "Connection was cancelled or failed" });
256
+ }
257
+ }
258
+ }, 500);
259
+ setTimeout(() => {
260
+ clearInterval(pollInterval);
261
+ if (!popup.closed) {
262
+ popup.close();
263
+ }
264
+ setIsLoading(false);
265
+ resolve({ success: false, error: "Connection timed out" });
266
+ }, 10 * 60 * 1e3);
267
+ });
268
+ } catch (err) {
269
+ const connectError = err instanceof Error ? err : new Error("Failed to connect");
270
+ setError(connectError);
271
+ onError?.(connectError);
272
+ setIsLoading(false);
273
+ return { success: false, error: connectError.message };
274
+ }
275
+ },
276
+ [getApiClient, integrations, onConnect, onError, refreshConnections]
277
+ );
278
+ const disconnect = react.useCallback(
279
+ async (connectionId) => {
280
+ setIsLoading(true);
281
+ try {
282
+ const client = getApiClient();
283
+ await client.disconnect(connectionId);
284
+ setConnections((prev) => prev.filter((c) => c.id !== connectionId));
285
+ onDisconnect?.(connectionId);
286
+ setError(null);
287
+ } catch (err) {
288
+ const disconnectError = err instanceof Error ? err : new Error("Failed to disconnect");
289
+ setError(disconnectError);
290
+ onError?.(disconnectError);
291
+ throw disconnectError;
292
+ } finally {
293
+ setIsLoading(false);
294
+ }
295
+ },
296
+ [getApiClient, onDisconnect, onError]
297
+ );
298
+ const contextValue = react.useMemo(
299
+ () => ({
300
+ isReady,
301
+ isLoading,
302
+ integrations,
303
+ connections,
304
+ connect,
305
+ disconnect,
306
+ refreshConnections,
307
+ error,
308
+ accountId
309
+ }),
310
+ [isReady, isLoading, integrations, connections, connect, disconnect, refreshConnections, error, accountId]
311
+ );
312
+ return /* @__PURE__ */ jsxRuntime.jsx(HandledContext.Provider, { value: contextValue, children });
313
+ }
314
+ function useHandledContext() {
315
+ const context = react.useContext(HandledContext);
316
+ if (!context) {
317
+ throw new Error("useHandled must be used within a HandledProvider");
318
+ }
319
+ return context;
320
+ }
321
+ var modalStyles = {
322
+ overlay: {
323
+ position: "fixed",
324
+ inset: 0,
325
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
326
+ display: "flex",
327
+ alignItems: "center",
328
+ justifyContent: "center",
329
+ zIndex: 9999,
330
+ padding: "16px"
331
+ },
332
+ modal: {
333
+ backgroundColor: "#ffffff",
334
+ borderRadius: "16px",
335
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
336
+ width: "100%",
337
+ maxWidth: "480px",
338
+ maxHeight: "90vh",
339
+ overflow: "auto",
340
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
341
+ },
342
+ header: {
343
+ padding: "24px 24px 0"
344
+ },
345
+ titleRow: {
346
+ display: "flex",
347
+ alignItems: "center",
348
+ gap: "12px"
349
+ },
350
+ logo: {
351
+ width: "40px",
352
+ height: "40px",
353
+ borderRadius: "8px",
354
+ objectFit: "contain"
355
+ },
356
+ logoPlaceholder: {
357
+ width: "40px",
358
+ height: "40px",
359
+ borderRadius: "8px",
360
+ backgroundColor: "#f3f4f6",
361
+ display: "flex",
362
+ alignItems: "center",
363
+ justifyContent: "center",
364
+ fontSize: "18px",
365
+ fontWeight: 600,
366
+ color: "#6b7280"
367
+ },
368
+ title: {
369
+ fontSize: "18px",
370
+ fontWeight: 600,
371
+ color: "#111827",
372
+ margin: 0
373
+ },
374
+ description: {
375
+ fontSize: "14px",
376
+ color: "#6b7280",
377
+ marginTop: "8px",
378
+ lineHeight: 1.5
379
+ },
380
+ body: {
381
+ padding: "24px"
382
+ },
383
+ oauthMessage: {
384
+ textAlign: "center",
385
+ padding: "16px",
386
+ backgroundColor: "#f9fafb",
387
+ borderRadius: "8px",
388
+ marginBottom: "16px"
389
+ },
390
+ oauthText: {
391
+ fontSize: "14px",
392
+ color: "#6b7280",
393
+ margin: 0
394
+ },
395
+ infoBox: {
396
+ display: "flex",
397
+ alignItems: "flex-start",
398
+ gap: "12px",
399
+ padding: "12px",
400
+ backgroundColor: "#eff6ff",
401
+ border: "1px solid #bfdbfe",
402
+ borderRadius: "8px",
403
+ marginBottom: "16px"
404
+ },
405
+ infoContent: {
406
+ flex: 1
407
+ },
408
+ infoTitle: {
409
+ fontSize: "14px",
410
+ fontWeight: 500,
411
+ color: "#1e40af",
412
+ margin: 0
413
+ },
414
+ infoText: {
415
+ fontSize: "13px",
416
+ color: "#1e40af",
417
+ margin: "4px 0 0"
418
+ },
419
+ fieldGroup: {
420
+ display: "flex",
421
+ flexDirection: "column",
422
+ gap: "16px"
423
+ },
424
+ field: {
425
+ display: "flex",
426
+ flexDirection: "column",
427
+ gap: "6px"
428
+ },
429
+ label: {
430
+ fontSize: "14px",
431
+ fontWeight: 500,
432
+ color: "#374151"
433
+ },
434
+ required: {
435
+ color: "#ef4444"
436
+ },
437
+ input: {
438
+ padding: "10px 12px",
439
+ fontSize: "14px",
440
+ border: "1px solid #d1d5db",
441
+ borderRadius: "8px",
442
+ outline: "none",
443
+ transition: "border-color 0.2s, box-shadow 0.2s"
444
+ },
445
+ fieldDescription: {
446
+ fontSize: "12px",
447
+ color: "#6b7280"
448
+ },
449
+ select: {
450
+ padding: "10px 12px",
451
+ fontSize: "14px",
452
+ border: "1px solid #d1d5db",
453
+ borderRadius: "8px",
454
+ backgroundColor: "#ffffff",
455
+ cursor: "pointer"
456
+ },
457
+ actions: {
458
+ display: "flex",
459
+ justifyContent: "flex-end",
460
+ gap: "12px",
461
+ padding: "16px 24px 24px",
462
+ borderTop: "1px solid #e5e7eb"
463
+ },
464
+ button: {
465
+ display: "inline-flex",
466
+ alignItems: "center",
467
+ justifyContent: "center",
468
+ gap: "8px",
469
+ padding: "10px 20px",
470
+ fontSize: "14px",
471
+ fontWeight: 500,
472
+ borderRadius: "8px",
473
+ border: "none",
474
+ cursor: "pointer",
475
+ transition: "background-color 0.2s, opacity 0.2s"
476
+ },
477
+ buttonCancel: {
478
+ backgroundColor: "#f3f4f6",
479
+ color: "#374151"
480
+ },
481
+ buttonPrimary: {
482
+ backgroundColor: "#3b82f6",
483
+ color: "#ffffff"
484
+ },
485
+ buttonDisabled: {
486
+ opacity: 0.6,
487
+ cursor: "not-allowed"
488
+ },
489
+ error: {
490
+ padding: "12px",
491
+ backgroundColor: "#fef2f2",
492
+ border: "1px solid #fecaca",
493
+ borderRadius: "8px",
494
+ color: "#dc2626",
495
+ fontSize: "14px",
496
+ marginBottom: "16px"
497
+ },
498
+ spinner: {
499
+ width: "16px",
500
+ height: "16px",
501
+ border: "2px solid rgba(255, 255, 255, 0.3)",
502
+ borderTopColor: "#ffffff",
503
+ borderRadius: "50%",
504
+ animation: "handled-spin 0.6s linear infinite"
505
+ }
506
+ };
507
+ var InfoIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
508
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
509
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
510
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
511
+ ] });
512
+ var ExternalLinkIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
513
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
514
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "15 3 21 3 21 9" }),
515
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
516
+ ] });
517
+ var Spinner = () => /* @__PURE__ */ jsxRuntime.jsx("div", { style: modalStyles.spinner, children: /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes handled-spin { to { transform: rotate(360deg); } }` }) });
518
+ function ConnectionModal({
519
+ isOpen,
520
+ onClose,
521
+ integration,
522
+ onSuccess,
523
+ onError
524
+ }) {
525
+ const { connect } = useHandledContext();
526
+ const [formData, setFormData] = react.useState({});
527
+ const [loading, setLoading] = react.useState(false);
528
+ const [error, setError] = react.useState(null);
529
+ const isOAuth = integration.authType === "oauth" || integration.authType === "oauth2" || integration.authType === "oauth1";
530
+ react.useEffect(() => {
531
+ if (integration) {
532
+ const initialFormData = {};
533
+ if (integration.inputFields) {
534
+ Object.entries(integration.inputFields).forEach(([fieldName, fieldConfig]) => {
535
+ initialFormData[fieldName] = fieldConfig.default || "";
536
+ });
537
+ }
538
+ setFormData(initialFormData);
539
+ setError(null);
540
+ }
541
+ }, [integration]);
542
+ if (!isOpen) return null;
543
+ const displayName = integration.displayName || integration.name;
544
+ const handleInputChange = (e) => {
545
+ const { name, value } = e.target;
546
+ setFormData((prev) => ({
547
+ ...prev,
548
+ [name]: value
549
+ }));
550
+ };
551
+ const handleSubmit = async (e) => {
552
+ e.preventDefault();
553
+ setLoading(true);
554
+ setError(null);
555
+ try {
556
+ const result = await connect(integration.id, {
557
+ credentials: formData,
558
+ accountIntegrationName: formData["account_integration_name"]
559
+ });
560
+ if (result.success && result.connection) {
561
+ onSuccess(result.connection);
562
+ onClose();
563
+ } else {
564
+ const errorMsg = result.error || "Failed to connect";
565
+ setError(errorMsg);
566
+ onError(errorMsg);
567
+ }
568
+ } catch (err) {
569
+ const errorMsg = err instanceof Error ? err.message : "Connection failed";
570
+ setError(errorMsg);
571
+ onError(errorMsg);
572
+ } finally {
573
+ setLoading(false);
574
+ }
575
+ };
576
+ const renderFormFields = () => {
577
+ if (!integration.inputFields) return null;
578
+ return Object.entries(integration.inputFields).sort(([, a], [, b]) => (a.sort_order || 0) - (b.sort_order || 0)).map(([fieldName, fieldConfig]) => {
579
+ const {
580
+ label = fieldName,
581
+ type = "text",
582
+ required = false,
583
+ placeholder = "",
584
+ description = "",
585
+ options
586
+ } = fieldConfig;
587
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.field, children: [
588
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: modalStyles.label, children: [
589
+ label,
590
+ " ",
591
+ required && /* @__PURE__ */ jsxRuntime.jsx("span", { style: modalStyles.required, children: "*" })
592
+ ] }),
593
+ type === "select" && options ? /* @__PURE__ */ jsxRuntime.jsx(
594
+ "select",
595
+ {
596
+ name: fieldName,
597
+ value: formData[fieldName] || "",
598
+ onChange: handleInputChange,
599
+ required,
600
+ style: modalStyles.select,
601
+ children: options.map((option) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: option.value, children: option.name }, option.value))
602
+ }
603
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
604
+ "input",
605
+ {
606
+ type: type === "password" ? "password" : type === "email" ? "email" : "text",
607
+ name: fieldName,
608
+ value: formData[fieldName] || "",
609
+ onChange: handleInputChange,
610
+ placeholder,
611
+ required,
612
+ style: modalStyles.input
613
+ }
614
+ ),
615
+ description && /* @__PURE__ */ jsxRuntime.jsx("span", { style: modalStyles.fieldDescription, children: description })
616
+ ] }, fieldName);
617
+ });
618
+ };
619
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: modalStyles.overlay, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.modal, onClick: (e) => e.stopPropagation(), children: [
620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.header, children: [
621
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.titleRow, children: [
622
+ integration.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: integration.logoUrl, alt: displayName, style: modalStyles.logo }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: modalStyles.logoPlaceholder, children: displayName.charAt(0) }),
623
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { style: modalStyles.title, children: [
624
+ "Connect to ",
625
+ displayName
626
+ ] })
627
+ ] }),
628
+ integration.description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: modalStyles.description, children: integration.description })
629
+ ] }),
630
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [
631
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.body, children: [
632
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: modalStyles.error, children: error }),
633
+ integration.authType === "api_key" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.infoBox, children: [
634
+ /* @__PURE__ */ jsxRuntime.jsx(InfoIcon, {}),
635
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.infoContent, children: [
636
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { style: modalStyles.infoTitle, children: "Where to find your API key" }),
637
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: modalStyles.infoText, children: [
638
+ "Find your API key in your ",
639
+ displayName,
640
+ " account settings or developer section."
641
+ ] })
642
+ ] })
643
+ ] }),
644
+ isOAuth && /* @__PURE__ */ jsxRuntime.jsx("div", { style: modalStyles.oauthMessage, children: /* @__PURE__ */ jsxRuntime.jsxs("p", { style: modalStyles.oauthText, children: [
645
+ "You'll be redirected to ",
646
+ displayName,
647
+ " to authorize access to your account. No credentials will be stored in our system."
648
+ ] }) }),
649
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.fieldGroup, children: [
650
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.field, children: [
651
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: modalStyles.label, children: [
652
+ "Integration Name ",
653
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: modalStyles.required, children: "*" })
654
+ ] }),
655
+ /* @__PURE__ */ jsxRuntime.jsx(
656
+ "input",
657
+ {
658
+ type: "text",
659
+ name: "account_integration_name",
660
+ placeholder: `My ${displayName} Store`,
661
+ value: formData["account_integration_name"] || "",
662
+ onChange: handleInputChange,
663
+ required: true,
664
+ autoFocus: true,
665
+ style: modalStyles.input
666
+ }
667
+ )
668
+ ] }),
669
+ !isOAuth && renderFormFields()
670
+ ] })
671
+ ] }),
672
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: modalStyles.actions, children: [
673
+ /* @__PURE__ */ jsxRuntime.jsx(
674
+ "button",
675
+ {
676
+ type: "button",
677
+ onClick: onClose,
678
+ disabled: loading,
679
+ style: {
680
+ ...modalStyles.button,
681
+ ...modalStyles.buttonCancel,
682
+ ...loading ? modalStyles.buttonDisabled : {}
683
+ },
684
+ children: "Cancel"
685
+ }
686
+ ),
687
+ /* @__PURE__ */ jsxRuntime.jsx(
688
+ "button",
689
+ {
690
+ type: "submit",
691
+ disabled: loading,
692
+ style: {
693
+ ...modalStyles.button,
694
+ ...modalStyles.buttonPrimary,
695
+ ...loading ? modalStyles.buttonDisabled : {}
696
+ },
697
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
698
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
699
+ "Connecting..."
700
+ ] }) : isOAuth ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
701
+ /* @__PURE__ */ jsxRuntime.jsx(ExternalLinkIcon, {}),
702
+ "Connect with ",
703
+ displayName
704
+ ] }) : "Connect"
705
+ }
706
+ )
707
+ ] })
708
+ ] })
709
+ ] }) });
710
+ }
711
+ var styles = {
712
+ container: {
713
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
714
+ maxWidth: "800px",
715
+ margin: "0 auto"
716
+ },
717
+ header: {
718
+ marginBottom: "24px"
719
+ },
720
+ title: {
721
+ fontSize: "24px",
722
+ fontWeight: 600,
723
+ color: "#111827",
724
+ margin: 0
725
+ },
726
+ description: {
727
+ fontSize: "14px",
728
+ color: "#6b7280",
729
+ marginTop: "8px"
730
+ },
731
+ section: {
732
+ marginBottom: "32px"
733
+ },
734
+ sectionTitle: {
735
+ fontSize: "14px",
736
+ fontWeight: 600,
737
+ color: "#374151",
738
+ marginBottom: "16px",
739
+ textTransform: "uppercase",
740
+ letterSpacing: "0.05em"
741
+ },
742
+ grid: {
743
+ display: "grid",
744
+ gridTemplateColumns: "repeat(auto-fill, minmax(200px, 1fr))",
745
+ gap: "16px"
746
+ },
747
+ card: {
748
+ border: "1px solid #e5e7eb",
749
+ borderRadius: "12px",
750
+ padding: "20px",
751
+ backgroundColor: "#ffffff",
752
+ transition: "border-color 0.2s, box-shadow 0.2s",
753
+ cursor: "pointer"
754
+ },
755
+ cardHover: {
756
+ borderColor: "#3b82f6",
757
+ boxShadow: "0 4px 12px rgba(59, 130, 246, 0.15)"
758
+ },
759
+ cardConnected: {
760
+ borderColor: "#10b981",
761
+ backgroundColor: "#f0fdf4"
762
+ },
763
+ cardLogo: {
764
+ width: "48px",
765
+ height: "48px",
766
+ borderRadius: "8px",
767
+ objectFit: "contain",
768
+ marginBottom: "12px",
769
+ backgroundColor: "#f3f4f6"
770
+ },
771
+ cardLogoPlaceholder: {
772
+ width: "48px",
773
+ height: "48px",
774
+ borderRadius: "8px",
775
+ backgroundColor: "#e5e7eb",
776
+ display: "flex",
777
+ alignItems: "center",
778
+ justifyContent: "center",
779
+ marginBottom: "12px",
780
+ fontSize: "20px",
781
+ fontWeight: 600,
782
+ color: "#6b7280"
783
+ },
784
+ cardName: {
785
+ fontSize: "16px",
786
+ fontWeight: 600,
787
+ color: "#111827",
788
+ marginBottom: "4px"
789
+ },
790
+ cardDescription: {
791
+ fontSize: "13px",
792
+ color: "#6b7280",
793
+ marginBottom: "12px",
794
+ lineHeight: 1.4
795
+ },
796
+ cardStatus: {
797
+ display: "flex",
798
+ alignItems: "center",
799
+ gap: "6px",
800
+ fontSize: "12px",
801
+ color: "#10b981",
802
+ fontWeight: 500
803
+ },
804
+ cardStoreName: {
805
+ fontSize: "12px",
806
+ color: "#6b7280",
807
+ marginTop: "4px"
808
+ },
809
+ button: {
810
+ display: "inline-flex",
811
+ alignItems: "center",
812
+ justifyContent: "center",
813
+ gap: "8px",
814
+ padding: "8px 16px",
815
+ fontSize: "14px",
816
+ fontWeight: 500,
817
+ borderRadius: "8px",
818
+ border: "none",
819
+ cursor: "pointer",
820
+ transition: "background-color 0.2s"
821
+ },
822
+ buttonPrimary: {
823
+ backgroundColor: "#3b82f6",
824
+ color: "#ffffff"
825
+ },
826
+ buttonDanger: {
827
+ backgroundColor: "#fef2f2",
828
+ color: "#dc2626"
829
+ },
830
+ buttonDisabled: {
831
+ opacity: 0.6,
832
+ cursor: "not-allowed"
833
+ },
834
+ badge: {
835
+ display: "inline-flex",
836
+ alignItems: "center",
837
+ padding: "2px 8px",
838
+ fontSize: "11px",
839
+ fontWeight: 500,
840
+ borderRadius: "9999px"
841
+ },
842
+ badgeBeta: {
843
+ backgroundColor: "#fef3c7",
844
+ color: "#92400e"
845
+ },
846
+ badgeComingSoon: {
847
+ backgroundColor: "#f3f4f6",
848
+ color: "#6b7280"
849
+ },
850
+ loading: {
851
+ display: "flex",
852
+ alignItems: "center",
853
+ justifyContent: "center",
854
+ padding: "48px",
855
+ color: "#6b7280"
856
+ },
857
+ spinner: {
858
+ width: "24px",
859
+ height: "24px",
860
+ border: "3px solid #e5e7eb",
861
+ borderTopColor: "#3b82f6",
862
+ borderRadius: "50%",
863
+ animation: "spin 1s linear infinite"
864
+ },
865
+ emptyState: {
866
+ textAlign: "center",
867
+ padding: "48px",
868
+ color: "#6b7280"
869
+ }
870
+ };
871
+ var CheckIcon = ({ size = 16 }) => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) });
872
+ var LinkIcon = ({ size = 16 }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
873
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
874
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
875
+ ] });
876
+ var TrashIcon = ({ size = 16 }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
877
+ /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "3 6 5 6 21 6" }),
878
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
879
+ ] });
880
+ var Spinner2 = () => /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.spinner, children: /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` }) });
881
+ function IntegrationCard({
882
+ integration,
883
+ connection,
884
+ onConnect,
885
+ onDisconnect,
886
+ isLoading
887
+ }) {
888
+ const [isHovered, setIsHovered] = react.useState(false);
889
+ const isConnected = !!connection;
890
+ const isComingSoon = integration.status === "coming_soon";
891
+ const isBeta = integration.status === "beta";
892
+ const cardStyle = {
893
+ ...styles.card,
894
+ ...isHovered && !isConnected && !isComingSoon ? styles.cardHover : {},
895
+ ...isConnected ? styles.cardConnected : {},
896
+ ...isComingSoon ? { opacity: 0.6, cursor: "default" } : {}
897
+ };
898
+ return /* @__PURE__ */ jsxRuntime.jsxs(
899
+ "div",
900
+ {
901
+ style: cardStyle,
902
+ onMouseEnter: () => setIsHovered(true),
903
+ onMouseLeave: () => setIsHovered(false),
904
+ children: [
905
+ integration.logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: integration.logoUrl, alt: integration.name, style: styles.cardLogo }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.cardLogoPlaceholder, children: integration.name.charAt(0) }),
906
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.cardName, children: [
907
+ integration.name,
908
+ isBeta && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles.badge, ...styles.badgeBeta, marginLeft: "8px" }, children: "Beta" }),
909
+ isComingSoon && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles.badge, ...styles.badgeComingSoon, marginLeft: "8px" }, children: "Coming Soon" })
910
+ ] }),
911
+ integration.description && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.cardDescription, children: integration.description }),
912
+ isConnected ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
913
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.cardStatus, children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { size: 14 }),
915
+ "Connected"
916
+ ] }),
917
+ connection.storeName && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.cardStoreName, children: connection.storeName }),
918
+ /* @__PURE__ */ jsxRuntime.jsxs(
919
+ "button",
920
+ {
921
+ onClick: (e) => {
922
+ e.stopPropagation();
923
+ onDisconnect();
924
+ },
925
+ disabled: isLoading,
926
+ style: {
927
+ ...styles.button,
928
+ ...styles.buttonDanger,
929
+ ...isLoading ? styles.buttonDisabled : {},
930
+ marginTop: "12px",
931
+ width: "100%"
932
+ },
933
+ children: [
934
+ /* @__PURE__ */ jsxRuntime.jsx(TrashIcon, { size: 14 }),
935
+ "Disconnect"
936
+ ]
937
+ }
938
+ )
939
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
940
+ "button",
941
+ {
942
+ onClick: onConnect,
943
+ disabled: isLoading || isComingSoon,
944
+ style: {
945
+ ...styles.button,
946
+ ...styles.buttonPrimary,
947
+ ...isLoading || isComingSoon ? styles.buttonDisabled : {},
948
+ width: "100%"
949
+ },
950
+ children: [
951
+ /* @__PURE__ */ jsxRuntime.jsx(LinkIcon, { size: 14 }),
952
+ "Connect"
953
+ ]
954
+ }
955
+ )
956
+ ]
957
+ }
958
+ );
959
+ }
960
+ function IntegrationHub({
961
+ onConnect,
962
+ onDisconnect,
963
+ onError,
964
+ className,
965
+ style,
966
+ categories,
967
+ integrationIds,
968
+ hideConnected,
969
+ title = "Integrations",
970
+ description = "Connect your e-commerce platforms to sync data automatically."
971
+ }) {
972
+ const {
973
+ isReady,
974
+ isLoading,
975
+ integrations,
976
+ connections,
977
+ connect,
978
+ disconnect,
979
+ error
980
+ } = useHandledContext();
981
+ const [loadingIntegrationId, setLoadingIntegrationId] = react.useState(null);
982
+ const [selectedIntegration, setSelectedIntegration] = react.useState(null);
983
+ const [showConnectionModal, setShowConnectionModal] = react.useState(false);
984
+ const filteredIntegrations = integrations.filter((integration) => {
985
+ if (integrationIds && !integrationIds.includes(integration.id)) {
986
+ return false;
987
+ }
988
+ if (categories && !categories.includes(integration.category)) {
989
+ return false;
990
+ }
991
+ return true;
992
+ });
993
+ const getConnection = (integrationId) => {
994
+ return connections.find((c) => c.integrationId === integrationId);
995
+ };
996
+ const handleConnect = (integration) => {
997
+ setSelectedIntegration(integration);
998
+ setShowConnectionModal(true);
999
+ };
1000
+ const handleConnectionSuccess = (connection) => {
1001
+ setShowConnectionModal(false);
1002
+ setSelectedIntegration(null);
1003
+ onConnect?.(connection);
1004
+ };
1005
+ const handleConnectionError = (errorMsg) => {
1006
+ onError?.(new Error(errorMsg));
1007
+ };
1008
+ const handleModalClose = () => {
1009
+ setShowConnectionModal(false);
1010
+ setSelectedIntegration(null);
1011
+ };
1012
+ const handleDisconnect = async (connectionId) => {
1013
+ setLoadingIntegrationId(connectionId);
1014
+ try {
1015
+ await disconnect(connectionId);
1016
+ onDisconnect?.(connectionId);
1017
+ } catch (err) {
1018
+ onError?.(err instanceof Error ? err : new Error("Disconnect failed"));
1019
+ } finally {
1020
+ setLoadingIntegrationId(null);
1021
+ }
1022
+ };
1023
+ if (!isReady) {
1024
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...styles.container, ...style }, className, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.loading, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner2, {}) }) });
1025
+ }
1026
+ if (error && integrations.length === 0) {
1027
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...styles.container, ...style }, className, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.emptyState, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Failed to load integrations. Please try again." }) }) });
1028
+ }
1029
+ const connectedIntegrations = filteredIntegrations.filter((i) => getConnection(i.id));
1030
+ const availableIntegrations = filteredIntegrations.filter((i) => !getConnection(i.id));
1031
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...styles.container, ...style }, className, children: [
1032
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.header, children: [
1033
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: styles.title, children: title }),
1034
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: styles.description, children: description })
1035
+ ] }),
1036
+ !hideConnected && connectedIntegrations.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.section, children: [
1037
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { style: styles.sectionTitle, children: "Connected" }),
1038
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.grid, children: connectedIntegrations.map((integration) => {
1039
+ const connection = getConnection(integration.id);
1040
+ return /* @__PURE__ */ jsxRuntime.jsx(
1041
+ IntegrationCard,
1042
+ {
1043
+ integration,
1044
+ connection,
1045
+ onConnect: () => handleConnect(integration),
1046
+ onDisconnect: () => handleDisconnect(connection.id),
1047
+ isLoading: isLoading || loadingIntegrationId === connection.id
1048
+ },
1049
+ integration.id
1050
+ );
1051
+ }) })
1052
+ ] }),
1053
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles.section, children: [
1054
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { style: styles.sectionTitle, children: connectedIntegrations.length > 0 ? "Available" : "All Integrations" }),
1055
+ availableIntegrations.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.grid, children: availableIntegrations.map((integration) => /* @__PURE__ */ jsxRuntime.jsx(
1056
+ IntegrationCard,
1057
+ {
1058
+ integration,
1059
+ onConnect: () => handleConnect(integration),
1060
+ onDisconnect: () => {
1061
+ },
1062
+ isLoading: isLoading || loadingIntegrationId === integration.id
1063
+ },
1064
+ integration.id
1065
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles.emptyState, children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "All available integrations are connected!" }) })
1066
+ ] }),
1067
+ selectedIntegration && /* @__PURE__ */ jsxRuntime.jsx(
1068
+ ConnectionModal,
1069
+ {
1070
+ isOpen: showConnectionModal,
1071
+ onClose: handleModalClose,
1072
+ integration: selectedIntegration,
1073
+ onSuccess: handleConnectionSuccess,
1074
+ onError: handleConnectionError
1075
+ }
1076
+ )
1077
+ ] });
1078
+ }
1079
+
1080
+ // src/hooks/useHandled.ts
1081
+ function useHandled() {
1082
+ return useHandledContext();
1083
+ }
1084
+
1085
+ exports.ConnectionModal = ConnectionModal;
1086
+ exports.HandledApiClient = HandledApiClient;
1087
+ exports.HandledProvider = HandledProvider;
1088
+ exports.IntegrationHub = IntegrationHub;
1089
+ exports.useHandled = useHandled;
1090
+ //# sourceMappingURL=index.js.map
1091
+ //# sourceMappingURL=index.js.map