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