@kryptos_connect/mobile-sdk 1.0.6 → 2.0.1-dev.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 DELETED
@@ -1,4832 +0,0 @@
1
- // src/KryptosConnectButton.tsx
2
- import React39, { useEffect as useEffect5, useState as useState4 } from "react";
3
- import { StyleSheet as StyleSheet21, Text as Text19, TouchableOpacity as TouchableOpacity9, View as View21 } from "react-native";
4
-
5
- // src/contexts/KryptosContext.tsx
6
- import React from "react";
7
-
8
- // src/services/api.ts
9
- import axios from "axios";
10
-
11
- // src/config/index.ts
12
- var getBaseUrl = () => {
13
- return getGlobalBaseUrl() || "https://connect-api.kryptos.io/";
14
- };
15
- var isSvgUrl = (url) => {
16
- if (!url) return false;
17
- const urlWithoutParams = url.split("?")[0].split("#")[0];
18
- return /\.svg$/i.test(urlWithoutParams);
19
- };
20
-
21
- // src/services/api.ts
22
- var api = axios.create({
23
- headers: {
24
- "Content-Type": "application/json"
25
- }
26
- });
27
- api.interceptors.request.use((config) => {
28
- config.baseURL = getBaseUrl();
29
- return config;
30
- });
31
- var SCOPES = "openid profile offline_access email portfolios:read transactions:read integrations:read tax:read accounting:read reports:read workspace:read users:read";
32
- async function sendEmailOtp(linkToken, email, clientId) {
33
- const res = await api.post(
34
- "/v1/sendEmailOTP",
35
- {
36
- email,
37
- purpose: "login",
38
- clientId
39
- },
40
- {
41
- headers: {
42
- "X-LINK-TOKEN": linkToken
43
- }
44
- }
45
- );
46
- return res.data;
47
- }
48
- async function loginWithOtp(linkToken, email, code, clientId) {
49
- const res = await api.post(
50
- "/v1/loginUserUsingOTP",
51
- {
52
- email,
53
- code,
54
- clientId,
55
- purpose: "login"
56
- },
57
- {
58
- headers: {
59
- "X-LINK-TOKEN": linkToken
60
- }
61
- }
62
- );
63
- return res.data;
64
- }
65
- async function createAnonymousUser(linkToken, clientId) {
66
- const res = await api.post(
67
- "/link-token/login",
68
- { clientId },
69
- {
70
- headers: {
71
- "X-LINK-TOKEN": linkToken
72
- }
73
- }
74
- );
75
- return res.data?.data;
76
- }
77
- async function addUserIntegration(linkToken, integration) {
78
- const res = await api.post(
79
- "/integrations/keys",
80
- { keys: [...integration] },
81
- {
82
- headers: {
83
- "X-LINK-TOKEN": linkToken
84
- }
85
- }
86
- );
87
- return res.data?.data;
88
- }
89
- async function giveUserConsent(linkToken) {
90
- const res = await api.post(
91
- "/consent",
92
- {
93
- granted_scopes: SCOPES
94
- },
95
- {
96
- headers: {
97
- "X-LINK-TOKEN": linkToken
98
- }
99
- }
100
- );
101
- return res.data?.data;
102
- }
103
- async function testCredentials(linkToken, data) {
104
- const res = await api.post("/integrations/credentials/test", data, {
105
- headers: {
106
- "X-LINK-TOKEN": linkToken
107
- }
108
- });
109
- return res.data?.data;
110
- }
111
- async function getSupportedProviders(linkToken, id) {
112
- const res = await api.get(`/integrations/providers${id ? `?id=${id}` : ""}`, {
113
- headers: {
114
- "X-LINK-TOKEN": linkToken
115
- }
116
- });
117
- return res.data?.data;
118
- }
119
- async function getUserIntegrations(linkToken) {
120
- const res = await api.get("/integrations", {
121
- headers: {
122
- "X-LINK-TOKEN": linkToken
123
- }
124
- });
125
- return res.data?.data;
126
- }
127
- async function getUserUsedChains(linkToken, address) {
128
- const res = await api.get("/integrations/user-used-chain", {
129
- headers: {
130
- "X-LINK-TOKEN": linkToken
131
- },
132
- params: {
133
- id: address
134
- }
135
- });
136
- return res.data?.data?.chains || [];
137
- }
138
- async function getClientInfo(linkToken) {
139
- const res = await api.get("/client", {
140
- headers: {
141
- "X-LINK-TOKEN": linkToken
142
- }
143
- });
144
- return res.data?.data;
145
- }
146
- async function getUserInfo(linkToken) {
147
- const res = await api.get("/link-token/session", {
148
- headers: {
149
- "X-LINK-TOKEN": linkToken
150
- }
151
- });
152
- return res.data?.data;
153
- }
154
-
155
- // src/contexts/KryptosContext.tsx
156
- var globalBaseUrl;
157
- var getGlobalBaseUrl = () => globalBaseUrl;
158
- var KryptosContext = React.createContext(
159
- void 0
160
- );
161
- var KryptosConnectProvider = ({ children, config }) => {
162
- const [isInitialized, setIsInitialized] = React.useState(false);
163
- const [linkToken, setLinkToken] = React.useState("");
164
- const [user, setUser] = React.useState(null);
165
- const [email, setEmail] = React.useState("");
166
- const [userConsent, setUserConsent] = React.useState(
167
- null
168
- );
169
- const [isAuthorized, setIsAuthorized] = React.useState(false);
170
- const [clientInfo, setClientInfo] = React.useState(null);
171
- React.useEffect(() => {
172
- globalBaseUrl = config.baseUrl;
173
- }, [config.baseUrl]);
174
- React.useEffect(() => {
175
- const fetchClientInfo = async () => {
176
- if (linkToken) {
177
- const res = await getClientInfo(linkToken);
178
- setClientInfo(res);
179
- }
180
- };
181
- fetchClientInfo();
182
- }, [linkToken]);
183
- return /* @__PURE__ */ React.createElement(
184
- KryptosContext.Provider,
185
- {
186
- value: {
187
- ...config,
188
- isInitialized,
189
- setIsInitialized,
190
- linkToken,
191
- setLinkToken,
192
- user,
193
- setUser,
194
- email,
195
- setEmail,
196
- userConsent,
197
- setUserConsent,
198
- clientInfo,
199
- isAuthorized,
200
- setIsAuthorized
201
- }
202
- },
203
- children
204
- );
205
- };
206
- var useKryptosConnect = () => {
207
- const ctx = React.useContext(KryptosContext);
208
- if (!ctx)
209
- throw new Error(
210
- "useKryptosConnect must be used inside <KryptosConnectProvider>"
211
- );
212
- return ctx;
213
- };
214
-
215
- // src/hooks/useTheme.ts
216
- import React2 from "react";
217
-
218
- // src/theme/index.ts
219
- var lightTheme = {
220
- colors: {
221
- primary: "#00C693",
222
- primaryDark: "#00A67A",
223
- background: "#FFFFFF",
224
- surface: "#F8F9FA",
225
- surfaceSecondary: "#F1F3F5",
226
- text: "#1A1A1A",
227
- textSecondary: "#6B7280",
228
- textTertiary: "#9CA3AF",
229
- border: "#E5E7EB",
230
- borderLight: "#F3F4F6",
231
- error: "#EF4444",
232
- errorLight: "#FEE2E2",
233
- success: "#10B981",
234
- successLight: "#D1FAE5",
235
- warning: "#F59E0B",
236
- warningLight: "#FEF3C7",
237
- warningText: "#92400E",
238
- overlay: "rgba(0, 0, 0, 0.5)",
239
- white: "#FFFFFF",
240
- black: "#000000"
241
- },
242
- spacing: {
243
- xs: 4,
244
- sm: 8,
245
- md: 12,
246
- lg: 16,
247
- xl: 20,
248
- xxl: 24,
249
- xxxl: 32
250
- },
251
- borderRadius: {
252
- xs: 4,
253
- sm: 8,
254
- md: 12,
255
- lg: 16,
256
- xl: 20,
257
- full: 9999
258
- },
259
- fontSize: {
260
- xs: 10,
261
- sm: 12,
262
- md: 14,
263
- lg: 16,
264
- xl: 18,
265
- xxl: 20,
266
- xxxl: 24,
267
- display: 32
268
- },
269
- fontWeight: {
270
- regular: "400",
271
- medium: "500",
272
- semibold: "600",
273
- bold: "700"
274
- },
275
- shadow: {
276
- sm: {
277
- shadowColor: "#000",
278
- shadowOffset: { width: 0, height: 1 },
279
- shadowOpacity: 0.05,
280
- shadowRadius: 2,
281
- elevation: 1
282
- },
283
- md: {
284
- shadowColor: "#000",
285
- shadowOffset: { width: 0, height: 2 },
286
- shadowOpacity: 0.1,
287
- shadowRadius: 4,
288
- elevation: 3
289
- },
290
- lg: {
291
- shadowColor: "#000",
292
- shadowOffset: { width: 0, height: 4 },
293
- shadowOpacity: 0.15,
294
- shadowRadius: 8,
295
- elevation: 5
296
- }
297
- }
298
- };
299
- var darkTheme = {
300
- colors: {
301
- primary: "#00C693",
302
- primaryDark: "#00A67A",
303
- background: "#0F0F0F",
304
- surface: "#1A1A1A",
305
- surfaceSecondary: "#252525",
306
- text: "#FFFFFF",
307
- textSecondary: "#A1A1A1",
308
- textTertiary: "#6B6B6B",
309
- border: "#2D2D2D",
310
- borderLight: "#1F1F1F",
311
- error: "#EF4444",
312
- errorLight: "#7F1D1D",
313
- success: "#10B981",
314
- successLight: "#065F46",
315
- warning: "#F59E0B",
316
- warningLight: "#78350F",
317
- warningText: "#FEF3C7",
318
- overlay: "rgba(0, 0, 0, 0.7)",
319
- white: "#FFFFFF",
320
- black: "#000000"
321
- },
322
- spacing: lightTheme.spacing,
323
- borderRadius: lightTheme.borderRadius,
324
- fontSize: lightTheme.fontSize,
325
- fontWeight: lightTheme.fontWeight,
326
- shadow: {
327
- sm: {
328
- shadowColor: "#000",
329
- shadowOffset: { width: 0, height: 1 },
330
- shadowOpacity: 0.2,
331
- shadowRadius: 2,
332
- elevation: 1
333
- },
334
- md: {
335
- shadowColor: "#000",
336
- shadowOffset: { width: 0, height: 2 },
337
- shadowOpacity: 0.3,
338
- shadowRadius: 4,
339
- elevation: 3
340
- },
341
- lg: {
342
- shadowColor: "#000",
343
- shadowOffset: { width: 0, height: 4 },
344
- shadowOpacity: 0.4,
345
- shadowRadius: 8,
346
- elevation: 5
347
- }
348
- }
349
- };
350
- var getTheme = (isDark) => {
351
- return isDark ? darkTheme : lightTheme;
352
- };
353
-
354
- // src/hooks/useTheme.ts
355
- var useTheme = () => {
356
- const { theme } = useKryptosConnect();
357
- const currentTheme = React2.useMemo(() => {
358
- return getTheme(theme === "dark");
359
- }, [theme]);
360
- return currentTheme;
361
- };
362
-
363
- // src/components/Button.tsx
364
- import React3 from "react";
365
- import {
366
- TouchableOpacity,
367
- Text,
368
- StyleSheet,
369
- ActivityIndicator,
370
- View
371
- } from "react-native";
372
- var Button = ({
373
- variant = "primary",
374
- size = "md",
375
- children,
376
- onPress,
377
- disabled = false,
378
- loading = false,
379
- style,
380
- textStyle
381
- }) => {
382
- const theme = useTheme();
383
- const getVariantStyles = () => {
384
- switch (variant) {
385
- case "primary":
386
- return {
387
- backgroundColor: disabled ? theme.colors.textTertiary : theme.colors.primary,
388
- borderWidth: 0
389
- };
390
- case "secondary":
391
- return {
392
- backgroundColor: disabled ? theme.colors.surfaceSecondary : theme.colors.surface,
393
- borderWidth: 1,
394
- borderColor: theme.colors.border
395
- };
396
- case "outline":
397
- return {
398
- backgroundColor: "transparent",
399
- borderWidth: 1,
400
- borderColor: disabled ? theme.colors.textTertiary : theme.colors.primary
401
- };
402
- case "ghost":
403
- return {
404
- backgroundColor: "transparent",
405
- borderWidth: 0
406
- };
407
- case "success":
408
- return {
409
- backgroundColor: disabled ? theme.colors.textTertiary : theme.colors.success,
410
- borderWidth: 0
411
- };
412
- case "error":
413
- return {
414
- backgroundColor: disabled ? theme.colors.textTertiary : theme.colors.error,
415
- borderWidth: 0
416
- };
417
- default:
418
- return {};
419
- }
420
- };
421
- const getTextColor = () => {
422
- if (disabled) {
423
- return variant === "outline" || variant === "ghost" ? theme.colors.textTertiary : theme.colors.white;
424
- }
425
- switch (variant) {
426
- case "primary":
427
- case "success":
428
- case "error":
429
- return theme.colors.white;
430
- case "secondary":
431
- return theme.colors.text;
432
- case "outline":
433
- return theme.colors.primary;
434
- case "ghost":
435
- return theme.colors.text;
436
- default:
437
- return theme.colors.white;
438
- }
439
- };
440
- const getSizeStyles = () => {
441
- switch (size) {
442
- case "sm":
443
- return {
444
- button: {
445
- paddingVertical: theme.spacing.sm,
446
- paddingHorizontal: theme.spacing.md,
447
- borderRadius: theme.borderRadius.sm
448
- },
449
- text: {
450
- fontSize: theme.fontSize.sm
451
- }
452
- };
453
- case "md":
454
- return {
455
- button: {
456
- paddingVertical: theme.spacing.md,
457
- paddingHorizontal: theme.spacing.lg,
458
- borderRadius: theme.borderRadius.md
459
- },
460
- text: {
461
- fontSize: theme.fontSize.md
462
- }
463
- };
464
- case "lg":
465
- return {
466
- button: {
467
- paddingVertical: theme.spacing.lg,
468
- paddingHorizontal: theme.spacing.xl,
469
- borderRadius: theme.borderRadius.md
470
- },
471
- text: {
472
- fontSize: theme.fontSize.lg
473
- }
474
- };
475
- default:
476
- return {
477
- button: {},
478
- text: {}
479
- };
480
- }
481
- };
482
- const sizeStyles = getSizeStyles();
483
- return /* @__PURE__ */ React3.createElement(
484
- TouchableOpacity,
485
- {
486
- onPress,
487
- disabled: disabled || loading,
488
- activeOpacity: 0.7,
489
- style: [
490
- styles.button,
491
- getVariantStyles(),
492
- sizeStyles.button,
493
- disabled && styles.disabled,
494
- style
495
- ]
496
- },
497
- loading ? /* @__PURE__ */ React3.createElement(ActivityIndicator, { size: "small", color: getTextColor() }) : typeof children === "string" ? /* @__PURE__ */ React3.createElement(
498
- Text,
499
- {
500
- style: [
501
- styles.text,
502
- { color: getTextColor() },
503
- sizeStyles.text,
504
- textStyle
505
- ]
506
- },
507
- children
508
- ) : /* @__PURE__ */ React3.createElement(View, { style: styles.contentContainer }, children)
509
- );
510
- };
511
- var styles = StyleSheet.create({
512
- button: {
513
- flexDirection: "row",
514
- alignItems: "center",
515
- justifyContent: "center",
516
- minHeight: 44
517
- },
518
- text: {
519
- fontWeight: "600",
520
- textAlign: "center"
521
- },
522
- disabled: {
523
- opacity: 0.6
524
- },
525
- contentContainer: {
526
- flexDirection: "row",
527
- alignItems: "center",
528
- justifyContent: "center"
529
- }
530
- });
531
-
532
- // src/components/Input.tsx
533
- import React4 from "react";
534
- import {
535
- View as View2,
536
- TextInput,
537
- Text as Text2,
538
- StyleSheet as StyleSheet2
539
- } from "react-native";
540
- var Input = ({
541
- label,
542
- error,
543
- helperText,
544
- status = "default",
545
- containerStyle,
546
- inputStyle,
547
- labelStyle,
548
- ...props
549
- }) => {
550
- const theme = useTheme();
551
- const inputStatus = error ? "error" : status;
552
- const getBorderColor = () => {
553
- switch (inputStatus) {
554
- case "error":
555
- return theme.colors.error;
556
- case "success":
557
- return theme.colors.success;
558
- default:
559
- return theme.colors.border;
560
- }
561
- };
562
- return /* @__PURE__ */ React4.createElement(View2, { style: [styles2.wrapper, containerStyle] }, label && /* @__PURE__ */ React4.createElement(
563
- Text2,
564
- {
565
- style: [
566
- styles2.label,
567
- { color: theme.colors.text, fontSize: theme.fontSize.sm },
568
- labelStyle
569
- ]
570
- },
571
- label
572
- ), /* @__PURE__ */ React4.createElement(
573
- TextInput,
574
- {
575
- placeholderTextColor: theme.colors.textTertiary,
576
- style: [
577
- styles2.input,
578
- {
579
- backgroundColor: theme.colors.surface,
580
- borderColor: getBorderColor(),
581
- color: theme.colors.text,
582
- fontSize: theme.fontSize.md,
583
- borderRadius: theme.borderRadius.md,
584
- paddingHorizontal: theme.spacing.lg,
585
- paddingVertical: theme.spacing.md
586
- },
587
- inputStyle
588
- ],
589
- ...props
590
- }
591
- ), error && /* @__PURE__ */ React4.createElement(
592
- Text2,
593
- {
594
- style: [
595
- styles2.error,
596
- { color: theme.colors.error, fontSize: theme.fontSize.sm }
597
- ]
598
- },
599
- error
600
- ), helperText && !error && /* @__PURE__ */ React4.createElement(
601
- Text2,
602
- {
603
- style: [
604
- styles2.helper,
605
- {
606
- color: theme.colors.textSecondary,
607
- fontSize: theme.fontSize.sm
608
- }
609
- ]
610
- },
611
- helperText
612
- ));
613
- };
614
- var styles2 = StyleSheet2.create({
615
- wrapper: {
616
- marginBottom: 16
617
- // theme.spacing.lg - consistent form spacing
618
- },
619
- label: {
620
- fontWeight: "500",
621
- marginBottom: 8
622
- // theme.spacing.sm
623
- },
624
- input: {
625
- borderWidth: 1,
626
- minHeight: 48
627
- },
628
- error: {
629
- marginTop: 4
630
- // theme.spacing.xs
631
- },
632
- helper: {
633
- marginTop: 4
634
- // theme.spacing.xs
635
- }
636
- });
637
-
638
- // src/components/Alert.tsx
639
- import React5 from "react";
640
- import { StyleSheet as StyleSheet3, Text as Text3, View as View3 } from "react-native";
641
- var Alert = ({
642
- variant = "default",
643
- children,
644
- style
645
- }) => {
646
- const theme = useTheme();
647
- const getVariantStyles = () => {
648
- switch (variant) {
649
- case "destructive":
650
- return {
651
- backgroundColor: theme.colors.errorLight,
652
- borderColor: theme.colors.error
653
- };
654
- default:
655
- return {
656
- backgroundColor: theme.colors.surface,
657
- borderColor: theme.colors.border
658
- };
659
- }
660
- };
661
- return /* @__PURE__ */ React5.createElement(
662
- View3,
663
- {
664
- style: [
665
- styles3.alert,
666
- {
667
- borderRadius: theme.borderRadius.md,
668
- padding: theme.spacing.md
669
- },
670
- getVariantStyles(),
671
- style
672
- ]
673
- },
674
- children
675
- );
676
- };
677
- var AlertTitle = ({ children, style }) => {
678
- const theme = useTheme();
679
- return /* @__PURE__ */ React5.createElement(
680
- Text3,
681
- {
682
- style: [
683
- styles3.title,
684
- {
685
- color: theme.colors.text,
686
- fontSize: theme.fontSize.md,
687
- textAlign: "center"
688
- },
689
- style
690
- ]
691
- },
692
- children
693
- );
694
- };
695
- var AlertDescription = ({
696
- children,
697
- style
698
- }) => {
699
- const theme = useTheme();
700
- return /* @__PURE__ */ React5.createElement(
701
- Text3,
702
- {
703
- style: [
704
- styles3.description,
705
- {
706
- color: theme.colors.text,
707
- fontSize: theme.fontSize.md
708
- },
709
- style
710
- ]
711
- },
712
- children
713
- );
714
- };
715
- var styles3 = StyleSheet3.create({
716
- alert: {
717
- borderWidth: 1,
718
- marginVertical: 12
719
- // theme.spacing.md - consistent alert spacing
720
- },
721
- title: {
722
- fontWeight: "600",
723
- marginBottom: 4
724
- // theme.spacing.xs
725
- },
726
- description: {
727
- lineHeight: 18,
728
- textAlign: "center"
729
- }
730
- });
731
-
732
- // src/components/Modal.tsx
733
- import React7 from "react";
734
- import {
735
- Dimensions,
736
- KeyboardAvoidingView,
737
- Platform,
738
- Modal as RNModal,
739
- ScrollView,
740
- StyleSheet as StyleSheet4,
741
- Text as Text4,
742
- TouchableOpacity as TouchableOpacity2,
743
- View as View4
744
- } from "react-native";
745
-
746
- // src/assets/CloseIcon.tsx
747
- import React6 from "react";
748
- import Svg, { Path } from "react-native-svg";
749
- var CloseIcon = ({
750
- size = 20,
751
- color = "#000"
752
- }) => {
753
- return /* @__PURE__ */ React6.createElement(Svg, { width: size, height: size, viewBox: "0 0 20 20", fill: "none" }, /* @__PURE__ */ React6.createElement(
754
- Path,
755
- {
756
- d: "M15 5L5 15M5 5L15 15",
757
- stroke: color,
758
- strokeWidth: 2,
759
- strokeLinecap: "round",
760
- strokeLinejoin: "round"
761
- }
762
- ));
763
- };
764
-
765
- // src/components/Modal.tsx
766
- var { height: SCREEN_HEIGHT } = Dimensions.get("window");
767
- var Modal = ({
768
- isOpen,
769
- onClose,
770
- children,
771
- size = "md",
772
- closeOnOverlayClick = true,
773
- disableClose = true,
774
- style
775
- }) => {
776
- const theme = useTheme();
777
- const getSizeStyles = () => {
778
- switch (size) {
779
- case "xs":
780
- return { maxHeight: SCREEN_HEIGHT * 0.4 };
781
- case "sm":
782
- return { maxHeight: SCREEN_HEIGHT * 0.45 };
783
- case "md":
784
- return { maxHeight: SCREEN_HEIGHT * 0.55 };
785
- case "lg":
786
- return { maxHeight: SCREEN_HEIGHT * 0.65 };
787
- case "xl":
788
- return { maxHeight: SCREEN_HEIGHT * 0.75 };
789
- case "full":
790
- return { maxHeight: SCREEN_HEIGHT * 0.85 };
791
- default:
792
- return { maxHeight: SCREEN_HEIGHT * 0.6 };
793
- }
794
- };
795
- const handleOverlayPress = () => {
796
- if (!disableClose && closeOnOverlayClick) onClose();
797
- };
798
- return /* @__PURE__ */ React7.createElement(
799
- KeyboardAvoidingView,
800
- {
801
- behavior: Platform.OS === "ios" ? "padding" : "height",
802
- style: styles4.keyboardView
803
- },
804
- /* @__PURE__ */ React7.createElement(
805
- RNModal,
806
- {
807
- visible: isOpen,
808
- transparent: true,
809
- animationType: "none",
810
- statusBarTranslucent: true,
811
- onRequestClose: disableClose ? void 0 : onClose
812
- },
813
- /* @__PURE__ */ React7.createElement(
814
- TouchableOpacity2,
815
- {
816
- activeOpacity: 1,
817
- style: [styles4.overlay, { backgroundColor: theme.colors.overlay }],
818
- onPress: handleOverlayPress
819
- },
820
- /* @__PURE__ */ React7.createElement(
821
- View4,
822
- {
823
- style: [
824
- styles4.container,
825
- {
826
- backgroundColor: theme.colors.background,
827
- borderTopLeftRadius: theme.borderRadius.xl,
828
- borderTopRightRadius: theme.borderRadius.xl,
829
- ...theme.shadow.lg,
830
- paddingBottom: theme.spacing.xl
831
- // 20
832
- },
833
- getSizeStyles(),
834
- style
835
- ],
836
- onStartShouldSetResponder: () => true
837
- },
838
- children
839
- )
840
- )
841
- )
842
- );
843
- };
844
- var ModalHeader = ({
845
- children,
846
- onClose,
847
- showCloseButton = true,
848
- style
849
- }) => {
850
- const theme = useTheme();
851
- return /* @__PURE__ */ React7.createElement(
852
- View4,
853
- {
854
- style: [
855
- styles4.header,
856
- {
857
- borderBottomColor: theme.colors.border,
858
- paddingHorizontal: theme.spacing.lg,
859
- paddingVertical: theme.spacing.md
860
- },
861
- style
862
- ]
863
- },
864
- /* @__PURE__ */ React7.createElement(View4, { style: styles4.headerContent }, typeof children === "string" ? /* @__PURE__ */ React7.createElement(
865
- Text4,
866
- {
867
- style: [
868
- styles4.title,
869
- { color: theme.colors.text, fontSize: theme.fontSize.lg }
870
- ]
871
- },
872
- children
873
- ) : children),
874
- showCloseButton && onClose && /* @__PURE__ */ React7.createElement(
875
- TouchableOpacity2,
876
- {
877
- onPress: onClose,
878
- hitSlop: { top: 10, bottom: 10, left: 10, right: 10 },
879
- style: [
880
- styles4.closeButton,
881
- { backgroundColor: theme.colors.surface }
882
- ]
883
- },
884
- /* @__PURE__ */ React7.createElement(CloseIcon, { color: theme.colors.text, size: 20 })
885
- )
886
- );
887
- };
888
- var ModalBody = ({
889
- children,
890
- style,
891
- scrollable = true
892
- }) => {
893
- const theme = useTheme();
894
- if (scrollable) {
895
- return /* @__PURE__ */ React7.createElement(
896
- ScrollView,
897
- {
898
- style: styles4.bodyScroll,
899
- contentContainerStyle: [
900
- styles4.bodyContent,
901
- { padding: theme.spacing.lg },
902
- style
903
- ],
904
- showsVerticalScrollIndicator: false,
905
- keyboardShouldPersistTaps: "handled"
906
- },
907
- children
908
- );
909
- }
910
- return /* @__PURE__ */ React7.createElement(View4, { style: [styles4.body, { padding: theme.spacing.lg }, style] }, children);
911
- };
912
- var ModalFooter = ({
913
- children,
914
- style
915
- }) => {
916
- const theme = useTheme();
917
- return /* @__PURE__ */ React7.createElement(
918
- View4,
919
- {
920
- style: [
921
- styles4.footer,
922
- {
923
- borderTopColor: theme.colors.border,
924
- paddingHorizontal: theme.spacing.lg,
925
- paddingVertical: theme.spacing.md
926
- },
927
- style
928
- ]
929
- },
930
- children
931
- );
932
- };
933
- var styles4 = StyleSheet4.create({
934
- keyboardView: {
935
- flex: 1
936
- },
937
- overlay: {
938
- flex: 1,
939
- justifyContent: "flex-end",
940
- alignItems: "center"
941
- // 🔥 ensures modal respects height
942
- },
943
- container: {
944
- width: "100%",
945
- overflow: "hidden",
946
- alignSelf: "center",
947
- flexDirection: "column",
948
- maxWidth: "100%",
949
- minHeight: 0,
950
- flexShrink: 1,
951
- flexGrow: 1
952
- },
953
- header: {
954
- flexDirection: "row",
955
- alignItems: "center",
956
- justifyContent: "space-between",
957
- borderBottomWidth: 1
958
- },
959
- headerContent: {
960
- flex: 1,
961
- flexDirection: "row",
962
- alignItems: "center"
963
- },
964
- title: {
965
- fontWeight: "600"
966
- },
967
- closeButton: {
968
- width: 32,
969
- height: 32,
970
- borderRadius: 16,
971
- alignItems: "center",
972
- justifyContent: "center",
973
- marginLeft: 12
974
- },
975
- bodyScroll: {
976
- flexShrink: 1
977
- },
978
- bodyContent: {
979
- flexGrow: 1,
980
- paddingBottom: 20
981
- },
982
- body: {
983
- flex: 1
984
- },
985
- footer: {
986
- borderTopWidth: 1
987
- }
988
- });
989
-
990
- // src/components/OTP.tsx
991
- import React8 from "react";
992
- import {
993
- View as View5,
994
- TextInput as TextInput2,
995
- Text as Text5,
996
- StyleSheet as StyleSheet5
997
- } from "react-native";
998
- var OTP = ({
999
- length = 6,
1000
- value = "",
1001
- onChange,
1002
- onComplete,
1003
- error,
1004
- label,
1005
- disabled = false,
1006
- containerStyle,
1007
- inputStyle,
1008
- setErrorMessage
1009
- }) => {
1010
- const theme = useTheme();
1011
- const AUTO_SUBMIT_DELAY = 500;
1012
- const [otp, setOtp] = React8.useState(
1013
- value.split("").concat(Array(length).fill("")).slice(0, length)
1014
- );
1015
- const inputRefs = React8.useRef([]);
1016
- React8.useEffect(() => {
1017
- const isComplete = otp.every((digit) => digit !== "");
1018
- let timer;
1019
- if (isComplete && onComplete) {
1020
- timer = setTimeout(() => {
1021
- onComplete(otp.join(""));
1022
- }, AUTO_SUBMIT_DELAY);
1023
- }
1024
- return () => {
1025
- if (timer) clearTimeout(timer);
1026
- };
1027
- }, [otp, onComplete]);
1028
- React8.useEffect(() => {
1029
- setTimeout(() => {
1030
- inputRefs.current[0]?.focus();
1031
- }, 100);
1032
- }, []);
1033
- const handleChange = React8.useCallback(
1034
- (index, val) => {
1035
- if (disabled) return;
1036
- setErrorMessage("");
1037
- const numericValue = val.replace(/[^0-9]/g, "");
1038
- const newValue = numericValue.slice(-1);
1039
- if (val && !numericValue) {
1040
- return;
1041
- }
1042
- const newOtp = [...otp];
1043
- newOtp[index] = newValue;
1044
- setOtp(newOtp);
1045
- const otpString = newOtp.join("");
1046
- onChange?.(otpString);
1047
- if (newValue && index < length - 1) {
1048
- inputRefs.current[index + 1]?.focus();
1049
- }
1050
- if (otpString.length === length && !otpString.includes("")) {
1051
- onComplete?.(otpString);
1052
- }
1053
- },
1054
- [otp, length, onChange, onComplete, disabled]
1055
- );
1056
- const handleKeyPress = React8.useCallback(
1057
- (index, e) => {
1058
- if (disabled) return;
1059
- if (e.nativeEvent.key === "Backspace") {
1060
- if (!otp[index] && index > 0) {
1061
- inputRefs.current[index - 1]?.focus();
1062
- } else {
1063
- const newOtp = [...otp];
1064
- newOtp[index] = "";
1065
- setOtp(newOtp);
1066
- onChange?.(newOtp.join(""));
1067
- }
1068
- }
1069
- },
1070
- [otp, onChange, disabled]
1071
- );
1072
- const getBorderColor = (index) => {
1073
- if (error) return theme.colors.error;
1074
- if (otp[index]) return theme.colors.success;
1075
- return theme.colors.border;
1076
- };
1077
- return /* @__PURE__ */ React8.createElement(View5, { style: [styles5.wrapper, containerStyle] }, label && /* @__PURE__ */ React8.createElement(
1078
- Text5,
1079
- {
1080
- style: [
1081
- styles5.label,
1082
- { color: theme.colors.text, fontSize: theme.fontSize.sm }
1083
- ]
1084
- },
1085
- label
1086
- ), /* @__PURE__ */ React8.createElement(View5, { style: styles5.container }, Array.from({ length }, (_, index) => /* @__PURE__ */ React8.createElement(
1087
- TextInput2,
1088
- {
1089
- key: index,
1090
- ref: (el) => inputRefs.current[index] = el,
1091
- style: [
1092
- styles5.input,
1093
- {
1094
- backgroundColor: theme.colors.surface,
1095
- borderColor: getBorderColor(index),
1096
- color: theme.colors.text,
1097
- fontSize: theme.fontSize.xxl,
1098
- borderRadius: theme.borderRadius.md
1099
- },
1100
- inputStyle
1101
- ],
1102
- keyboardType: "numeric",
1103
- maxLength: 1,
1104
- value: otp[index] || "",
1105
- onChangeText: (val) => handleChange(index, val),
1106
- onKeyPress: (e) => handleKeyPress(index, e),
1107
- editable: !disabled,
1108
- selectTextOnFocus: true,
1109
- caretHidden: true
1110
- }
1111
- ))), error && /* @__PURE__ */ React8.createElement(
1112
- Text5,
1113
- {
1114
- style: [
1115
- styles5.error,
1116
- { color: theme.colors.error, fontSize: theme.fontSize.sm }
1117
- ]
1118
- },
1119
- error
1120
- ));
1121
- };
1122
- var styles5 = StyleSheet5.create({
1123
- wrapper: {
1124
- marginBottom: 16
1125
- // theme.spacing.lg
1126
- },
1127
- label: {
1128
- fontWeight: "500",
1129
- marginBottom: 12,
1130
- // theme.spacing.md - consistent label spacing
1131
- textAlign: "center"
1132
- },
1133
- container: {
1134
- flexDirection: "row",
1135
- justifyContent: "center",
1136
- gap: 8
1137
- // theme.spacing.sm
1138
- },
1139
- input: {
1140
- width: 48,
1141
- height: 56,
1142
- borderWidth: 1,
1143
- textAlign: "center",
1144
- fontWeight: "600"
1145
- },
1146
- error: {
1147
- marginTop: 12,
1148
- // theme.spacing.md - consistent error spacing
1149
- textAlign: "center"
1150
- }
1151
- });
1152
-
1153
- // src/components/SkeletonItem.tsx
1154
- import React9, { useEffect, useRef } from "react";
1155
- import { Animated, View as View6, StyleSheet as StyleSheet6 } from "react-native";
1156
- var SkeletonItem = () => {
1157
- const opacity = useRef(new Animated.Value(0.3)).current;
1158
- useEffect(() => {
1159
- Animated.loop(
1160
- Animated.sequence([
1161
- Animated.timing(opacity, {
1162
- toValue: 1,
1163
- duration: 600,
1164
- useNativeDriver: true
1165
- }),
1166
- Animated.timing(opacity, {
1167
- toValue: 0.3,
1168
- duration: 600,
1169
- useNativeDriver: true
1170
- })
1171
- ])
1172
- ).start();
1173
- }, []);
1174
- return /* @__PURE__ */ React9.createElement(Animated.View, { style: [styles6.row, { opacity }] }, /* @__PURE__ */ React9.createElement(View6, { style: styles6.iconCircle }), /* @__PURE__ */ React9.createElement(View6, { style: styles6.textBlock }, /* @__PURE__ */ React9.createElement(View6, { style: styles6.lineLong }), /* @__PURE__ */ React9.createElement(View6, { style: styles6.lineShort })));
1175
- };
1176
- var styles6 = StyleSheet6.create({
1177
- row: {
1178
- flexDirection: "row",
1179
- alignItems: "center",
1180
- paddingVertical: 16
1181
- },
1182
- iconCircle: {
1183
- width: 45,
1184
- height: 45,
1185
- borderRadius: 22.5,
1186
- backgroundColor: "#E5E5E5"
1187
- },
1188
- textBlock: {
1189
- marginLeft: 12,
1190
- flex: 1
1191
- },
1192
- lineShort: {
1193
- width: "50%",
1194
- height: 14,
1195
- borderRadius: 6,
1196
- backgroundColor: "#E5E5E5"
1197
- },
1198
- lineLong: {
1199
- marginBottom: 6,
1200
- width: "100%",
1201
- height: 14,
1202
- borderRadius: 6,
1203
- backgroundColor: "#E5E5E5"
1204
- }
1205
- });
1206
- var SkeletonItem_default = SkeletonItem;
1207
-
1208
- // src/components/Mode.tsx
1209
- import React10 from "react";
1210
- import { View as View7, Text as Text6, StyleSheet as StyleSheet7 } from "react-native";
1211
- var Mode = () => {
1212
- const { clientInfo } = useKryptosConnect();
1213
- const theme = useTheme();
1214
- if (!clientInfo) return null;
1215
- if (clientInfo?.project_stage === "production") return null;
1216
- return /* @__PURE__ */ React10.createElement(View7, { style: [styles7.container, { backgroundColor: theme.colors.warning }] }, /* @__PURE__ */ React10.createElement(Text6, { style: [styles7.text, { color: theme.colors.warningText }] }, "Sandbox Mode"));
1217
- };
1218
- var styles7 = StyleSheet7.create({
1219
- container: {
1220
- paddingVertical: 4,
1221
- paddingHorizontal: 8,
1222
- borderRadius: 8,
1223
- alignItems: "center",
1224
- justifyContent: "center"
1225
- },
1226
- text: {
1227
- fontSize: 12,
1228
- fontWeight: "600"
1229
- }
1230
- });
1231
-
1232
- // src/components/Footer.tsx
1233
- import React13 from "react";
1234
- import { View as View9, StyleSheet as StyleSheet9 } from "react-native";
1235
-
1236
- // src/components/PoweredByKryptos.tsx
1237
- import React12 from "react";
1238
- import {
1239
- Linking,
1240
- StyleSheet as StyleSheet8,
1241
- Text as Text7,
1242
- TouchableOpacity as TouchableOpacity3,
1243
- View as View8
1244
- } from "react-native";
1245
-
1246
- // src/assets/LogoIcon.tsx
1247
- import React11 from "react";
1248
- import Svg2, { Path as Path2 } from "react-native-svg";
1249
- var LogoIcon = ({ size = 36 }) => {
1250
- return /* @__PURE__ */ React11.createElement(Svg2, { width: size, height: size, viewBox: "0 0 36 36", fill: "none" }, /* @__PURE__ */ React11.createElement(
1251
- Path2,
1252
- {
1253
- d: "M0 4.11429C0 1.84203 1.84203 0 4.11429 0H31.8857C34.158 0 36 1.84203 36 4.11429V31.8857C36 34.158 34.158 36 31.8857 36H4.11429C1.84203 36 0 34.158 0 31.8857V4.11429Z",
1254
- fill: "#00C693"
1255
- }
1256
- ), /* @__PURE__ */ React11.createElement(
1257
- Path2,
1258
- {
1259
- d: "M12.3916 28.2857H8.43388C8.03646 28.2857 7.71429 27.9886 7.71429 27.6221V22.92C7.71429 22.744 7.7901 22.5752 7.92505 22.4508L9.66229 20.8487C9.79724 20.7243 9.98027 20.6544 10.1711 20.6544H12.3916C12.789 20.6544 13.1112 20.9515 13.1112 21.318V27.6221C13.1112 27.9886 12.789 28.2857 12.3916 28.2857Z",
1260
- fill: "white"
1261
- }
1262
- ), /* @__PURE__ */ React11.createElement(
1263
- Path2,
1264
- {
1265
- d: "M27.5647 28.2857H22.0443C21.8535 28.2857 21.6704 28.2158 21.5355 28.0914L13.9798 21.1236C13.6988 20.8645 13.6988 20.4443 13.9798 20.1851L15.7788 18.5262C15.9137 18.4017 16.0968 18.3318 16.2876 18.3318H18.21C18.4009 18.3318 18.5839 18.4017 18.7189 18.5262L28.0735 27.1529C28.5268 27.5709 28.2058 28.2857 27.5647 28.2857Z",
1266
- fill: "white"
1267
- }
1268
- ), /* @__PURE__ */ React11.createElement(
1269
- Path2,
1270
- {
1271
- d: "M27.5647 7.71429H22.0443C21.8535 7.71429 21.6704 7.7842 21.5355 7.90865L13.9798 14.8764C13.6988 15.1355 13.6988 15.5557 13.9798 15.8149L15.7788 17.4738C15.9137 17.5983 16.0968 17.6682 16.2876 17.6682H18.21C18.4009 17.6682 18.5839 17.5983 18.7189 17.4738L28.0735 8.84711C28.5268 8.42907 28.2058 7.71429 27.5647 7.71429Z",
1272
- fill: "white"
1273
- }
1274
- ), /* @__PURE__ */ React11.createElement(
1275
- Path2,
1276
- {
1277
- d: "M12.3916 7.71429H8.43388C8.03646 7.71429 7.71429 8.01139 7.71429 8.37788V13.08C7.71429 13.256 7.7901 13.4248 7.92505 13.5492L9.66229 15.1513C9.79724 15.2757 9.98027 15.3456 10.1711 15.3456H12.3916C12.789 15.3456 13.1112 15.0485 13.1112 14.682V8.37788C13.1112 8.01139 12.789 7.71429 12.3916 7.71429Z",
1278
- fill: "white"
1279
- }
1280
- ));
1281
- };
1282
-
1283
- // src/components/PoweredByKryptos.tsx
1284
- var PoweredByKryptos = () => {
1285
- const theme = useTheme();
1286
- const handlePress = () => {
1287
- Linking.openURL("https://kryptos.io");
1288
- };
1289
- return /* @__PURE__ */ React12.createElement(View8, { style: styles8.container }, /* @__PURE__ */ React12.createElement(Text7, { style: [styles8.text, { color: theme.colors.textSecondary }] }, "Powered by", " "), /* @__PURE__ */ React12.createElement(TouchableOpacity3, { onPress: handlePress, activeOpacity: 0.7 }, /* @__PURE__ */ React12.createElement(LogoIcon, { size: 16 })));
1290
- };
1291
- var styles8 = StyleSheet8.create({
1292
- container: {
1293
- flexDirection: "row",
1294
- alignItems: "center"
1295
- },
1296
- text: {
1297
- fontSize: 12,
1298
- fontWeight: "400"
1299
- }
1300
- });
1301
-
1302
- // src/components/Footer.tsx
1303
- var Footer = () => {
1304
- const { clientInfo } = useKryptosConnect();
1305
- const isSandbox = clientInfo?.project_stage === "sandbox";
1306
- return /* @__PURE__ */ React13.createElement(
1307
- View9,
1308
- {
1309
- style: [
1310
- styles9.container,
1311
- { justifyContent: isSandbox ? "space-between" : "center" }
1312
- ]
1313
- },
1314
- /* @__PURE__ */ React13.createElement(PoweredByKryptos, null),
1315
- /* @__PURE__ */ React13.createElement(Mode, null)
1316
- );
1317
- };
1318
- var styles9 = StyleSheet9.create({
1319
- container: {
1320
- width: "100%",
1321
- paddingVertical: 8,
1322
- alignItems: "center",
1323
- flexDirection: "row"
1324
- }
1325
- });
1326
-
1327
- // src/molecules/Auth.tsx
1328
- import React20 from "react";
1329
- import { Linking as Linking2, StyleSheet as StyleSheet11, Text as Text9, View as View11 } from "react-native";
1330
-
1331
- // src/assets/LinkIcon.tsx
1332
- import React14 from "react";
1333
- import Svg3, { Path as Path3 } from "react-native-svg";
1334
- var LinkIcon = ({
1335
- size = 20,
1336
- color = "#00C693"
1337
- }) => {
1338
- return /* @__PURE__ */ React14.createElement(Svg3, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React14.createElement(
1339
- Path3,
1340
- {
1341
- d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
1342
- stroke: color,
1343
- strokeWidth: 2,
1344
- strokeLinecap: "round",
1345
- strokeLinejoin: "round"
1346
- }
1347
- ), /* @__PURE__ */ React14.createElement(
1348
- Path3,
1349
- {
1350
- d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71",
1351
- stroke: color,
1352
- strokeWidth: 2,
1353
- strokeLinecap: "round",
1354
- strokeLinejoin: "round"
1355
- }
1356
- ));
1357
- };
1358
-
1359
- // src/assets/ShieldIcon.tsx
1360
- import React15 from "react";
1361
- import Svg4, { Path as Path4 } from "react-native-svg";
1362
- var ShieldIcon = ({
1363
- size = 20,
1364
- color = "#00C693"
1365
- }) => {
1366
- return /* @__PURE__ */ React15.createElement(Svg4, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React15.createElement(
1367
- Path4,
1368
- {
1369
- d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z",
1370
- stroke: color,
1371
- strokeWidth: 2,
1372
- strokeLinecap: "round",
1373
- strokeLinejoin: "round"
1374
- }
1375
- ), /* @__PURE__ */ React15.createElement(
1376
- Path4,
1377
- {
1378
- d: "m9 12 2 2 4-4",
1379
- stroke: color,
1380
- strokeWidth: 2,
1381
- strokeLinecap: "round",
1382
- strokeLinejoin: "round"
1383
- }
1384
- ));
1385
- };
1386
-
1387
- // src/assets/eye.tsx
1388
- import React16 from "react";
1389
- import Svg5, { Path as Path5 } from "react-native-svg";
1390
- var EyeIcon = ({
1391
- size = 20,
1392
- color = "#00C693"
1393
- }) => {
1394
- return /* @__PURE__ */ React16.createElement(Svg5, { fill: color, width: size, height: size, viewBox: "0 0 0.72 0.72" }, /* @__PURE__ */ React16.createElement(Path5, { d: "M0.658 0.348C0.597 0.207 0.483 0.12 0.36 0.12s-0.237 0.087 -0.298 0.228a0.03 0.03 0 0 0 0 0.024C0.123 0.513 0.237 0.6 0.36 0.6s0.237 -0.087 0.298 -0.228a0.03 0.03 0 0 0 0 -0.024M0.36 0.54c-0.095 0 -0.185 -0.069 -0.237 -0.18C0.175 0.249 0.265 0.18 0.36 0.18s0.185 0.069 0.237 0.18c-0.052 0.111 -0.142 0.18 -0.237 0.18m0 -0.3a0.12 0.12 0 1 0 0.12 0.12 0.12 0.12 0 0 0 -0.12 -0.12m0 0.18a0.06 0.06 0 1 1 0.06 -0.06 0.06 0.06 0 0 1 -0.06 0.06" }));
1395
- };
1396
-
1397
- // src/molecules/ConnectLogo.tsx
1398
- import React19, { isValidElement } from "react";
1399
- import {
1400
- Image,
1401
- StyleSheet as StyleSheet10,
1402
- Text as Text8,
1403
- View as View10
1404
- } from "react-native";
1405
-
1406
- // src/assets/UnplugIcon.tsx
1407
- import React17 from "react";
1408
- import Svg6, { Path as Path6, Line } from "react-native-svg";
1409
- var UnplugIcon = ({
1410
- size = 24,
1411
- color = "#6B7280"
1412
- }) => {
1413
- return /* @__PURE__ */ React17.createElement(Svg6, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React17.createElement(
1414
- Path6,
1415
- {
1416
- d: "m19 5 3-3",
1417
- stroke: color,
1418
- strokeWidth: 2,
1419
- strokeLinecap: "round",
1420
- strokeLinejoin: "round"
1421
- }
1422
- ), /* @__PURE__ */ React17.createElement(
1423
- Path6,
1424
- {
1425
- d: "m2 22 3-3",
1426
- stroke: color,
1427
- strokeWidth: 2,
1428
- strokeLinecap: "round",
1429
- strokeLinejoin: "round"
1430
- }
1431
- ), /* @__PURE__ */ React17.createElement(
1432
- Path6,
1433
- {
1434
- d: "M6.3 20.3a2.4 2.4 0 0 0 3.4 0L12 18l-6-6-2.3 2.3a2.4 2.4 0 0 0 0 3.4Z",
1435
- stroke: color,
1436
- strokeWidth: 2,
1437
- strokeLinecap: "round",
1438
- strokeLinejoin: "round"
1439
- }
1440
- ), /* @__PURE__ */ React17.createElement(
1441
- Path6,
1442
- {
1443
- d: "m18 12-6-6 2.3-2.3a2.4 2.4 0 0 1 3.4 0l2.6 2.6a2.4 2.4 0 0 1 0 3.4Z",
1444
- stroke: color,
1445
- strokeWidth: 2,
1446
- strokeLinecap: "round",
1447
- strokeLinejoin: "round"
1448
- }
1449
- ), /* @__PURE__ */ React17.createElement(
1450
- Line,
1451
- {
1452
- x1: 7.5,
1453
- y1: 13.5,
1454
- x2: 10.5,
1455
- y2: 10.5,
1456
- stroke: color,
1457
- strokeWidth: 2,
1458
- strokeLinecap: "round"
1459
- }
1460
- ));
1461
- };
1462
-
1463
- // src/components/remote-svg.tsx
1464
- import React18, { useEffect as useEffect2, useState } from "react";
1465
- import { ActivityIndicator as ActivityIndicator2 } from "react-native";
1466
- import { SvgXml } from "react-native-svg";
1467
- function RemoteSvg({
1468
- uri,
1469
- width = 32,
1470
- height = 32
1471
- }) {
1472
- const [svgXml, setSvgXml] = useState(null);
1473
- useEffect2(() => {
1474
- fetch(uri).then((res) => res.text()).then((text) => setSvgXml(text)).catch((err) => console.error("SVG load error:", err));
1475
- }, [uri]);
1476
- if (!svgXml) return /* @__PURE__ */ React18.createElement(ActivityIndicator2, null);
1477
- return /* @__PURE__ */ React18.createElement(
1478
- SvgXml,
1479
- {
1480
- xml: svgXml,
1481
- width,
1482
- height,
1483
- style: { borderRadius: 8 }
1484
- }
1485
- );
1486
- }
1487
-
1488
- // src/molecules/ConnectLogo.tsx
1489
- var KryptosLogo = () => {
1490
- const theme = useTheme();
1491
- return /* @__PURE__ */ React19.createElement(
1492
- View10,
1493
- {
1494
- style: [styles10.logoContainer, { backgroundColor: theme.colors.surface }]
1495
- },
1496
- /* @__PURE__ */ React19.createElement(LogoIcon, { size: 36 })
1497
- );
1498
- };
1499
- var ConnectLogo = () => {
1500
- const { appName, appLogo } = useKryptosConnect();
1501
- const theme = useTheme();
1502
- const isValidUrl = (str) => {
1503
- try {
1504
- new URL(str);
1505
- return true;
1506
- } catch {
1507
- return false;
1508
- }
1509
- };
1510
- const renderLogo = () => {
1511
- if (isValidElement(appLogo)) {
1512
- return appLogo;
1513
- } else if (typeof appLogo === "string" && isValidUrl(appLogo)) {
1514
- return isSvgUrl(appLogo) ? /* @__PURE__ */ React19.createElement(RemoteSvg, { uri: appLogo }) : /* @__PURE__ */ React19.createElement(
1515
- Image,
1516
- {
1517
- source: { uri: appLogo },
1518
- style: styles10.appLogoImage,
1519
- resizeMode: "contain"
1520
- }
1521
- );
1522
- } else if (typeof appLogo === "number" || typeof appLogo === "object" && appLogo !== null) {
1523
- return /* @__PURE__ */ React19.createElement(
1524
- Image,
1525
- {
1526
- source: appLogo,
1527
- style: styles10.appLogoImage,
1528
- resizeMode: "contain"
1529
- }
1530
- );
1531
- } else if (appName) {
1532
- return /* @__PURE__ */ React19.createElement(Text8, { style: [styles10.appLogoText, { color: theme.colors.text }] }, appName.charAt(0).toUpperCase());
1533
- }
1534
- return /* @__PURE__ */ React19.createElement(Text8, { style: [styles10.appLogoText, { color: theme.colors.text }] }, "?");
1535
- };
1536
- return /* @__PURE__ */ React19.createElement(View10, { style: styles10.container }, /* @__PURE__ */ React19.createElement(KryptosLogo, null), /* @__PURE__ */ React19.createElement(View10, { style: styles10.iconContainer }, /* @__PURE__ */ React19.createElement(UnplugIcon, { size: 24, color: theme.colors.textSecondary })), /* @__PURE__ */ React19.createElement(
1537
- View10,
1538
- {
1539
- style: [
1540
- styles10.logoContainer,
1541
- { backgroundColor: theme.colors.surface }
1542
- ]
1543
- },
1544
- renderLogo()
1545
- ));
1546
- };
1547
- var styles10 = StyleSheet10.create({
1548
- container: {
1549
- flexDirection: "row",
1550
- alignItems: "center",
1551
- justifyContent: "center",
1552
- gap: 12
1553
- // theme.spacing.md
1554
- },
1555
- logoContainer: {
1556
- width: 56,
1557
- height: 56,
1558
- borderRadius: 12,
1559
- // theme.borderRadius.md
1560
- alignItems: "center",
1561
- justifyContent: "center",
1562
- overflow: "hidden"
1563
- },
1564
- iconContainer: {
1565
- paddingHorizontal: 8
1566
- // theme.spacing.sm
1567
- },
1568
- appLogoImage: {
1569
- width: 32,
1570
- height: 32
1571
- },
1572
- appLogoText: {
1573
- fontSize: 24,
1574
- // theme.fontSize.xxxl
1575
- fontWeight: "700"
1576
- }
1577
- });
1578
-
1579
- // src/molecules/Auth.tsx
1580
- var Auth = ({
1581
- open,
1582
- onEmailSuccess,
1583
- onGuestSuccess,
1584
- onClose
1585
- }) => {
1586
- const { appName, linkToken, clientId, setUser, setEmail } = useKryptosConnect();
1587
- const theme = useTheme();
1588
- const [isLoading, setIsLoading] = React20.useState(false);
1589
- const [errorMessage, setErrorMessage] = React20.useState("");
1590
- const [emailValue, setEmailValue] = React20.useState("");
1591
- const [emailError, setEmailError] = React20.useState("");
1592
- const [loadingType, setLoadingType] = React20.useState(null);
1593
- const validateEmail = (email) => {
1594
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1595
- if (!email) {
1596
- setEmailError("Email is required");
1597
- return false;
1598
- }
1599
- if (!emailRegex.test(email)) {
1600
- setEmailError("Invalid email address");
1601
- return false;
1602
- }
1603
- setEmailError("");
1604
- return true;
1605
- };
1606
- const handleClose = () => {
1607
- onClose();
1608
- setEmailValue("");
1609
- setEmailError("");
1610
- setErrorMessage("");
1611
- };
1612
- const handleEmailSubmit = async () => {
1613
- if (!validateEmail(emailValue)) return;
1614
- try {
1615
- setIsLoading(true);
1616
- setLoadingType("email");
1617
- setErrorMessage("");
1618
- await sendEmailOtp(linkToken, emailValue, clientId);
1619
- setEmail(emailValue);
1620
- setEmailError("");
1621
- onEmailSuccess();
1622
- } catch (error) {
1623
- const err = error;
1624
- setErrorMessage(
1625
- err?.response?.data?.message || "Failed to send email OTP"
1626
- );
1627
- } finally {
1628
- setIsLoading(false);
1629
- setLoadingType(null);
1630
- }
1631
- };
1632
- const handleContinueAsGuest = async () => {
1633
- try {
1634
- setIsLoading(true);
1635
- setLoadingType("guest");
1636
- setErrorMessage("");
1637
- const res = await createAnonymousUser(linkToken, clientId);
1638
- setUser(res);
1639
- setEmailError("");
1640
- onGuestSuccess();
1641
- } catch (error) {
1642
- const err = error;
1643
- console.error(error);
1644
- setErrorMessage(
1645
- err?.response?.data?.error || err?.response?.data?.message || "Failed to continue as guest"
1646
- );
1647
- } finally {
1648
- setIsLoading(false);
1649
- setLoadingType(null);
1650
- }
1651
- };
1652
- const infoSections = [
1653
- {
1654
- icon: /* @__PURE__ */ React20.createElement(LinkIcon, { size: 20, color: theme.colors.primary }),
1655
- title: "Simple and secure",
1656
- text: "Link your accounts in just a few clicks"
1657
- },
1658
- {
1659
- icon: /* @__PURE__ */ React20.createElement(ShieldIcon, { size: 20, color: theme.colors.primary }),
1660
- title: "Control what you share",
1661
- text: "We never share your data without your permission"
1662
- },
1663
- {
1664
- icon: /* @__PURE__ */ React20.createElement(EyeIcon, { size: 20, color: theme.colors.primary }),
1665
- title: "View Only Access",
1666
- text: "Kryptos retrieves view-only data and cannot perform any transactions on your behalf."
1667
- }
1668
- ];
1669
- return /* @__PURE__ */ React20.createElement(Modal, { isOpen: open, onClose: handleClose, size: "full" }, /* @__PURE__ */ React20.createElement(ModalHeader, { onClose: handleClose }, ""), /* @__PURE__ */ React20.createElement(ModalBody, null, /* @__PURE__ */ React20.createElement(View11, { style: styles11.container }, /* @__PURE__ */ React20.createElement(View11, { style: styles11.header }, /* @__PURE__ */ React20.createElement(Text9, { style: [styles11.title, { color: theme.colors.text }] }, "Link your accounts to", " ", /* @__PURE__ */ React20.createElement(Text9, { style: { fontWeight: "700" } }, appName), " using Kryptos"), /* @__PURE__ */ React20.createElement(ConnectLogo, null), infoSections.map((section, index) => /* @__PURE__ */ React20.createElement(View11, { key: `info-${index}`, style: styles11.infoSection }, /* @__PURE__ */ React20.createElement(View11, { style: styles11.infoIcon }, section.icon), /* @__PURE__ */ React20.createElement(View11, { style: styles11.infoContent }, /* @__PURE__ */ React20.createElement(
1670
- Text9,
1671
- {
1672
- style: [styles11.infoTitle, { color: theme.colors.text }]
1673
- },
1674
- section.title
1675
- ), /* @__PURE__ */ React20.createElement(
1676
- Text9,
1677
- {
1678
- style: [
1679
- styles11.infoDescription,
1680
- { color: theme.colors.textSecondary }
1681
- ]
1682
- },
1683
- section.text
1684
- )))), errorMessage ? /* @__PURE__ */ React20.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React20.createElement(AlertDescription, null, errorMessage)) : null), /* @__PURE__ */ React20.createElement(View11, { style: styles11.footer }, /* @__PURE__ */ React20.createElement(
1685
- Button,
1686
- {
1687
- variant: "outline",
1688
- size: "lg",
1689
- onPress: handleContinueAsGuest,
1690
- loading: loadingType === "guest",
1691
- disabled: isLoading,
1692
- style: styles11.button
1693
- },
1694
- "Continue"
1695
- ), /* @__PURE__ */ React20.createElement(
1696
- Text9,
1697
- {
1698
- style: [styles11.footerText, { color: theme.colors.textSecondary }]
1699
- },
1700
- "By continuing, you agree to Kryptos",
1701
- " ",
1702
- /* @__PURE__ */ React20.createElement(
1703
- Text9,
1704
- {
1705
- style: {
1706
- color: theme.colors.primary,
1707
- textDecorationLine: "underline"
1708
- },
1709
- onPress: () => Linking2.openURL("https://kryptos.io/privacy-policy")
1710
- },
1711
- "Privacy Policy"
1712
- ),
1713
- " ",
1714
- "and",
1715
- " ",
1716
- /* @__PURE__ */ React20.createElement(
1717
- Text9,
1718
- {
1719
- style: {
1720
- color: theme.colors.primary,
1721
- textDecorationLine: "underline"
1722
- },
1723
- onPress: () => Linking2.openURL("https://kryptos.io/terms-of-services")
1724
- },
1725
- "Terms of Service"
1726
- )
1727
- )))), /* @__PURE__ */ React20.createElement(ModalFooter, { style: { paddingVertical: 0 } }, /* @__PURE__ */ React20.createElement(Footer, null)));
1728
- };
1729
- var styles11 = StyleSheet11.create({
1730
- container: {
1731
- flex: 1,
1732
- flexDirection: "column",
1733
- justifyContent: "space-between"
1734
- },
1735
- header: {
1736
- flex: 1,
1737
- gap: 8
1738
- },
1739
- footer: {
1740
- flex: 1,
1741
- justifyContent: "flex-end",
1742
- gap: 8
1743
- },
1744
- title: {
1745
- fontSize: 18,
1746
- // theme.fontSize.xl
1747
- fontWeight: "500",
1748
- textAlign: "center"
1749
- },
1750
- infoSection: {
1751
- flexDirection: "row",
1752
- alignItems: "flex-start",
1753
- padding: 8,
1754
- gap: 12
1755
- },
1756
- infoIcon: {
1757
- width: 32,
1758
- // theme.spacing.xxxl
1759
- height: 32,
1760
- // theme.spacing.xxxl
1761
- borderRadius: 16,
1762
- // theme.borderRadius.lg
1763
- alignItems: "center",
1764
- justifyContent: "center"
1765
- },
1766
- infoContent: {
1767
- flex: 1,
1768
- gap: 4
1769
- },
1770
- infoTitle: {
1771
- fontSize: 14,
1772
- // theme.fontSize.md
1773
- fontWeight: "600"
1774
- },
1775
- infoDescription: {
1776
- fontSize: 13,
1777
- // theme.fontSize.sm + 1
1778
- lineHeight: 18
1779
- },
1780
- button: {
1781
- width: "100%"
1782
- },
1783
- footerText: {
1784
- fontSize: 12,
1785
- // theme.fontSize.sm
1786
- textAlign: "center",
1787
- padding: 8,
1788
- maxWidth: "80%",
1789
- alignSelf: "center"
1790
- }
1791
- });
1792
-
1793
- // src/molecules/EndModal.tsx
1794
- import React22, { useEffect as useEffect3 } from "react";
1795
- import { StyleSheet as StyleSheet12, Text as Text10, View as View12 } from "react-native";
1796
-
1797
- // src/assets/SuccessIcon.tsx
1798
- import React21 from "react";
1799
- import Svg7, { Circle, Path as Path7 } from "react-native-svg";
1800
- var SuccessIcon = ({ size = 64 }) => {
1801
- return /* @__PURE__ */ React21.createElement(Svg7, { width: size, height: size, viewBox: "0 0 64 64", fill: "none" }, /* @__PURE__ */ React21.createElement(
1802
- Circle,
1803
- {
1804
- cx: 32,
1805
- cy: 32,
1806
- r: 30,
1807
- fill: "#00C693",
1808
- opacity: 0.1
1809
- }
1810
- ), /* @__PURE__ */ React21.createElement(
1811
- Circle,
1812
- {
1813
- cx: 32,
1814
- cy: 32,
1815
- r: 24,
1816
- fill: "#00C693"
1817
- }
1818
- ), /* @__PURE__ */ React21.createElement(
1819
- Path7,
1820
- {
1821
- d: "M24 32l6 6 12-12",
1822
- stroke: "white",
1823
- strokeWidth: 3,
1824
- strokeLinecap: "round",
1825
- strokeLinejoin: "round"
1826
- }
1827
- ));
1828
- };
1829
-
1830
- // src/molecules/EndModal.tsx
1831
- var EndModal = ({ open, onClose }) => {
1832
- const theme = useTheme();
1833
- useEffect3(() => {
1834
- if (!open) return;
1835
- const timer = setTimeout(() => {
1836
- onClose();
1837
- }, 5e3);
1838
- return () => clearTimeout(timer);
1839
- }, []);
1840
- return /* @__PURE__ */ React22.createElement(Modal, { isOpen: open, onClose, size: "xl" }, /* @__PURE__ */ React22.createElement(ModalHeader, { onClose }, ""), /* @__PURE__ */ React22.createElement(ModalBody, null, /* @__PURE__ */ React22.createElement(View12, { style: styles12.container }, /* @__PURE__ */ React22.createElement(
1841
- View12,
1842
- {
1843
- style: [
1844
- styles12.iconContainer,
1845
- { backgroundColor: theme.colors.successLight }
1846
- ]
1847
- },
1848
- /* @__PURE__ */ React22.createElement(SuccessIcon, { size: 80 })
1849
- ), /* @__PURE__ */ React22.createElement(Text10, { style: [styles12.message, { color: theme.colors.text }] }, "All set! We're redirecting you back to the app. If it takes longer than expected, tap the button below to continue."), /* @__PURE__ */ React22.createElement(Alert, null, /* @__PURE__ */ React22.createElement(AlertTitle, null, "Sync in Progress"), /* @__PURE__ */ React22.createElement(AlertDescription, null, "The syncing process might take a moment. Please wait, and your data will be updated soon.")))), /* @__PURE__ */ React22.createElement(ModalFooter, { style: { paddingVertical: 8 } }, /* @__PURE__ */ React22.createElement(
1850
- Button,
1851
- {
1852
- variant: "primary",
1853
- size: "lg",
1854
- onPress: onClose,
1855
- style: styles12.button
1856
- },
1857
- "Continue to App"
1858
- ), /* @__PURE__ */ React22.createElement(Footer, null)));
1859
- };
1860
- var styles12 = StyleSheet12.create({
1861
- container: {
1862
- alignItems: "center",
1863
- paddingVertical: 20
1864
- },
1865
- iconContainer: {
1866
- width: 80,
1867
- height: 80,
1868
- borderRadius: 40,
1869
- alignItems: "center",
1870
- justifyContent: "center",
1871
- marginBottom: 20
1872
- },
1873
- message: {
1874
- fontSize: 14,
1875
- textAlign: "center",
1876
- lineHeight: 20,
1877
- marginBottom: 24,
1878
- paddingHorizontal: 20
1879
- },
1880
- button: {
1881
- width: "100%"
1882
- }
1883
- });
1884
-
1885
- // src/molecules/Init.tsx
1886
- import React23 from "react";
1887
- import { ActivityIndicator as ActivityIndicator3, StyleSheet as StyleSheet13, Text as Text11, View as View13 } from "react-native";
1888
- var Init = ({
1889
- open,
1890
- onSuccess,
1891
- onClose,
1892
- generateLinkToken
1893
- }) => {
1894
- const {
1895
- setIsInitialized,
1896
- isInitialized,
1897
- setLinkToken,
1898
- setIsAuthorized,
1899
- setUser
1900
- } = useKryptosConnect();
1901
- const theme = useTheme();
1902
- const [isFetching, setIsFetching] = React23.useState(false);
1903
- const [error, setError] = React23.useState(null);
1904
- const fetchLinkToken = React23.useCallback(async () => {
1905
- if (!open) return;
1906
- setIsFetching(true);
1907
- setError(null);
1908
- try {
1909
- const linkToken = await generateLinkToken();
1910
- if (!linkToken || !linkToken?.link_token || linkToken.link_token === "" || linkToken.link_token === null || linkToken.link_token === void 0) {
1911
- setIsInitialized(false);
1912
- setError("Failed to fetch link token. Please try again.");
1913
- return;
1914
- }
1915
- setLinkToken(linkToken.link_token);
1916
- setIsInitialized(true);
1917
- setIsAuthorized(linkToken.isAuthorized || false);
1918
- if (linkToken.isAuthorized) {
1919
- const userInfo = await getUserInfo(linkToken.link_token);
1920
- setUser(userInfo);
1921
- }
1922
- onSuccess(linkToken.isAuthorized ? { isAuthorized: true } : null);
1923
- } catch (err) {
1924
- console.error("Failed to fetch link token:", err);
1925
- setIsInitialized(false);
1926
- setError("Failed to fetch link token. Please try again.");
1927
- } finally {
1928
- setIsFetching(false);
1929
- }
1930
- }, []);
1931
- React23.useEffect(() => {
1932
- fetchLinkToken();
1933
- }, [fetchLinkToken]);
1934
- return /* @__PURE__ */ React23.createElement(Modal, { isOpen: open, onClose, size: "md" }, /* @__PURE__ */ React23.createElement(ModalHeader, { onClose }, "Kryptos Connect"), /* @__PURE__ */ React23.createElement(ModalBody, null, /* @__PURE__ */ React23.createElement(View13, { style: styles13.container }, isFetching && /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement(
1935
- ActivityIndicator3,
1936
- {
1937
- size: "large",
1938
- color: theme.colors.primary,
1939
- style: styles13.spinner
1940
- }
1941
- ), /* @__PURE__ */ React23.createElement(Text11, { style: [styles13.message, { color: theme.colors.text }] }, isInitialized ? "Fetching link token..." : "Initializing...")), !isFetching && error && /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React23.createElement(AlertDescription, null, error)), /* @__PURE__ */ React23.createElement(
1942
- Button,
1943
- {
1944
- variant: "primary",
1945
- size: "lg",
1946
- onPress: fetchLinkToken,
1947
- style: styles13.retryButton
1948
- },
1949
- "Retry"
1950
- )))), /* @__PURE__ */ React23.createElement(ModalFooter, { style: { paddingVertical: 0 } }, /* @__PURE__ */ React23.createElement(Footer, null)));
1951
- };
1952
- var styles13 = StyleSheet13.create({
1953
- container: {
1954
- flex: 1,
1955
- alignItems: "center",
1956
- justifyContent: "center",
1957
- paddingVertical: 16
1958
- // theme.spacing.lg
1959
- },
1960
- spinner: {
1961
- marginBottom: 16
1962
- // theme.spacing.lg
1963
- },
1964
- message: {
1965
- fontSize: 16,
1966
- // theme.fontSize.lg
1967
- fontWeight: "500"
1968
- },
1969
- errorText: {
1970
- textAlign: "center"
1971
- },
1972
- retryButton: {
1973
- marginTop: 12,
1974
- width: "100%"
1975
- }
1976
- });
1977
-
1978
- // src/molecules/Integration.tsx
1979
- import React35, { useCallback } from "react";
1980
- import {
1981
- FlatList,
1982
- Image as Image4,
1983
- StyleSheet as StyleSheet17,
1984
- Text as Text15,
1985
- TouchableOpacity as TouchableOpacity7,
1986
- View as View17
1987
- } from "react-native";
1988
-
1989
- // src/assets/ArrowLeftIcon.tsx
1990
- import React24 from "react";
1991
- import Svg8, { Path as Path8 } from "react-native-svg";
1992
- var ArrowLeftIcon = ({
1993
- size = 20,
1994
- color = "#000"
1995
- }) => {
1996
- return /* @__PURE__ */ React24.createElement(Svg8, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React24.createElement(
1997
- Path8,
1998
- {
1999
- d: "M19 12H5M12 19l-7-7 7-7",
2000
- stroke: color,
2001
- strokeWidth: 2,
2002
- strokeLinecap: "round",
2003
- strokeLinejoin: "round"
2004
- }
2005
- ));
2006
- };
2007
-
2008
- // src/assets/CheckCircleIcon.tsx
2009
- import React25 from "react";
2010
- import Svg9, { Path as Path9, Circle as Circle2 } from "react-native-svg";
2011
- var CheckCircleIcon = ({
2012
- size = 20,
2013
- color = "#10B981"
2014
- }) => {
2015
- return /* @__PURE__ */ React25.createElement(Svg9, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React25.createElement(
2016
- Circle2,
2017
- {
2018
- cx: 12,
2019
- cy: 12,
2020
- r: 10,
2021
- stroke: color,
2022
- strokeWidth: 2
2023
- }
2024
- ), /* @__PURE__ */ React25.createElement(
2025
- Path9,
2026
- {
2027
- d: "m9 12 2 2 4-4",
2028
- stroke: color,
2029
- strokeWidth: 2,
2030
- strokeLinecap: "round",
2031
- strokeLinejoin: "round"
2032
- }
2033
- ));
2034
- };
2035
-
2036
- // src/assets/LoaderIcon.tsx
2037
- import React26 from "react";
2038
- import { Animated as Animated2, Easing } from "react-native";
2039
- import Svg10, { Path as Path10 } from "react-native-svg";
2040
- var AnimatedSvg = Animated2.createAnimatedComponent(Svg10);
2041
- var LoaderIcon = ({
2042
- size = 20,
2043
- color = "#00C693"
2044
- }) => {
2045
- const rotateAnim = React26.useRef(new Animated2.Value(0)).current;
2046
- React26.useEffect(() => {
2047
- Animated2.loop(
2048
- Animated2.timing(rotateAnim, {
2049
- toValue: 1,
2050
- duration: 1e3,
2051
- easing: Easing.linear,
2052
- useNativeDriver: true
2053
- })
2054
- ).start();
2055
- }, [rotateAnim]);
2056
- const spin = rotateAnim.interpolate({
2057
- inputRange: [0, 1],
2058
- outputRange: ["0deg", "360deg"]
2059
- });
2060
- return /* @__PURE__ */ React26.createElement(
2061
- AnimatedSvg,
2062
- {
2063
- width: size,
2064
- height: size,
2065
- viewBox: "0 0 24 24",
2066
- fill: "none",
2067
- style: { transform: [{ rotate: spin }] }
2068
- },
2069
- /* @__PURE__ */ React26.createElement(
2070
- Path10,
2071
- {
2072
- d: "M21 12a9 9 0 1 1-6.219-8.56",
2073
- stroke: color,
2074
- strokeWidth: 2,
2075
- strokeLinecap: "round",
2076
- strokeLinejoin: "round"
2077
- }
2078
- )
2079
- );
2080
- };
2081
-
2082
- // src/assets/ErrorIcon.tsx
2083
- import React27 from "react";
2084
- import Svg11, { Circle as Circle3, Path as Path11 } from "react-native-svg";
2085
- var ErrorIcon = ({ size = 64 }) => {
2086
- return /* @__PURE__ */ React27.createElement(Svg11, { width: size, height: size, viewBox: "0 0 64 64", fill: "none" }, /* @__PURE__ */ React27.createElement(
2087
- Circle3,
2088
- {
2089
- cx: 32,
2090
- cy: 32,
2091
- r: 30,
2092
- fill: "#EF4444",
2093
- opacity: 0.1
2094
- }
2095
- ), /* @__PURE__ */ React27.createElement(
2096
- Circle3,
2097
- {
2098
- cx: 32,
2099
- cy: 32,
2100
- r: 24,
2101
- fill: "#EF4444"
2102
- }
2103
- ), /* @__PURE__ */ React27.createElement(
2104
- Path11,
2105
- {
2106
- d: "M24 24l16 16M40 24l-16 16",
2107
- stroke: "white",
2108
- strokeWidth: 3,
2109
- strokeLinecap: "round",
2110
- strokeLinejoin: "round"
2111
- }
2112
- ));
2113
- };
2114
-
2115
- // src/assets/SearchIcon.tsx
2116
- import React28 from "react";
2117
- import Svg12, { Circle as Circle4, Path as Path12 } from "react-native-svg";
2118
-
2119
- // src/assets/PlusIcon.tsx
2120
- import React29 from "react";
2121
- import Svg13, { Path as Path13 } from "react-native-svg";
2122
- var PlusIcon = ({
2123
- size = 14,
2124
- color = "#6B7280"
2125
- }) => {
2126
- return /* @__PURE__ */ React29.createElement(Svg13, { width: size, height: size, viewBox: "0 0 14 14", fill: "none" }, /* @__PURE__ */ React29.createElement(
2127
- Path13,
2128
- {
2129
- d: "M7 3.5v7M3.5 7h7",
2130
- stroke: color,
2131
- strokeWidth: 2,
2132
- strokeLinecap: "round"
2133
- }
2134
- ));
2135
- };
2136
-
2137
- // src/assets/RedirectIcon.tsx
2138
- import React30 from "react";
2139
- import Svg14, { Path as Path14 } from "react-native-svg";
2140
- var RedirectIcon = ({
2141
- width = 20,
2142
- height = 20,
2143
- color = "currentColor"
2144
- }) => {
2145
- return /* @__PURE__ */ React30.createElement(Svg14, { width, height, viewBox: "0 0 20 20", fill: "none" }, /* @__PURE__ */ React30.createElement(
2146
- Path14,
2147
- {
2148
- d: "M10.8333 9.16536L17.6666 2.33203",
2149
- stroke: color,
2150
- strokeWidth: 2,
2151
- strokeLinecap: "round",
2152
- strokeLinejoin: "round"
2153
- }
2154
- ), /* @__PURE__ */ React30.createElement(
2155
- Path14,
2156
- {
2157
- d: "M18.3333 5.66797V1.66797H14.3333",
2158
- stroke: color,
2159
- strokeWidth: 2,
2160
- strokeLinecap: "round",
2161
- strokeLinejoin: "round"
2162
- }
2163
- ), /* @__PURE__ */ React30.createElement(
2164
- Path14,
2165
- {
2166
- d: "M9.16669 1.66797H7.50002C3.33335 1.66797 1.66669 3.33464 1.66669 7.5013V12.5013C1.66669 16.668 3.33335 18.3346 7.50002 18.3346H12.5C16.6667 18.3346 18.3334 16.668 18.3334 12.5013V10.8346",
2167
- stroke: color,
2168
- strokeWidth: 2,
2169
- strokeLinecap: "round",
2170
- strokeLinejoin: "round"
2171
- }
2172
- ));
2173
- };
2174
-
2175
- // src/wallet-connect/index.tsx
2176
- import { useAccount, useAppKit } from "@reown/appkit-react-native";
2177
- import React32, { useEffect as useEffect4, useMemo, useState as useState2 } from "react";
2178
- import { StyleSheet as StyleSheet14, Text as Text12, TouchableOpacity as TouchableOpacity4, View as View14, Image as Image2 } from "react-native";
2179
-
2180
- // src/utils/uuid.ts
2181
- function generateUUID() {
2182
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
2183
- const r = Math.random() * 16 | 0;
2184
- const v = c === "x" ? r : r & 3 | 8;
2185
- return v.toString(16);
2186
- });
2187
- }
2188
-
2189
- // src/wallet-connect/wallet-connect.tsx
2190
- import React31 from "react";
2191
- import { AppKit, AppKitProvider } from "@reown/appkit-react-native";
2192
-
2193
- // src/wallet-connect/AppKitConfig.ts
2194
- import "@walletconnect/react-native-compat";
2195
- import AsyncStorage from "@react-native-async-storage/async-storage";
2196
- import { createAppKit } from "@reown/appkit-react-native";
2197
-
2198
- // node_modules/@walletconnect/safe-json/dist/esm/index.js
2199
- var JSONStringify = (data) => JSON.stringify(data, (_, value) => typeof value === "bigint" ? value.toString() + "n" : value);
2200
- var JSONParse = (json) => {
2201
- const numbersBiggerThanMaxInt = /([\[:])?(\d{17,}|(?:[9](?:[1-9]07199254740991|0[1-9]7199254740991|00[8-9]199254740991|007[2-9]99254740991|007199[3-9]54740991|0071992[6-9]4740991|00719925[5-9]740991|007199254[8-9]40991|0071992547[5-9]0991|00719925474[1-9]991|00719925474099[2-9])))([,\}\]])/g;
2202
- const serializedData = json.replace(numbersBiggerThanMaxInt, '$1"$2n"$3');
2203
- return JSON.parse(serializedData, (_, value) => {
2204
- const isCustomFormatBigInt = typeof value === "string" && value.match(/^\d+n$/);
2205
- if (isCustomFormatBigInt)
2206
- return BigInt(value.substring(0, value.length - 1));
2207
- return value;
2208
- });
2209
- };
2210
- function safeJsonParse(value) {
2211
- if (typeof value !== "string") {
2212
- throw new Error(`Cannot safe json parse value of type ${typeof value}`);
2213
- }
2214
- try {
2215
- return JSONParse(value);
2216
- } catch (_a) {
2217
- return value;
2218
- }
2219
- }
2220
- function safeJsonStringify(value) {
2221
- return typeof value === "string" ? value : JSONStringify(value) || "";
2222
- }
2223
-
2224
- // src/wallet-connect/AppKitConfig.ts
2225
- import { EthersAdapter } from "@reown/appkit-ethers-react-native";
2226
- import {
2227
- arbitrum,
2228
- avalanche,
2229
- base,
2230
- bsc,
2231
- fantom,
2232
- gnosis,
2233
- mainnet,
2234
- optimism,
2235
- polygon
2236
- } from "viem/chains";
2237
- var ethersAdapter = new EthersAdapter();
2238
- var storage = {
2239
- getKeys: async () => {
2240
- return await AsyncStorage.getAllKeys();
2241
- },
2242
- getEntries: async () => {
2243
- const keys = await AsyncStorage.getAllKeys();
2244
- return Promise.all(
2245
- keys.map(async (key) => {
2246
- const value = await AsyncStorage.getItem(key);
2247
- return [key, safeJsonParse(value || "")];
2248
- })
2249
- );
2250
- },
2251
- setItem: async (key, value) => {
2252
- await AsyncStorage.setItem(key, safeJsonStringify(value));
2253
- },
2254
- getItem: async (key) => {
2255
- const raw = await AsyncStorage.getItem(key);
2256
- if (!raw) return void 0;
2257
- return safeJsonParse(raw);
2258
- },
2259
- removeItem: async (key) => {
2260
- await AsyncStorage.removeItem(key);
2261
- }
2262
- };
2263
- var createAppKitInstance = (projectId) => {
2264
- if (!projectId) {
2265
- throw new Error("walletConnectProjectId is required to initialize AppKit");
2266
- }
2267
- return createAppKit({
2268
- projectId,
2269
- networks: [
2270
- mainnet,
2271
- arbitrum,
2272
- avalanche,
2273
- bsc,
2274
- fantom,
2275
- gnosis,
2276
- optimism,
2277
- polygon,
2278
- base
2279
- ],
2280
- adapters: [ethersAdapter],
2281
- features: {
2282
- swaps: false,
2283
- socials: false,
2284
- onramp: false
2285
- },
2286
- metadata: {
2287
- name: "Kryptos Connect",
2288
- description: "Kryptos Connect",
2289
- url: "https://kryptos.com",
2290
- icons: ["https://kryptos.com/icon.png"]
2291
- },
2292
- storage
2293
- });
2294
- };
2295
-
2296
- // src/wallet-connect/wallet-connect.tsx
2297
- var WalletConnectWrapper = ({ children }) => {
2298
- const { walletConnectProjectId } = useKryptosConnect();
2299
- const appKit = React31.useMemo(() => {
2300
- if (!walletConnectProjectId) {
2301
- console.warn(
2302
- "walletConnectProjectId is missing in KryptosConnectProvider config"
2303
- );
2304
- return null;
2305
- }
2306
- return createAppKitInstance(walletConnectProjectId);
2307
- }, [walletConnectProjectId]);
2308
- if (!appKit) {
2309
- return /* @__PURE__ */ React31.createElement(React31.Fragment, null, children);
2310
- }
2311
- return /* @__PURE__ */ React31.createElement(AppKitProvider, { instance: appKit }, /* @__PURE__ */ React31.createElement(AppKit, null), children);
2312
- };
2313
- var wallet_connect_default = WalletConnectWrapper;
2314
-
2315
- // src/wallet-connect/index.tsx
2316
- var WalletConnectComponent = ({
2317
- integration,
2318
- onClose,
2319
- onAddHandle,
2320
- handleClose,
2321
- modalOpen,
2322
- setAddIntegrationMode,
2323
- providersList,
2324
- errorMessage,
2325
- showBackButton
2326
- }) => {
2327
- const { walletConnectProjectId } = useKryptosConnect();
2328
- const theme = useTheme();
2329
- if (!walletConnectProjectId) {
2330
- return /* @__PURE__ */ React32.createElement(Modal, { isOpen: modalOpen, onClose: handleClose, size: "full" }, /* @__PURE__ */ React32.createElement(ModalHeader, { onClose: handleClose }, /* @__PURE__ */ React32.createElement(View14, { style: styles14.headerContent }, showBackButton && /* @__PURE__ */ React32.createElement(
2331
- TouchableOpacity4,
2332
- {
2333
- onPress: () => {
2334
- setAddIntegrationMode(null);
2335
- },
2336
- style: styles14.backButton
2337
- },
2338
- /* @__PURE__ */ React32.createElement(ArrowLeftIcon, { size: 20, color: theme.colors.text })
2339
- ), /* @__PURE__ */ React32.createElement(Text12, { style: [styles14.headerTitle, { color: theme.colors.text }] }, "Integration"))), /* @__PURE__ */ React32.createElement(ModalBody, { scrollable: true, style: styles14.contentContainer }, /* @__PURE__ */ React32.createElement(View14, { style: styles14.emptyState }, /* @__PURE__ */ React32.createElement(
2340
- Text12,
2341
- {
2342
- style: [styles14.emptyStateTitle, { color: theme.colors.text }]
2343
- },
2344
- "WalletConnect is not configured"
2345
- ), /* @__PURE__ */ React32.createElement(
2346
- Text12,
2347
- {
2348
- style: [
2349
- styles14.infoText,
2350
- { color: theme.colors.textSecondary, textAlign: "center" }
2351
- ]
2352
- },
2353
- "Please add a walletConnectProjectId to KryptosConnectProvider to enable wallet connections."
2354
- ), /* @__PURE__ */ React32.createElement(
2355
- Button,
2356
- {
2357
- variant: "outline",
2358
- size: "sm",
2359
- onPress: () => setAddIntegrationMode(null),
2360
- style: styles14.emptyStateButton
2361
- },
2362
- "Go back"
2363
- ))));
2364
- }
2365
- return /* @__PURE__ */ React32.createElement(wallet_connect_default, null, /* @__PURE__ */ React32.createElement(
2366
- ConnectButton,
2367
- {
2368
- integration,
2369
- onAddHandle,
2370
- onClose,
2371
- handleClose,
2372
- modalOpen,
2373
- setAddIntegrationMode,
2374
- providersList,
2375
- errorMessage,
2376
- showBackButton
2377
- }
2378
- ));
2379
- };
2380
- function ConnectButton({
2381
- integration,
2382
- onAddHandle,
2383
- handleClose,
2384
- modalOpen,
2385
- setAddIntegrationMode,
2386
- providersList,
2387
- errorMessage: errorMessageProp,
2388
- showBackButton
2389
- }) {
2390
- const theme = useTheme();
2391
- const { open, disconnect } = useAppKit();
2392
- const { address, isConnected } = useAccount();
2393
- const { linkToken, user, clientId } = useKryptosConnect();
2394
- const [selectedChains, setSelectedChains] = useState2(/* @__PURE__ */ new Set());
2395
- const [errorMessage, setErrorMessage] = useState2("");
2396
- const [chainErrors, setChainErrors] = useState2({});
2397
- const [isLoading, setIsLoading] = useState2(false);
2398
- const [userUsedChains, setUserUsedChains] = useState2([]);
2399
- const [isFetchingChains, setIsFetchingChains] = useState2(false);
2400
- const availableChains = useMemo(() => {
2401
- if (userUsedChains.length > 0) {
2402
- return userUsedChains;
2403
- }
2404
- if (integration.walletSupportedChains && integration.walletSupportedChains.length > 0) {
2405
- return integration.walletSupportedChains.filter((supportedChain) => {
2406
- const provider = providersList.find(
2407
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2408
- (p) => p.id === supportedChain.id
2409
- );
2410
- return provider && provider.is_working === true && provider.isEvmWallet === true;
2411
- });
2412
- }
2413
- return [];
2414
- }, [userUsedChains, integration.walletSupportedChains, providersList]);
2415
- useEffect4(() => {
2416
- if (!isConnected || !address || !address.trim()) {
2417
- setUserUsedChains([]);
2418
- setSelectedChains(/* @__PURE__ */ new Set());
2419
- setIsFetchingChains(false);
2420
- return;
2421
- }
2422
- const debounceTimer = setTimeout(async () => {
2423
- if (linkToken && address && address.trim() && isConnected) {
2424
- try {
2425
- setIsFetchingChains(true);
2426
- let chains = [];
2427
- if (integration.isEvmWallet) {
2428
- const res = await getUserUsedChains(linkToken, address.trim());
2429
- if (res && Array.isArray(res) && res.length > 0) {
2430
- chains = res;
2431
- }
2432
- }
2433
- if (chains.length === 0 && integration.walletSupportedChains && integration.walletSupportedChains.length > 0) {
2434
- chains = integration.walletSupportedChains.filter(
2435
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2436
- (supportedChain) => {
2437
- const provider = providersList.find(
2438
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2439
- (p) => p.id === supportedChain.id
2440
- );
2441
- return provider && provider.is_working === true && provider.isEvmWallet === true;
2442
- }
2443
- );
2444
- }
2445
- if (chains.length > 0) {
2446
- setUserUsedChains(chains);
2447
- setSelectedChains(new Set(chains.map((chain) => chain.id)));
2448
- } else {
2449
- setUserUsedChains([]);
2450
- setSelectedChains(/* @__PURE__ */ new Set());
2451
- }
2452
- } catch (error) {
2453
- console.error("Failed to fetch user chains:", error);
2454
- if (integration.walletSupportedChains && integration.walletSupportedChains.length > 0) {
2455
- const validChains = integration.walletSupportedChains.filter(
2456
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2457
- (supportedChain) => {
2458
- const provider = providersList.find(
2459
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2460
- (p) => p.id === supportedChain.id
2461
- );
2462
- return provider && provider.is_working === true && provider.isEvmWallet === true;
2463
- }
2464
- );
2465
- setUserUsedChains(validChains);
2466
- setSelectedChains(
2467
- new Set(
2468
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2469
- validChains.map((chain) => chain.id)
2470
- )
2471
- );
2472
- } else {
2473
- setUserUsedChains([]);
2474
- setSelectedChains(/* @__PURE__ */ new Set());
2475
- }
2476
- } finally {
2477
- setIsFetchingChains(false);
2478
- }
2479
- }
2480
- }, 500);
2481
- return () => {
2482
- clearTimeout(debounceTimer);
2483
- };
2484
- }, [
2485
- linkToken,
2486
- address,
2487
- isConnected,
2488
- integration.isEvmWallet,
2489
- integration.walletSupportedChains,
2490
- providersList
2491
- ]);
2492
- const validateForm = () => {
2493
- if (!address) {
2494
- setErrorMessage("Please connect a wallet");
2495
- return false;
2496
- }
2497
- if (selectedChains.size === 0) {
2498
- setErrorMessage("Select at least one chain");
2499
- return false;
2500
- }
2501
- return true;
2502
- };
2503
- const onSubmitWalletConnect = async () => {
2504
- if (!validateForm()) return;
2505
- try {
2506
- setIsLoading(true);
2507
- setErrorMessage("");
2508
- setChainErrors({});
2509
- const chainsToProcess = availableChains.filter(
2510
- (c) => selectedChains.has(c.id)
2511
- );
2512
- const integrationsToAdd = [];
2513
- const errors = {};
2514
- const walletTestsPayload = chainsToProcess.map((chain) => {
2515
- const walletId = generateUUID();
2516
- const displaySuffix = address ? address?.length > 8 ? `${address.slice(0, 4)}...${address.slice(-4)}` : address : "";
2517
- const alias = `${integration.public_name} (${displaySuffix})`;
2518
- const provider = providersList.find((p) => p.id === chain.id);
2519
- return {
2520
- chain,
2521
- walletId,
2522
- alias,
2523
- credential: {
2524
- source: provider?.id,
2525
- credential: {
2526
- address,
2527
- userId: user?.user_id || "",
2528
- projectId: provider?.projectId,
2529
- apiKey: "",
2530
- secret: "",
2531
- privateKey: "",
2532
- alias,
2533
- walletId,
2534
- exchange: provider?.id
2535
- }
2536
- }
2537
- };
2538
- });
2539
- const results = await Promise.allSettled(
2540
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2541
- walletTestsPayload.map(
2542
- (testData) => testCredentials(linkToken, { ...testData.credential })
2543
- )
2544
- );
2545
- results.forEach((result, index) => {
2546
- const { chain, walletId, alias } = walletTestsPayload[index];
2547
- const provider = providersList.find((p) => p.id === chain.id);
2548
- if (result.status === "fulfilled" && result.value?.valid && provider) {
2549
- const data = {
2550
- alias,
2551
- exchange: provider.id.toLowerCase(),
2552
- id: provider.id,
2553
- public_name: provider.public_name,
2554
- sync_time: (/* @__PURE__ */ new Date()).getTime(),
2555
- fetchAll: true,
2556
- logo: provider.logo || null,
2557
- startTime: null,
2558
- endTime: null,
2559
- uid: user?.user_id || "",
2560
- walletId,
2561
- clientMetadata: {
2562
- clientId,
2563
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2564
- },
2565
- addedOn: (/* @__PURE__ */ new Date()).getTime(),
2566
- default_chain: integration?.id || "",
2567
- default_chain_logo: integration?.logo || "",
2568
- type: provider.type || "",
2569
- isNftSupported: provider?.isEvmWallet || provider?.nftSupport || false,
2570
- isCustomWallet: false,
2571
- chainId: provider?.community_id,
2572
- address
2573
- };
2574
- integrationsToAdd.push(data);
2575
- } else {
2576
- if (result.status === "rejected") {
2577
- errors[chain.id] = result.reason?.response?.data?.message || "Failed to process chain";
2578
- } else if (result.status === "fulfilled") {
2579
- errors[chain.id] = result.value?.message || "Failed to verify chain";
2580
- }
2581
- }
2582
- });
2583
- setChainErrors(errors);
2584
- if (Object.keys(errors).length > 0) {
2585
- setErrorMessage(
2586
- `Cannot add integrations. ${Object.keys(errors).length} chain${Object.keys(errors).length > 1 ? "s" : ""} failed verification. Please fix the errors and try again.`
2587
- );
2588
- return;
2589
- }
2590
- if (integrationsToAdd.length > 0) {
2591
- await onAddHandle(integrationsToAdd);
2592
- if (isConnected) {
2593
- disconnect();
2594
- }
2595
- setChainErrors({});
2596
- setErrorMessage("");
2597
- } else {
2598
- setErrorMessage("No integrations could be added. Please try again.");
2599
- }
2600
- } catch (error) {
2601
- const err = error;
2602
- console.error(error);
2603
- setErrorMessage(
2604
- err?.response?.data?.message || err?.response?.data?.error || "Failed to add integrations"
2605
- );
2606
- } finally {
2607
- setIsLoading(false);
2608
- }
2609
- };
2610
- const toggleChainSelection = (chainId) => {
2611
- const newSelected = new Set(selectedChains);
2612
- if (newSelected.has(chainId)) {
2613
- newSelected.delete(chainId);
2614
- } else {
2615
- newSelected.add(chainId);
2616
- }
2617
- setSelectedChains(newSelected);
2618
- if (chainErrors[chainId]) {
2619
- const newErrors = { ...chainErrors };
2620
- delete newErrors[chainId];
2621
- setChainErrors(newErrors);
2622
- }
2623
- };
2624
- const onClose = () => {
2625
- if (isConnected) {
2626
- disconnect();
2627
- }
2628
- handleClose();
2629
- };
2630
- return /* @__PURE__ */ React32.createElement(Modal, { isOpen: modalOpen, onClose, size: "full" }, /* @__PURE__ */ React32.createElement(ModalHeader, { onClose }, /* @__PURE__ */ React32.createElement(View14, { style: styles14.headerContent }, showBackButton && /* @__PURE__ */ React32.createElement(
2631
- TouchableOpacity4,
2632
- {
2633
- onPress: () => {
2634
- setAddIntegrationMode(null);
2635
- if (isConnected) {
2636
- disconnect();
2637
- }
2638
- },
2639
- style: styles14.backButton
2640
- },
2641
- /* @__PURE__ */ React32.createElement(ArrowLeftIcon, { size: 20, color: theme.colors.text })
2642
- ), /* @__PURE__ */ React32.createElement(Text12, { style: [styles14.headerTitle, { color: theme.colors.text }] }, "Integration"))), /* @__PURE__ */ React32.createElement(ModalBody, { scrollable: true, style: styles14.contentContainer }, /* @__PURE__ */ React32.createElement(View14, { style: styles14.header }, integration.logo ? isSvgUrl(integration.logo) ? /* @__PURE__ */ React32.createElement(RemoteSvg, { uri: integration.logo }) : /* @__PURE__ */ React32.createElement(
2643
- Image2,
2644
- {
2645
- source: { uri: integration.logo },
2646
- style: styles14.logo,
2647
- resizeMode: "contain"
2648
- }
2649
- ) : /* @__PURE__ */ React32.createElement(
2650
- View14,
2651
- {
2652
- style: [
2653
- styles14.logoPlaceholder,
2654
- { backgroundColor: theme.colors.surface }
2655
- ]
2656
- },
2657
- /* @__PURE__ */ React32.createElement(Text12, { style: { color: theme.colors.text } }, integration.name?.charAt(0) || "?")
2658
- ), /* @__PURE__ */ React32.createElement(Text12, { style: [styles14.name, { color: theme.colors.text }] }, integration.name)), !isConnected ? /* @__PURE__ */ React32.createElement(View14, null, /* @__PURE__ */ React32.createElement(Text12, { style: [styles14.infoText, { color: theme.colors.text }] }, "Connect your wallet to continue"), /* @__PURE__ */ React32.createElement(
2659
- Button,
2660
- {
2661
- variant: "primary",
2662
- size: "sm",
2663
- onPress: () => open({ view: "Connect" })
2664
- },
2665
- "Connect Wallet"
2666
- )) : /* @__PURE__ */ React32.createElement(View14, { style: styles14.connectedContainer }, /* @__PURE__ */ React32.createElement(View14, { style: styles14.connectedHeader }, /* @__PURE__ */ React32.createElement(
2667
- Text12,
2668
- {
2669
- style: [styles14.connectedTitle, { color: theme.colors.text }]
2670
- },
2671
- "Wallet Connected"
2672
- ), /* @__PURE__ */ React32.createElement(
2673
- Text12,
2674
- {
2675
- style: [styles14.connectedText, { color: theme.colors.text }]
2676
- },
2677
- "Address: ",
2678
- address
2679
- ), /* @__PURE__ */ React32.createElement(Button, { variant: "ghost", size: "sm", onPress: () => disconnect() }, "Disconnect Wallet"), isFetchingChains ? /* @__PURE__ */ React32.createElement(
2680
- Text12,
2681
- {
2682
- style: [styles14.fetchingText, { color: theme.colors.text }]
2683
- },
2684
- "Fetching chains..."
2685
- ) : null), availableChains.length > 0 && address && /* @__PURE__ */ React32.createElement(View14, { style: styles14.chainListWrapper }, /* @__PURE__ */ React32.createElement(Text12, { style: [styles14.chainTitle, { color: theme.colors.text }] }, "Select Chains to Add:"), /* @__PURE__ */ React32.createElement(View14, { style: styles14.chainListContent }, /* @__PURE__ */ React32.createElement(View14, { style: styles14.chainChips }, availableChains.map((chain) => {
2686
- const isSelected = selectedChains.has(chain.id);
2687
- const hasError = chainErrors[chain.id];
2688
- return /* @__PURE__ */ React32.createElement(
2689
- TouchableOpacity4,
2690
- {
2691
- onPress: () => toggleChainSelection(chain.id),
2692
- style: styles14.chainButton,
2693
- key: chain.id
2694
- },
2695
- /* @__PURE__ */ React32.createElement(
2696
- View14,
2697
- {
2698
- style: [
2699
- styles14.chainChip,
2700
- {
2701
- backgroundColor: hasError ? theme.colors.errorLight : isSelected ? theme.colors.primary + "20" : theme.colors.surface,
2702
- borderColor: hasError ? theme.colors.error : isSelected ? theme.colors.primary : theme.colors.border
2703
- }
2704
- ]
2705
- },
2706
- /* @__PURE__ */ React32.createElement(
2707
- Text12,
2708
- {
2709
- style: [
2710
- styles14.chainName,
2711
- {
2712
- color: hasError ? theme.colors.error : isSelected ? theme.colors.primary : theme.colors.text
2713
- }
2714
- ]
2715
- },
2716
- chain.name || chain?.id
2717
- ),
2718
- isSelected ? /* @__PURE__ */ React32.createElement(
2719
- CloseIcon,
2720
- {
2721
- size: 12,
2722
- color: hasError ? theme.colors.error : theme.colors.primary
2723
- }
2724
- ) : /* @__PURE__ */ React32.createElement(
2725
- PlusIcon,
2726
- {
2727
- size: 12,
2728
- color: theme.colors.textSecondary
2729
- }
2730
- )
2731
- )
2732
- );
2733
- })), selectedChains.size > 0 && availableChains.length > 0 && /* @__PURE__ */ React32.createElement(
2734
- TouchableOpacity4,
2735
- {
2736
- onPress: () => setSelectedChains(/* @__PURE__ */ new Set()),
2737
- style: styles14.chainButton,
2738
- activeOpacity: 0.7
2739
- },
2740
- /* @__PURE__ */ React32.createElement(
2741
- View14,
2742
- {
2743
- style: [
2744
- styles14.chainChip,
2745
- styles14.chainChipRemoveAll,
2746
- {
2747
- borderWidth: 0,
2748
- backgroundColor: "transparent"
2749
- }
2750
- ]
2751
- },
2752
- /* @__PURE__ */ React32.createElement(
2753
- Text12,
2754
- {
2755
- style: [
2756
- styles14.chainName,
2757
- { color: theme.colors.primary }
2758
- ]
2759
- },
2760
- "Remove All Chains"
2761
- ),
2762
- /* @__PURE__ */ React32.createElement(CloseIcon, { size: 12, color: theme.colors.primary })
2763
- )
2764
- ), Object.keys(chainErrors || {}).length > 0 && /* @__PURE__ */ React32.createElement(View14, { style: styles14.chainErrorsContainer }, /* @__PURE__ */ React32.createElement(
2765
- Text12,
2766
- {
2767
- style: [
2768
- styles14.chainErrorsTitle,
2769
- { color: theme.colors.error }
2770
- ]
2771
- },
2772
- "Errors:"
2773
- ), Object.entries(chainErrors).map(([chainId, error]) => {
2774
- const chain = availableChains.find(
2775
- (c) => c.id === chainId
2776
- );
2777
- return /* @__PURE__ */ React32.createElement(
2778
- Text12,
2779
- {
2780
- key: chainId,
2781
- style: [
2782
- styles14.chainErrorItem,
2783
- { color: theme.colors.error }
2784
- ]
2785
- },
2786
- "\u2022 ",
2787
- chain?.name ?? chainId,
2788
- ": ",
2789
- String(error)
2790
- );
2791
- })))), errorMessage || errorMessageProp ? /* @__PURE__ */ React32.createElement(View14, { style: styles14.errorMessageContainer }, /* @__PURE__ */ React32.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React32.createElement(AlertDescription, null, errorMessage || errorMessageProp))) : null)), /* @__PURE__ */ React32.createElement(ModalFooter, { style: { paddingVertical: 8 } }, availableChains.length > 0 && address && /* @__PURE__ */ React32.createElement(
2792
- Button,
2793
- {
2794
- variant: "outline",
2795
- size: "lg",
2796
- onPress: onSubmitWalletConnect,
2797
- loading: isLoading,
2798
- disabled: isLoading || !!address && availableChains.length > 0 && selectedChains.size === 0,
2799
- style: styles14.button
2800
- },
2801
- selectedChains.size > 0 ? `Add ${selectedChains.size} Chain${selectedChains.size > 1 ? "s" : ""}` : "Add Integration"
2802
- ), /* @__PURE__ */ React32.createElement(Footer, null)));
2803
- }
2804
- var styles14 = StyleSheet14.create({
2805
- connectedContainer: {
2806
- flexShrink: 0
2807
- },
2808
- chainListWrapper: {
2809
- marginTop: 8
2810
- },
2811
- chainListContent: {
2812
- paddingBottom: 8
2813
- },
2814
- connectedHeader: {
2815
- marginBottom: 8
2816
- },
2817
- connectedTitle: { fontSize: 12, fontWeight: "600", marginBottom: 4 },
2818
- connectedText: { fontSize: 14, marginBottom: 4 },
2819
- fetchingText: { fontSize: 12, marginBottom: 4, textAlign: "center" },
2820
- infoText: {
2821
- fontSize: 16,
2822
- fontWeight: "600",
2823
- marginBottom: 8,
2824
- textAlign: "center"
2825
- },
2826
- errorMessageContainer: {
2827
- marginTop: 16
2828
- },
2829
- headerContent: {
2830
- flexDirection: "row",
2831
- alignItems: "center"
2832
- },
2833
- backButton: {
2834
- padding: 4,
2835
- // theme.spacing.xs
2836
- marginRight: 8
2837
- // theme.spacing.sm
2838
- },
2839
- headerTitle: {
2840
- fontSize: 16,
2841
- // theme.fontSize.lg
2842
- fontWeight: "600"
2843
- },
2844
- contentContainer: {
2845
- padding: 20,
2846
- // theme.spacing.xl
2847
- paddingBottom: 20,
2848
- width: "100%",
2849
- alignSelf: "center",
2850
- flexDirection: "column"
2851
- },
2852
- chainTitle: {
2853
- fontSize: 14,
2854
- // theme.fontSize.md
2855
- fontWeight: "500",
2856
- marginBottom: 12
2857
- // theme.spacing.md - consistent label spacing
2858
- },
2859
- chainChips: {
2860
- flexDirection: "row",
2861
- flexWrap: "wrap",
2862
- gap: 6
2863
- // theme.spacing.sm
2864
- },
2865
- chainChip: {
2866
- flexDirection: "row",
2867
- alignItems: "center",
2868
- paddingHorizontal: 8,
2869
- // theme.spacing.sm
2870
- paddingVertical: 5,
2871
- // theme.spacing.xs
2872
- borderRadius: 12,
2873
- // theme.borderRadius.md
2874
- borderWidth: 1
2875
- },
2876
- chainChipRemoveAll: {
2877
- marginTop: 6,
2878
- // theme.spacing.sm - separate from chain list
2879
- width: "auto"
2880
- },
2881
- chainName: {
2882
- fontSize: 12,
2883
- fontWeight: "500",
2884
- marginRight: 6
2885
- // theme.spacing.xs
2886
- },
2887
- chainButton: {
2888
- padding: 2
2889
- // theme.spacing.xs
2890
- },
2891
- chainErrorsContainer: {
2892
- marginTop: 12
2893
- // theme.spacing.md - consistent spacing
2894
- },
2895
- chainErrorsTitle: {
2896
- fontSize: 13,
2897
- fontWeight: "500",
2898
- marginBottom: 4
2899
- // theme.spacing.xs
2900
- },
2901
- chainErrorItem: {
2902
- fontSize: 12,
2903
- // theme.fontSize.sm
2904
- marginBottom: 4
2905
- // theme.spacing.xs
2906
- },
2907
- button: {
2908
- width: "100%"
2909
- },
2910
- emptyState: {
2911
- flex: 1,
2912
- alignItems: "center",
2913
- justifyContent: "center",
2914
- gap: 12
2915
- },
2916
- emptyStateTitle: {
2917
- fontSize: 18,
2918
- fontWeight: "600"
2919
- },
2920
- emptyStateButton: {
2921
- marginTop: 8
2922
- },
2923
- header: {
2924
- flexDirection: "row",
2925
- justifyContent: "center",
2926
- alignItems: "center",
2927
- marginBottom: 16
2928
- // theme.spacing.lg
2929
- },
2930
- logo: {
2931
- width: 32,
2932
- height: 32,
2933
- borderRadius: 8
2934
- // theme.borderRadius.sm
2935
- },
2936
- logoPlaceholder: {
2937
- width: 32,
2938
- height: 32,
2939
- borderRadius: 8,
2940
- // theme.borderRadius.sm
2941
- alignItems: "center",
2942
- justifyContent: "center"
2943
- },
2944
- name: {
2945
- fontSize: 16,
2946
- // theme.fontSize.lg
2947
- fontWeight: "600",
2948
- marginLeft: 12,
2949
- // theme.spacing.md
2950
- textTransform: "capitalize"
2951
- }
2952
- });
2953
-
2954
- // src/molecules/IntegrationForm.tsx
2955
- import React34 from "react";
2956
- import { Image as Image3, StyleSheet as StyleSheet16, Text as Text14, TouchableOpacity as TouchableOpacity6, View as View16 } from "react-native";
2957
-
2958
- // src/molecules/IntegrationInfo.tsx
2959
- import React33, { useMemo as useMemo2, useState as useState3 } from "react";
2960
- import {
2961
- Linking as Linking3,
2962
- StyleSheet as StyleSheet15,
2963
- Text as Text13,
2964
- TouchableOpacity as TouchableOpacity5,
2965
- View as View15
2966
- } from "react-native";
2967
- var ChevronDown = ({ isOpen, color }) => /* @__PURE__ */ React33.createElement(Text13, { style: [styles15.chevron, { color }] }, isOpen ? "\u25BC" : "\u25B6");
2968
- var IntegrationInfo = ({
2969
- content,
2970
- type,
2971
- import_guide
2972
- }) => {
2973
- const theme = useTheme();
2974
- const instructionsData = useMemo2(
2975
- () => content?.instructions?.[type] || null,
2976
- [content, type]
2977
- );
2978
- const featuresData = useMemo2(
2979
- () => content?.features?.[type] || [],
2980
- [content, type]
2981
- );
2982
- const gtkData = useMemo2(() => content?.gtk?.[type] || [], [content, type]);
2983
- const hasInstructions = instructionsData && (instructionsData?.content?.length ?? 0) > 0;
2984
- const hasFeatures = (featuresData?.length ?? 0) > 0;
2985
- const hasGtk = (gtkData?.length ?? 0) > 0;
2986
- const defaultSection = hasInstructions ? "instructions" : hasGtk ? "good-to-know" : hasFeatures ? "supported-features" : null;
2987
- const [openSection, setOpenSection] = useState3(defaultSection);
2988
- const toggleSection = (value) => {
2989
- setOpenSection((prev) => prev === value ? null : value);
2990
- };
2991
- const handleImportGuidePress = () => {
2992
- if (import_guide) Linking3.openURL(import_guide);
2993
- };
2994
- if (!hasInstructions && !hasFeatures && !hasGtk && !import_guide) {
2995
- return null;
2996
- }
2997
- return /* @__PURE__ */ React33.createElement(View15, { style: styles15.container }, import_guide && /* @__PURE__ */ React33.createElement(
2998
- TouchableOpacity5,
2999
- {
3000
- onPress: handleImportGuidePress,
3001
- style: [styles15.importGuide, { borderColor: theme.colors.primary }],
3002
- activeOpacity: 0.7
3003
- },
3004
- /* @__PURE__ */ React33.createElement(
3005
- Text13,
3006
- {
3007
- style: [styles15.importGuideText, { color: theme.colors.primary }]
3008
- },
3009
- "Import Guide"
3010
- ),
3011
- /* @__PURE__ */ React33.createElement(RedirectIcon, { width: 14, height: 14, color: theme.colors.primary })
3012
- ), hasInstructions && /* @__PURE__ */ React33.createElement(View15, { style: styles15.accordionItem }, /* @__PURE__ */ React33.createElement(
3013
- TouchableOpacity5,
3014
- {
3015
- onPress: () => toggleSection("instructions"),
3016
- style: [
3017
- styles15.trigger,
3018
- {
3019
- borderBottomColor: theme.colors.border,
3020
- backgroundColor: theme.colors.surface
3021
- }
3022
- ],
3023
- activeOpacity: 0.7
3024
- },
3025
- /* @__PURE__ */ React33.createElement(Text13, { style: [styles15.triggerText, { color: theme.colors.text }] }, "Instructions"),
3026
- /* @__PURE__ */ React33.createElement(
3027
- ChevronDown,
3028
- {
3029
- isOpen: openSection === "instructions",
3030
- color: theme.colors.textSecondary
3031
- }
3032
- )
3033
- ), openSection === "instructions" && /* @__PURE__ */ React33.createElement(
3034
- View15,
3035
- {
3036
- style: [
3037
- styles15.content,
3038
- {
3039
- backgroundColor: theme.colors.background,
3040
- borderBottomColor: theme.colors.border
3041
- }
3042
- ]
3043
- },
3044
- instructionsData?.content && /* @__PURE__ */ React33.createElement(View15, { style: styles15.list }, instructionsData.content.map(
3045
- (step, index) => /* @__PURE__ */ React33.createElement(
3046
- Text13,
3047
- {
3048
- key: index,
3049
- style: [
3050
- styles15.listItem,
3051
- styles15.orderedItem,
3052
- { color: theme.colors.textSecondary }
3053
- ]
3054
- },
3055
- index + 1,
3056
- ". ",
3057
- step
3058
- )
3059
- )),
3060
- instructionsData?.videoLink && /* @__PURE__ */ React33.createElement(
3061
- TouchableOpacity5,
3062
- {
3063
- onPress: () => Linking3.openURL(instructionsData.videoLink),
3064
- style: styles15.videoLink,
3065
- activeOpacity: 0.7
3066
- },
3067
- /* @__PURE__ */ React33.createElement(
3068
- Text13,
3069
- {
3070
- style: [
3071
- styles15.videoLinkText,
3072
- { color: theme.colors.primary }
3073
- ]
3074
- },
3075
- "\u25B6 Watch Video Tutorial"
3076
- )
3077
- )
3078
- )), hasGtk && /* @__PURE__ */ React33.createElement(View15, { style: styles15.accordionItem }, /* @__PURE__ */ React33.createElement(
3079
- TouchableOpacity5,
3080
- {
3081
- onPress: () => toggleSection("good-to-know"),
3082
- style: [
3083
- styles15.trigger,
3084
- {
3085
- borderBottomColor: theme.colors.border,
3086
- backgroundColor: theme.colors.surface
3087
- }
3088
- ],
3089
- activeOpacity: 0.7
3090
- },
3091
- /* @__PURE__ */ React33.createElement(Text13, { style: [styles15.triggerText, { color: theme.colors.text }] }, "Good to know"),
3092
- /* @__PURE__ */ React33.createElement(
3093
- ChevronDown,
3094
- {
3095
- isOpen: openSection === "good-to-know",
3096
- color: theme.colors.textSecondary
3097
- }
3098
- )
3099
- ), openSection === "good-to-know" && /* @__PURE__ */ React33.createElement(
3100
- View15,
3101
- {
3102
- style: [
3103
- styles15.content,
3104
- {
3105
- backgroundColor: theme.colors.background,
3106
- borderBottomColor: theme.colors.border
3107
- }
3108
- ]
3109
- },
3110
- /* @__PURE__ */ React33.createElement(View15, { style: styles15.list }, gtkData.map((item, index) => /* @__PURE__ */ React33.createElement(
3111
- Text13,
3112
- {
3113
- key: index,
3114
- style: [
3115
- styles15.listItem,
3116
- styles15.bulletItem,
3117
- { color: theme.colors.textSecondary }
3118
- ]
3119
- },
3120
- "\u2022 ",
3121
- item
3122
- )))
3123
- )), hasFeatures && /* @__PURE__ */ React33.createElement(View15, { style: styles15.accordionItem }, /* @__PURE__ */ React33.createElement(
3124
- TouchableOpacity5,
3125
- {
3126
- onPress: () => toggleSection("supported-features"),
3127
- style: [
3128
- styles15.trigger,
3129
- {
3130
- borderBottomColor: theme.colors.border,
3131
- backgroundColor: theme.colors.surface
3132
- }
3133
- ],
3134
- activeOpacity: 0.7
3135
- },
3136
- /* @__PURE__ */ React33.createElement(Text13, { style: [styles15.triggerText, { color: theme.colors.text }] }, "Supported Features"),
3137
- /* @__PURE__ */ React33.createElement(
3138
- ChevronDown,
3139
- {
3140
- isOpen: openSection === "supported-features",
3141
- color: theme.colors.textSecondary
3142
- }
3143
- )
3144
- ), openSection === "supported-features" && /* @__PURE__ */ React33.createElement(
3145
- View15,
3146
- {
3147
- style: [
3148
- styles15.content,
3149
- {
3150
- backgroundColor: theme.colors.background,
3151
- borderBottomColor: theme.colors.border
3152
- }
3153
- ]
3154
- },
3155
- /* @__PURE__ */ React33.createElement(View15, { style: styles15.list }, featuresData.map((feature, index) => /* @__PURE__ */ React33.createElement(
3156
- Text13,
3157
- {
3158
- key: index,
3159
- style: [
3160
- styles15.listItem,
3161
- styles15.bulletItem,
3162
- { color: theme.colors.textSecondary }
3163
- ]
3164
- },
3165
- "\u2022 ",
3166
- feature
3167
- )))
3168
- )));
3169
- };
3170
- var styles15 = StyleSheet15.create({
3171
- container: {
3172
- marginTop: 8
3173
- },
3174
- importGuide: {
3175
- flexDirection: "row",
3176
- alignItems: "center",
3177
- alignSelf: "flex-end",
3178
- gap: 4,
3179
- paddingVertical: 6,
3180
- paddingHorizontal: 8,
3181
- marginBottom: 8
3182
- },
3183
- importGuideText: {
3184
- fontSize: 14,
3185
- fontWeight: "500"
3186
- },
3187
- accordionItem: {
3188
- marginBottom: 4,
3189
- borderRadius: 8,
3190
- overflow: "hidden"
3191
- },
3192
- trigger: {
3193
- flexDirection: "row",
3194
- alignItems: "center",
3195
- justifyContent: "space-between",
3196
- paddingVertical: 12,
3197
- paddingHorizontal: 16,
3198
- borderBottomWidth: 1
3199
- },
3200
- triggerText: {
3201
- fontSize: 14,
3202
- fontWeight: "600"
3203
- },
3204
- chevron: {
3205
- fontSize: 12
3206
- },
3207
- content: {
3208
- paddingVertical: 12,
3209
- paddingHorizontal: 16,
3210
- borderBottomWidth: 1
3211
- },
3212
- list: {
3213
- paddingLeft: 4
3214
- },
3215
- listItem: {
3216
- fontSize: 13,
3217
- lineHeight: 20,
3218
- marginBottom: 6
3219
- },
3220
- orderedItem: {
3221
- marginLeft: 0
3222
- },
3223
- bulletItem: {
3224
- marginLeft: 0
3225
- },
3226
- videoLink: {
3227
- marginTop: 8,
3228
- paddingVertical: 8,
3229
- paddingHorizontal: 12
3230
- },
3231
- videoLinkText: {
3232
- fontSize: 13,
3233
- fontWeight: "500"
3234
- }
3235
- });
3236
-
3237
- // src/molecules/IntegrationForm.tsx
3238
- var IntegrationForm = ({
3239
- metadata,
3240
- onAddHandle,
3241
- open,
3242
- setAddIntegrationMode,
3243
- handleClose,
3244
- providersList,
3245
- errorMessage: errorMessageProp,
3246
- showBackButton = true
3247
- }) => {
3248
- const { clientId, linkToken, user } = useKryptosConnect();
3249
- const theme = useTheme();
3250
- const [isLoading, setIsLoading] = React34.useState(false);
3251
- const [isFetchingChains, setIsFetchingChains] = React34.useState(false);
3252
- const [userUsedChains, setUserUsedChains] = React34.useState([]);
3253
- const [selectedChains, setSelectedChains] = React34.useState(
3254
- /* @__PURE__ */ new Set()
3255
- );
3256
- const [chainErrors, setChainErrors] = React34.useState(
3257
- {}
3258
- );
3259
- const [errorMessage, setErrorMessage] = React34.useState("");
3260
- const [formValues, setFormValues] = React34.useState({
3261
- address: "",
3262
- account_name: "",
3263
- api_key: "",
3264
- secret_key: "",
3265
- password: ""
3266
- });
3267
- const [formErrors, setFormErrors] = React34.useState({});
3268
- React34.useEffect(() => {
3269
- if (!formValues.address || !formValues.address.trim()) {
3270
- setUserUsedChains([]);
3271
- setSelectedChains(/* @__PURE__ */ new Set());
3272
- setIsFetchingChains(false);
3273
- return;
3274
- }
3275
- const debounceTimer = setTimeout(async () => {
3276
- if (linkToken && formValues.address && formValues.address.trim() && metadata.isEvmWallet) {
3277
- try {
3278
- setIsFetchingChains(true);
3279
- const res = await getUserUsedChains(
3280
- linkToken,
3281
- formValues.address.trim()
3282
- );
3283
- if (res && Array.isArray(res)) {
3284
- setUserUsedChains(res);
3285
- setSelectedChains(new Set(res.map((chain) => chain.id)));
3286
- } else {
3287
- setUserUsedChains([]);
3288
- setSelectedChains(/* @__PURE__ */ new Set());
3289
- }
3290
- } catch (error) {
3291
- console.error("Failed to fetch user chains:", error);
3292
- setUserUsedChains([]);
3293
- setSelectedChains(/* @__PURE__ */ new Set());
3294
- } finally {
3295
- setIsFetchingChains(false);
3296
- }
3297
- }
3298
- }, 500);
3299
- return () => {
3300
- clearTimeout(debounceTimer);
3301
- };
3302
- }, [linkToken, formValues.address]);
3303
- const toggleChainSelection = (chainId) => {
3304
- const newSelected = new Set(selectedChains);
3305
- if (newSelected.has(chainId)) {
3306
- newSelected.delete(chainId);
3307
- } else {
3308
- newSelected.add(chainId);
3309
- }
3310
- setSelectedChains(newSelected);
3311
- if (chainErrors[chainId]) {
3312
- const newErrors = { ...chainErrors };
3313
- delete newErrors[chainId];
3314
- setChainErrors(newErrors);
3315
- }
3316
- };
3317
- const validateForm = () => {
3318
- const errors = {};
3319
- if (metadata.address && !formValues.address.trim()) {
3320
- errors.address = "Address is required";
3321
- }
3322
- if (metadata.account_name && !formValues.account_name.trim()) {
3323
- errors.account_name = "Account name is required";
3324
- }
3325
- if (metadata.api_key && !formValues.api_key.trim()) {
3326
- errors.api_key = "API key is required";
3327
- }
3328
- if (metadata.secret_key && !formValues.secret_key.trim()) {
3329
- errors.secret_key = "Secret key is required";
3330
- }
3331
- if (metadata.password && !formValues.password.trim()) {
3332
- errors.password = "Password is required";
3333
- }
3334
- setFormErrors(errors);
3335
- return Object.keys(errors).length === 0;
3336
- };
3337
- const handleSubmit = async () => {
3338
- if (!validateForm()) return;
3339
- try {
3340
- setIsLoading(true);
3341
- setChainErrors({});
3342
- setErrorMessage("");
3343
- const primaryField = formValues.address || formValues.account_name || formValues.api_key || "";
3344
- const chainsToProcess = userUsedChains.length > 0 && formValues.address && selectedChains.size > 0 ? userUsedChains.filter((chain) => selectedChains.has(chain.id)) : [];
3345
- const integrationsToAdd = [];
3346
- const errors = {};
3347
- if (chainsToProcess.length > 0) {
3348
- const credentialTestsData = chainsToProcess.map((chain) => {
3349
- const walletId = generateUUID();
3350
- const displaySuffix = primaryField.length >= 8 ? `${primaryField.slice(0, 4)}...${primaryField.slice(-4)}` : primaryField;
3351
- const alias = `${metadata.id} - ${chain?.name || ""} (${displaySuffix})`;
3352
- const credential = {
3353
- source: metadata.id,
3354
- credential: {
3355
- apiKey: formValues.api_key?.trim() || "",
3356
- secret: formValues.secret_key?.trim() || "",
3357
- accountName: formValues.account_name?.trim() || "",
3358
- address: formValues.address?.trim() || "",
3359
- password: formValues.password?.trim() || "",
3360
- userId: user?.user_id || "",
3361
- projectId: metadata?.projectId || "",
3362
- privateKey: "",
3363
- alias,
3364
- walletId,
3365
- exchange: metadata.id
3366
- }
3367
- };
3368
- return { chain, walletId, alias, credential };
3369
- });
3370
- const results = await Promise.allSettled(
3371
- credentialTestsData.map(
3372
- (testData) => testCredentials(linkToken, { ...testData.credential })
3373
- )
3374
- );
3375
- results.forEach((result, index) => {
3376
- const { chain, walletId, alias } = credentialTestsData[index];
3377
- if (result.status === "fulfilled" && result.value?.valid) {
3378
- const data = {
3379
- alias,
3380
- exchange: chain.id.toLowerCase(),
3381
- id: chain.id,
3382
- public_name: chain.public_name || "",
3383
- sync_time: (/* @__PURE__ */ new Date()).getTime(),
3384
- fetchAll: true,
3385
- logo: chain.logo ? chain.logo : null,
3386
- startTime: null,
3387
- endTime: null,
3388
- uid: user?.user_id || "",
3389
- walletId,
3390
- clientMetadata: {
3391
- clientId,
3392
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
3393
- },
3394
- addedOn: (/* @__PURE__ */ new Date()).getTime(),
3395
- default_chain: metadata.id,
3396
- default_chain_logo: "",
3397
- type: metadata.type,
3398
- isNftSupported: metadata.isEvmWallet || metadata.nftSupport ? true : false,
3399
- chainId: chain?.community_id,
3400
- address: formValues.address,
3401
- isCustomWallet: false
3402
- };
3403
- if (formValues.account_name)
3404
- data.accountName = formValues.account_name;
3405
- if (formValues.api_key) data.apiKey = formValues.api_key;
3406
- if (formValues.secret_key) data.secret = formValues.secret_key;
3407
- if (formValues.password) data.password = formValues.password;
3408
- integrationsToAdd.push(data);
3409
- } else {
3410
- if (result.status === "rejected") {
3411
- errors[chain.id] = result.reason?.response?.data?.message || "Failed to process chain";
3412
- } else if (result.status === "fulfilled") {
3413
- errors[chain.id] = result.value?.message || "Failed to verify chain";
3414
- }
3415
- }
3416
- });
3417
- setChainErrors(errors);
3418
- if (Object.keys(errors).length > 0) {
3419
- setErrorMessage(
3420
- `Cannot add integrations. ${Object.keys(errors).length} chain${Object.keys(errors).length > 1 ? "s" : ""} failed verification. Please fix the errors and try again.`
3421
- );
3422
- return;
3423
- }
3424
- } else {
3425
- const displaySuffix = primaryField.length >= 8 ? `${primaryField.slice(0, 4)}...${primaryField.slice(-4)}` : primaryField;
3426
- const alias = `${metadata.id} (${displaySuffix})`;
3427
- const walletId = generateUUID();
3428
- const credential = {
3429
- source: metadata.id,
3430
- credential: {
3431
- apiKey: formValues.api_key?.trim() || "",
3432
- secret: formValues.secret_key?.trim() || "",
3433
- accountName: formValues.account_name?.trim() || "",
3434
- address: formValues.address?.trim() || "",
3435
- password: formValues.password?.trim() || "",
3436
- userId: user?.user_id || "",
3437
- projectId: metadata?.projectId || "",
3438
- privateKey: "",
3439
- alias,
3440
- walletId,
3441
- exchange: metadata.id
3442
- }
3443
- };
3444
- const testResult = await testCredentials(linkToken, { ...credential });
3445
- if (!testResult?.valid) {
3446
- setErrorMessage(testResult?.message || "Credentials are invalid");
3447
- return;
3448
- }
3449
- const data = {
3450
- alias,
3451
- exchange: metadata.id.toLowerCase(),
3452
- id: metadata.id,
3453
- public_name: metadata.public_name,
3454
- sync_time: (/* @__PURE__ */ new Date()).getTime(),
3455
- fetchAll: true,
3456
- logo: metadata.logo ? metadata.logo : null,
3457
- startTime: null,
3458
- endTime: null,
3459
- uid: user?.user_id || "",
3460
- walletId,
3461
- clientMetadata: {
3462
- clientId,
3463
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
3464
- },
3465
- addedOn: (/* @__PURE__ */ new Date()).getTime(),
3466
- default_chain: metadata.id,
3467
- default_chain_logo: "",
3468
- type: metadata.type,
3469
- isNftSupported: metadata.isEvmWallet || metadata.nftSupport ? true : false,
3470
- isCustomWallet: false
3471
- };
3472
- if (metadata.community_id) {
3473
- data.chainId = metadata.community_id;
3474
- }
3475
- if (formValues.address) data.address = formValues.address;
3476
- if (formValues.account_name) data.accountName = formValues.account_name;
3477
- if (formValues.api_key) data.apiKey = formValues.api_key;
3478
- if (formValues.secret_key) data.secret = formValues.secret_key;
3479
- if (formValues.password) data.password = formValues.password;
3480
- integrationsToAdd.push(data);
3481
- }
3482
- if (integrationsToAdd.length > 0) {
3483
- await onAddHandle(integrationsToAdd);
3484
- setFormErrors({});
3485
- setFormValues({
3486
- address: "",
3487
- account_name: "",
3488
- api_key: "",
3489
- secret_key: "",
3490
- password: ""
3491
- });
3492
- setErrorMessage("");
3493
- } else {
3494
- setErrorMessage("No integrations could be added. Please try again.");
3495
- }
3496
- } catch (error) {
3497
- console.error(error);
3498
- setErrorMessage(
3499
- error?.response?.data?.message || error?.response?.data?.error || "Failed to add integrations"
3500
- );
3501
- } finally {
3502
- setIsLoading(false);
3503
- }
3504
- };
3505
- const hasNoFields = !metadata.password && !metadata.secret_key && !metadata.api_key && !metadata.address && !metadata.account_name;
3506
- const shouldShowFormFields = metadata.password || metadata.secret_key || metadata.api_key || metadata.address || metadata.account_name;
3507
- const renderLogo = () => metadata.logo ? isSvgUrl(metadata.logo) ? /* @__PURE__ */ React34.createElement(RemoteSvg, { uri: metadata.logo }) : /* @__PURE__ */ React34.createElement(
3508
- Image3,
3509
- {
3510
- source: { uri: metadata.logo },
3511
- style: styles16.logo,
3512
- resizeMode: "contain"
3513
- }
3514
- ) : /* @__PURE__ */ React34.createElement(
3515
- View16,
3516
- {
3517
- style: [
3518
- styles16.logoPlaceholder,
3519
- { backgroundColor: theme.colors.surface }
3520
- ]
3521
- },
3522
- /* @__PURE__ */ React34.createElement(Text14, { style: { color: theme.colors.text } }, metadata.name?.charAt(0) || "?")
3523
- );
3524
- const renderInput = (key, props) => /* @__PURE__ */ React34.createElement(
3525
- Input,
3526
- {
3527
- placeholder: props.placeholder,
3528
- value: formValues[key],
3529
- onChangeText: (text) => setFormValues((prev) => ({ ...prev, [key]: text })),
3530
- error: formErrors[key],
3531
- autoCapitalize: props.autoCapitalize,
3532
- autoCorrect: props.autoCorrect,
3533
- secureTextEntry: props.secureTextEntry
3534
- }
3535
- );
3536
- const renderErrorAlert = () => errorMessage || errorMessageProp ? /* @__PURE__ */ React34.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React34.createElement(AlertDescription, null, errorMessage || errorMessageProp)) : null;
3537
- const renderChainSelection = () => userUsedChains.length > 0 && formValues.address && /* @__PURE__ */ React34.createElement(View16, { style: styles16.chainSelection }, /* @__PURE__ */ React34.createElement(Text14, { style: [styles16.chainTitle, { color: theme.colors.text }] }, "Select Chains to Add:"), /* @__PURE__ */ React34.createElement(View16, { style: styles16.chainListContent }, /* @__PURE__ */ React34.createElement(View16, { style: styles16.chainChips }, userUsedChains.map((chain) => {
3538
- const isSelected = selectedChains.has(chain.id);
3539
- const hasError = chainErrors[chain.id];
3540
- return /* @__PURE__ */ React34.createElement(
3541
- TouchableOpacity6,
3542
- {
3543
- onPress: () => toggleChainSelection(chain.id),
3544
- style: styles16.chainButton,
3545
- key: chain.id
3546
- },
3547
- /* @__PURE__ */ React34.createElement(
3548
- View16,
3549
- {
3550
- style: [
3551
- styles16.chainChip,
3552
- {
3553
- backgroundColor: hasError ? theme.colors.errorLight : isSelected ? theme.colors.primary + "20" : theme.colors.surface,
3554
- borderColor: hasError ? theme.colors.error : isSelected ? theme.colors.primary : theme.colors.border
3555
- }
3556
- ]
3557
- },
3558
- /* @__PURE__ */ React34.createElement(
3559
- Text14,
3560
- {
3561
- style: [
3562
- styles16.chainName,
3563
- {
3564
- color: hasError ? theme.colors.error : isSelected ? theme.colors.primary : theme.colors.text
3565
- }
3566
- ]
3567
- },
3568
- chain.name
3569
- ),
3570
- isSelected ? /* @__PURE__ */ React34.createElement(
3571
- CloseIcon,
3572
- {
3573
- size: 12,
3574
- color: hasError ? theme.colors.error : theme.colors.primary
3575
- }
3576
- ) : /* @__PURE__ */ React34.createElement(PlusIcon, { size: 12, color: theme.colors.textSecondary })
3577
- )
3578
- );
3579
- })), selectedChains.size > 0 && userUsedChains.length > 0 && /* @__PURE__ */ React34.createElement(
3580
- TouchableOpacity6,
3581
- {
3582
- onPress: () => setSelectedChains(/* @__PURE__ */ new Set()),
3583
- style: styles16.chainButton,
3584
- activeOpacity: 0.7
3585
- },
3586
- /* @__PURE__ */ React34.createElement(
3587
- View16,
3588
- {
3589
- style: [
3590
- styles16.chainChip,
3591
- styles16.chainChipRemoveAll,
3592
- {
3593
- borderWidth: 0
3594
- }
3595
- ]
3596
- },
3597
- /* @__PURE__ */ React34.createElement(
3598
- Text14,
3599
- {
3600
- style: [styles16.chainName, { color: theme.colors.primary }]
3601
- },
3602
- "Remove All Chains"
3603
- ),
3604
- /* @__PURE__ */ React34.createElement(CloseIcon, { size: 12, color: theme.colors.primary })
3605
- )
3606
- )), Object.keys(chainErrors).length > 0 && /* @__PURE__ */ React34.createElement(View16, { style: styles16.chainErrorsContainer }, /* @__PURE__ */ React34.createElement(
3607
- Text14,
3608
- {
3609
- style: [styles16.chainErrorsTitle, { color: theme.colors.error }]
3610
- },
3611
- "Errors:"
3612
- ), Object.entries(chainErrors).map(([chainId, error]) => {
3613
- const chain = userUsedChains.find((c) => c.id === chainId);
3614
- return /* @__PURE__ */ React34.createElement(
3615
- Text14,
3616
- {
3617
- key: chainId,
3618
- style: [styles16.chainErrorItem, { color: theme.colors.error }]
3619
- },
3620
- "\u2022 ",
3621
- chain?.name,
3622
- ": ",
3623
- error
3624
- );
3625
- })));
3626
- const renderFormBlock = () => /* @__PURE__ */ React34.createElement(React34.Fragment, null, /* @__PURE__ */ React34.createElement(View16, { style: styles16.header }, renderLogo(), /* @__PURE__ */ React34.createElement(Text14, { style: [styles16.name, { color: theme.colors.text }] }, metadata.name)), shouldShowFormFields && /* @__PURE__ */ React34.createElement(React34.Fragment, null, metadata.address && /* @__PURE__ */ React34.createElement(React34.Fragment, null, renderInput("address", {
3627
- placeholder: "Enter address",
3628
- autoCapitalize: "none",
3629
- autoCorrect: false
3630
- }), renderChainSelection()), metadata.account_name && renderInput("account_name", { placeholder: "Enter account name" }), metadata.api_key && renderInput("api_key", {
3631
- placeholder: "Enter API key",
3632
- autoCapitalize: "none",
3633
- autoCorrect: false
3634
- }), metadata.secret_key && renderInput("secret_key", {
3635
- placeholder: "Enter API secret",
3636
- autoCapitalize: "none",
3637
- autoCorrect: false,
3638
- secureTextEntry: true
3639
- }), metadata.password && renderInput("password", {
3640
- placeholder: "Enter Password",
3641
- secureTextEntry: true
3642
- })), hasNoFields && !metadata?.isWalletConnectSupported && /* @__PURE__ */ React34.createElement(Alert, { variant: "default", style: { marginTop: 12 } }, /* @__PURE__ */ React34.createElement(AlertDescription, null, "This integration is not supported here yet \u2014 try using it through our Kryptos Platform.")));
3643
- const addIntegrationLabel = formValues.address && userUsedChains.length > 0 && selectedChains.size > 0 ? `Add ${selectedChains.size} Chain${selectedChains.size !== 1 ? "s" : ""}` : "Add Integration";
3644
- return /* @__PURE__ */ React34.createElement(React34.Fragment, null, !metadata?.isWalletConnectSupported ? /* @__PURE__ */ React34.createElement(Modal, { isOpen: open, onClose: handleClose, size: "full" }, /* @__PURE__ */ React34.createElement(ModalHeader, { onClose: handleClose }, /* @__PURE__ */ React34.createElement(View16, { style: styles16.headerContent }, showBackButton && /* @__PURE__ */ React34.createElement(
3645
- TouchableOpacity6,
3646
- {
3647
- onPress: () => setAddIntegrationMode(null),
3648
- style: styles16.backButton
3649
- },
3650
- /* @__PURE__ */ React34.createElement(ArrowLeftIcon, { size: 20, color: theme.colors.text })
3651
- ), /* @__PURE__ */ React34.createElement(Text14, { style: [styles16.headerTitle, { color: theme.colors.text }] }, "Integration"))), /* @__PURE__ */ React34.createElement(ModalBody, { scrollable: true, style: styles16.contentContainer }, renderFormBlock(), renderErrorAlert(), !metadata?.isWalletConnectSupported && metadata?.walletLimitations && /* @__PURE__ */ React34.createElement(
3652
- IntegrationInfo,
3653
- {
3654
- content: metadata.walletLimitations,
3655
- type: "api",
3656
- import_guide: metadata.import_guide
3657
- }
3658
- )), !hasNoFields && /* @__PURE__ */ React34.createElement(ModalFooter, { style: { paddingVertical: 8 } }, /* @__PURE__ */ React34.createElement(
3659
- Button,
3660
- {
3661
- variant: "outline",
3662
- size: "lg",
3663
- onPress: handleSubmit,
3664
- loading: isLoading,
3665
- disabled: isLoading || isFetchingChains || !!formValues.address && userUsedChains.length > 0 && selectedChains.size === 0,
3666
- style: styles16.button
3667
- },
3668
- addIntegrationLabel
3669
- ), /* @__PURE__ */ React34.createElement(Footer, null))) : /* @__PURE__ */ React34.createElement(
3670
- WalletConnectComponent,
3671
- {
3672
- integration: metadata,
3673
- onClose: handleClose,
3674
- onAddHandle,
3675
- modalOpen: open,
3676
- setAddIntegrationMode,
3677
- handleClose,
3678
- providersList,
3679
- errorMessage: errorMessageProp,
3680
- showBackButton
3681
- }
3682
- ));
3683
- };
3684
- var styles16 = StyleSheet16.create({
3685
- chainListContent: {
3686
- paddingBottom: 8
3687
- },
3688
- headerContent: {
3689
- flexDirection: "row",
3690
- alignItems: "center"
3691
- },
3692
- backButton: {
3693
- padding: 4,
3694
- // theme.spacing.xs
3695
- marginRight: 8
3696
- // theme.spacing.sm
3697
- },
3698
- headerTitle: {
3699
- fontSize: 16,
3700
- // theme.fontSize.lg
3701
- fontWeight: "600"
3702
- },
3703
- container: {
3704
- flex: 1
3705
- },
3706
- contentContainer: {
3707
- padding: 20,
3708
- // theme.spacing.xl
3709
- paddingBottom: 40,
3710
- width: "100%",
3711
- alignSelf: "center",
3712
- flexDirection: "column"
3713
- },
3714
- header: {
3715
- flexDirection: "row",
3716
- justifyContent: "center",
3717
- alignItems: "center",
3718
- marginBottom: 16
3719
- // theme.spacing.lg
3720
- },
3721
- logo: {
3722
- width: 32,
3723
- height: 32,
3724
- borderRadius: 8
3725
- // theme.borderRadius.sm
3726
- },
3727
- logoPlaceholder: {
3728
- width: 32,
3729
- height: 32,
3730
- borderRadius: 8,
3731
- // theme.borderRadius.sm
3732
- alignItems: "center",
3733
- justifyContent: "center"
3734
- },
3735
- name: {
3736
- fontSize: 16,
3737
- // theme.fontSize.lg
3738
- fontWeight: "600",
3739
- marginLeft: 12,
3740
- // theme.spacing.md
3741
- textTransform: "capitalize"
3742
- },
3743
- chainSelection: {
3744
- marginBottom: 8
3745
- // theme.spacing.lg
3746
- },
3747
- chainTitle: {
3748
- fontSize: 14,
3749
- // theme.fontSize.md
3750
- fontWeight: "500",
3751
- marginBottom: 12
3752
- // theme.spacing.md - consistent label spacing
3753
- },
3754
- chainChips: {
3755
- flexDirection: "row",
3756
- flexWrap: "wrap",
3757
- gap: 6
3758
- // theme.spacing.sm
3759
- },
3760
- chainChip: {
3761
- flexDirection: "row",
3762
- alignItems: "center",
3763
- paddingHorizontal: 8,
3764
- // theme.spacing.sm
3765
- paddingVertical: 5,
3766
- // theme.spacing.xs
3767
- borderRadius: 12,
3768
- // theme.borderRadius.md
3769
- borderWidth: 1
3770
- },
3771
- chainChipRemoveAll: {
3772
- marginTop: 6,
3773
- // theme.spacing.sm - separate from chain list
3774
- width: "auto"
3775
- },
3776
- chainName: {
3777
- fontSize: 12,
3778
- fontWeight: "500",
3779
- marginRight: 6
3780
- // theme.spacing.xs
3781
- },
3782
- chainButton: {
3783
- padding: 2
3784
- // theme.spacing.xs
3785
- },
3786
- chainErrorsContainer: {
3787
- marginTop: 12
3788
- // theme.spacing.md - consistent spacing
3789
- },
3790
- chainErrorsTitle: {
3791
- fontSize: 13,
3792
- fontWeight: "500",
3793
- marginBottom: 4
3794
- // theme.spacing.xs
3795
- },
3796
- chainErrorItem: {
3797
- fontSize: 12,
3798
- // theme.fontSize.sm
3799
- marginBottom: 4
3800
- // theme.spacing.xs
3801
- },
3802
- button: {
3803
- width: "100%"
3804
- }
3805
- });
3806
-
3807
- // src/molecules/Integration.tsx
3808
- var Integration = ({
3809
- open,
3810
- onSuccess,
3811
- onClose,
3812
- integrationDetails
3813
- }) => {
3814
- const { appName, linkToken } = useKryptosConnect();
3815
- const theme = useTheme();
3816
- const [addIntegrationMode, setAddIntegrationMode] = React35.useState(integrationDetails || null);
3817
- const [query, setQuery] = React35.useState("");
3818
- const [activeTab, setActiveTab] = React35.useState("all");
3819
- const [supportedProviders, setSupportedProviders] = React35.useState([]);
3820
- const [existingIntegrations, setExistingIntegrations] = React35.useState([]);
3821
- const [isLoading, setIsLoading] = React35.useState(false);
3822
- const [errorMessage, setErrorMessage] = React35.useState("");
3823
- React35.useEffect(() => {
3824
- if (integrationDetails) {
3825
- setAddIntegrationMode(integrationDetails);
3826
- }
3827
- }, [integrationDetails]);
3828
- const handleClose = () => {
3829
- onClose();
3830
- };
3831
- const fetchExistingIntegrations = async () => {
3832
- try {
3833
- const res = await getUserIntegrations(linkToken);
3834
- setExistingIntegrations(res?.integrations || []);
3835
- } catch (error) {
3836
- const err = error;
3837
- setErrorMessage(
3838
- err?.response?.data?.message || err?.response?.data?.error || "Failed to fetch existing integrations"
3839
- );
3840
- console.error(error);
3841
- }
3842
- };
3843
- const fetchSupportedProviders = async () => {
3844
- try {
3845
- setIsLoading(true);
3846
- const res = await getSupportedProviders(linkToken);
3847
- setSupportedProviders(res?.providers || []);
3848
- } catch (error) {
3849
- const err = error;
3850
- setErrorMessage(
3851
- err?.response?.data?.message || err?.response?.data?.error || "Failed to fetch supported providers"
3852
- );
3853
- console.error(error);
3854
- } finally {
3855
- setIsLoading(false);
3856
- }
3857
- };
3858
- React35.useEffect(() => {
3859
- if (linkToken) {
3860
- fetchSupportedProviders();
3861
- fetchExistingIntegrations();
3862
- }
3863
- }, [linkToken]);
3864
- const isIntegrationAdded = useCallback(
3865
- (publicName) => {
3866
- const integrations = [...existingIntegrations];
3867
- return integrations.some(
3868
- (integration) => integration.public_name === publicName
3869
- );
3870
- },
3871
- [existingIntegrations]
3872
- );
3873
- const getIntegrationCount = useCallback(
3874
- (publicName) => {
3875
- const integrations = [...existingIntegrations];
3876
- return integrations.filter(
3877
- (integration) => integration.public_name === publicName
3878
- ).length;
3879
- },
3880
- [existingIntegrations]
3881
- );
3882
- const filteredResults = React35.useMemo(() => {
3883
- let filtered = supportedProviders;
3884
- if (activeTab !== "all") {
3885
- filtered = filtered.filter((provider) => provider.type === activeTab);
3886
- }
3887
- if (query?.trim()) {
3888
- const lowerQuery = query.trim().toLowerCase();
3889
- filtered = filtered.filter((provider) => {
3890
- return provider.name?.toLowerCase().includes(lowerQuery) || provider.public_name?.toLowerCase().includes(lowerQuery) || provider.id?.toLowerCase().includes(lowerQuery);
3891
- });
3892
- }
3893
- return [...filtered].sort((a, b) => {
3894
- const countA = getIntegrationCount(a.public_name);
3895
- const countB = getIntegrationCount(b.public_name);
3896
- if (countB !== countA) return countB - countA;
3897
- return (a.name ?? "").localeCompare(b.name ?? "");
3898
- });
3899
- }, [query, supportedProviders, getIntegrationCount, activeTab]);
3900
- const handleAddIntegration = async (integrationsList) => {
3901
- try {
3902
- setIsLoading(true);
3903
- const integrations = integrationsList ? integrationsList : [...existingIntegrations];
3904
- if (integrations.length === 0) {
3905
- setErrorMessage("Please add at least one integration");
3906
- } else {
3907
- await addUserIntegration(linkToken, integrations);
3908
- if (integrationDetails) {
3909
- onSuccess();
3910
- }
3911
- }
3912
- await fetchExistingIntegrations();
3913
- setAddIntegrationMode(null);
3914
- } catch (error) {
3915
- const message = error?.response?.data?.message || error?.response?.data?.error || "Failed to add integrations";
3916
- setErrorMessage(message);
3917
- throw error;
3918
- } finally {
3919
- setIsLoading(false);
3920
- }
3921
- };
3922
- const handleContinue = () => {
3923
- const integrations = [...existingIntegrations];
3924
- if (integrations.length === 0) {
3925
- setErrorMessage("Please add at least one integration");
3926
- return;
3927
- }
3928
- onSuccess();
3929
- };
3930
- const renderProviderItem = ({ item }) => /* @__PURE__ */ React35.createElement(
3931
- TouchableOpacity7,
3932
- {
3933
- style: [
3934
- styles17.providerItem,
3935
- {
3936
- backgroundColor: theme.colors.surface,
3937
- borderColor: theme.colors.border
3938
- }
3939
- ],
3940
- onPress: () => setAddIntegrationMode(item),
3941
- activeOpacity: 0.7
3942
- },
3943
- /* @__PURE__ */ React35.createElement(View17, { style: styles17.providerInfo }, item?.logo ? isSvgUrl(item?.logo) ? /* @__PURE__ */ React35.createElement(RemoteSvg, { uri: item?.logo }) : /* @__PURE__ */ React35.createElement(
3944
- Image4,
3945
- {
3946
- source: { uri: item?.logo },
3947
- style: styles17.providerLogo,
3948
- resizeMode: "contain"
3949
- }
3950
- ) : /* @__PURE__ */ React35.createElement(
3951
- View17,
3952
- {
3953
- style: [
3954
- styles17.providerLogoPlaceholder,
3955
- { backgroundColor: theme.colors.surfaceSecondary }
3956
- ]
3957
- },
3958
- /* @__PURE__ */ React35.createElement(Text15, { style: { color: theme.colors.text } }, item?.name?.charAt(0) || "?")
3959
- ), /* @__PURE__ */ React35.createElement(Text15, { style: [styles17.providerName, { color: theme.colors.text }] }, item?.name + "\u200B")),
3960
- isIntegrationAdded(item?.public_name) && /* @__PURE__ */ React35.createElement(View17, { style: styles17.providerStatus }, /* @__PURE__ */ React35.createElement(CheckCircleIcon, { size: 18, color: theme.colors.success }), /* @__PURE__ */ React35.createElement(
3961
- Text15,
3962
- {
3963
- style: [
3964
- styles17.providerCount,
3965
- { color: theme.colors.textSecondary }
3966
- ]
3967
- },
3968
- getIntegrationCount(item?.public_name)
3969
- ))
3970
- );
3971
- const renderSkeletonItem = () => /* @__PURE__ */ React35.createElement(SkeletonItem_default, null);
3972
- return /* @__PURE__ */ React35.createElement(React35.Fragment, null, !addIntegrationMode ? /* @__PURE__ */ React35.createElement(Modal, { isOpen: open, onClose: handleClose, size: "full" }, /* @__PURE__ */ React35.createElement(ModalHeader, { onClose: handleClose }, /* @__PURE__ */ React35.createElement(View17, { style: styles17.headerContent }, addIntegrationMode && /* @__PURE__ */ React35.createElement(
3973
- TouchableOpacity7,
3974
- {
3975
- onPress: () => setAddIntegrationMode(null),
3976
- style: styles17.backButton
3977
- },
3978
- /* @__PURE__ */ React35.createElement(ArrowLeftIcon, { size: 20, color: theme.colors.text })
3979
- ), /* @__PURE__ */ React35.createElement(Text15, { style: [styles17.headerTitle, { color: theme.colors.text }] }, "Integration"))), /* @__PURE__ */ React35.createElement(ModalBody, { scrollable: false, style: styles17.noPadding }, /* @__PURE__ */ React35.createElement(View17, { style: styles17.container }, /* @__PURE__ */ React35.createElement(View17, { style: styles17.headerSection }, /* @__PURE__ */ React35.createElement(ConnectLogo, null), /* @__PURE__ */ React35.createElement(Text15, { style: [styles17.title, { color: theme.colors.text }] }, "Select an account to link to ", appName)), /* @__PURE__ */ React35.createElement(
3980
- View17,
3981
- {
3982
- style: {
3983
- paddingHorizontal: theme.spacing.xl,
3984
- paddingVertical: theme.spacing.sm + 2,
3985
- backgroundColor: theme.colors.background,
3986
- zIndex: 10
3987
- }
3988
- },
3989
- /* @__PURE__ */ React35.createElement(
3990
- Input,
3991
- {
3992
- value: query,
3993
- onChangeText: setQuery,
3994
- placeholder: "Search Integrations...",
3995
- containerStyle: styles17.searchInput
3996
- }
3997
- ),
3998
- /* @__PURE__ */ React35.createElement(View17, { style: styles17.tabsContainer }, [
3999
- { label: "All", value: "all" },
4000
- { label: "Exchanges", value: "exchange" },
4001
- { label: "Blockchains", value: "blockchain" },
4002
- { label: "Wallets", value: "wallet" }
4003
- ].map((tab) => /* @__PURE__ */ React35.createElement(
4004
- TouchableOpacity7,
4005
- {
4006
- key: tab.value,
4007
- style: [
4008
- styles17.tab,
4009
- {
4010
- backgroundColor: activeTab === tab.value ? theme.colors.primary : theme.colors.surface,
4011
- borderColor: theme.colors.border
4012
- }
4013
- ],
4014
- onPress: () => {
4015
- setActiveTab(
4016
- tab.value
4017
- );
4018
- }
4019
- },
4020
- /* @__PURE__ */ React35.createElement(
4021
- Text15,
4022
- {
4023
- style: [
4024
- styles17.tabText,
4025
- {
4026
- color: activeTab === tab.value ? theme.colors.white : theme.colors.text
4027
- }
4028
- ]
4029
- },
4030
- tab.label
4031
- )
4032
- )))
4033
- ), /* @__PURE__ */ React35.createElement(
4034
- FlatList,
4035
- {
4036
- data: isLoading ? Array.from({ length: 8 }, (_, i) => ({
4037
- id: `skeleton-${i}`,
4038
- name: "",
4039
- public_name: "",
4040
- type: ""
4041
- })) : filteredResults,
4042
- keyExtractor: (item, index) => isLoading ? item.id : `provider-${item.id}-${index}`,
4043
- renderItem: isLoading ? renderSkeletonItem : renderProviderItem,
4044
- style: styles17.list,
4045
- contentContainerStyle: [
4046
- styles17.listContent,
4047
- { paddingHorizontal: theme.spacing.xl }
4048
- ],
4049
- showsVerticalScrollIndicator: false,
4050
- ListEmptyComponent: /* @__PURE__ */ React35.createElement(View17, { style: styles17.emptyContainer }, !isLoading && /* @__PURE__ */ React35.createElement(
4051
- Text15,
4052
- {
4053
- style: [
4054
- styles17.emptyText,
4055
- { color: theme.colors.textSecondary }
4056
- ]
4057
- },
4058
- query ? "No search results found" : "No supported integrations found"
4059
- )),
4060
- ListFooterComponent: errorMessage ? /* @__PURE__ */ React35.createElement(View17, { style: styles17.errorContainer }, /* @__PURE__ */ React35.createElement(Alert, { variant: "destructive", style: styles17.errorAlert }, /* @__PURE__ */ React35.createElement(AlertDescription, null, errorMessage))) : null
4061
- }
4062
- ))), /* @__PURE__ */ React35.createElement(ModalFooter, { style: { paddingVertical: 8 } }, filteredResults && filteredResults.length > 0 && /* @__PURE__ */ React35.createElement(
4063
- Button,
4064
- {
4065
- variant: "outline",
4066
- size: "lg",
4067
- onPress: handleContinue,
4068
- loading: isLoading,
4069
- disabled: isLoading,
4070
- style: styles17.continueButton
4071
- },
4072
- "Continue"
4073
- ), /* @__PURE__ */ React35.createElement(Footer, null))) : /* @__PURE__ */ React35.createElement(
4074
- IntegrationForm,
4075
- {
4076
- metadata: addIntegrationMode,
4077
- onAddHandle: async (int) => {
4078
- const integrationsList = [...int];
4079
- await handleAddIntegration(integrationsList);
4080
- },
4081
- open: !!addIntegrationMode,
4082
- setAddIntegrationMode,
4083
- handleClose,
4084
- providersList: supportedProviders,
4085
- errorMessage,
4086
- showBackButton: !integrationDetails
4087
- }
4088
- ));
4089
- };
4090
- var styles17 = StyleSheet17.create({
4091
- headerContent: {
4092
- flexDirection: "row",
4093
- alignItems: "center"
4094
- },
4095
- backButton: {
4096
- padding: 4,
4097
- // theme.spacing.xs
4098
- marginRight: 8
4099
- // theme.spacing.sm
4100
- },
4101
- headerTitle: {
4102
- fontSize: 16,
4103
- // theme.fontSize.lg
4104
- fontWeight: "600"
4105
- },
4106
- noPadding: {
4107
- padding: 0
4108
- },
4109
- container: {
4110
- flex: 1
4111
- },
4112
- headerSection: {
4113
- paddingHorizontal: 20,
4114
- // theme.spacing.xl
4115
- paddingTop: 10
4116
- // theme.spacing.xl
4117
- },
4118
- title: {
4119
- fontSize: 16,
4120
- // theme.fontSize.lg
4121
- fontWeight: "600",
4122
- textAlign: "center",
4123
- marginBottom: 10
4124
- // theme.spacing.lg
4125
- },
4126
- searchInput: {},
4127
- list: {
4128
- flex: 1,
4129
- minHeight: 200
4130
- },
4131
- listContent: {
4132
- paddingBottom: 12
4133
- // theme.spacing.md - consistent padding
4134
- },
4135
- providerItem: {
4136
- flexDirection: "row",
4137
- alignItems: "center",
4138
- justifyContent: "space-between",
4139
- paddingVertical: 8,
4140
- // theme.spacing.md
4141
- paddingHorizontal: 12,
4142
- // theme.spacing.md
4143
- borderRadius: 12,
4144
- // theme.borderRadius.md
4145
- borderWidth: 1,
4146
- marginBottom: 12
4147
- // theme.spacing.md - consistent list item spacing
4148
- },
4149
- providerInfo: {
4150
- flexDirection: "row",
4151
- alignItems: "center",
4152
- flex: 1
4153
- },
4154
- providerLogo: {
4155
- width: 32,
4156
- height: 32,
4157
- borderRadius: 8
4158
- // theme.borderRadius.sm
4159
- },
4160
- providerLogoPlaceholder: {
4161
- width: 32,
4162
- height: 32,
4163
- borderRadius: 8,
4164
- // theme.borderRadius.sm
4165
- alignItems: "center",
4166
- justifyContent: "center"
4167
- },
4168
- providerName: {
4169
- fontSize: 14,
4170
- // theme.fontSize.md
4171
- fontWeight: "500",
4172
- marginLeft: 12,
4173
- // theme.spacing.md
4174
- textTransform: "capitalize"
4175
- },
4176
- providerStatus: {
4177
- flexDirection: "row",
4178
- alignItems: "center"
4179
- },
4180
- providerCount: {
4181
- fontSize: 14,
4182
- // theme.fontSize.md
4183
- marginLeft: 4
4184
- // theme.spacing.xs
4185
- },
4186
- emptyContainer: {
4187
- flex: 1,
4188
- alignItems: "center",
4189
- justifyContent: "center",
4190
- paddingVertical: 40
4191
- // theme.spacing.xxxl + sm
4192
- },
4193
- emptyText: {
4194
- fontSize: 14
4195
- // theme.fontSize.md
4196
- },
4197
- continueButton: {
4198
- width: "100%"
4199
- },
4200
- errorContainer: {
4201
- paddingHorizontal: 20,
4202
- // theme.spacing.xl
4203
- marginBottom: 12
4204
- },
4205
- errorAlert: {
4206
- marginTop: 8
4207
- },
4208
- tabsContainer: {
4209
- flexDirection: "row",
4210
- gap: 6,
4211
- // theme.spacing.sm
4212
- flexWrap: "wrap"
4213
- },
4214
- tab: {
4215
- paddingVertical: 4,
4216
- // theme.spacing.sm
4217
- paddingHorizontal: 10,
4218
- // theme.spacing.lg
4219
- borderRadius: 8,
4220
- // theme.borderRadius.full / 2
4221
- borderWidth: 1,
4222
- alignItems: "center",
4223
- justifyContent: "center"
4224
- },
4225
- tabText: {
4226
- fontSize: 12,
4227
- // theme.fontSize.sm
4228
- fontWeight: "600"
4229
- }
4230
- });
4231
-
4232
- // src/molecules/OTPVerify.tsx
4233
- import React36 from "react";
4234
- import { StyleSheet as StyleSheet18, Text as Text16, TouchableOpacity as TouchableOpacity8, View as View18 } from "react-native";
4235
- var OTPVerify = ({
4236
- open,
4237
- onSuccess,
4238
- onClose
4239
- }) => {
4240
- const theme = useTheme();
4241
- const [otp, setOtp] = React36.useState("");
4242
- const { linkToken, clientId, email, setUser } = useKryptosConnect();
4243
- const [isLoading, setIsLoading] = React36.useState(false);
4244
- const [isResending, setIsResending] = React36.useState(false);
4245
- const [resendCooldown, setResendCooldown] = React36.useState(0);
4246
- const [errorMessage, setErrorMessage] = React36.useState("");
4247
- const [successMessage, setSuccessMessage] = React36.useState("");
4248
- const handleSubmit = async () => {
4249
- if (otp.length !== 6) return;
4250
- setIsLoading(true);
4251
- setErrorMessage("");
4252
- try {
4253
- const res = await loginWithOtp(linkToken, email, otp, clientId);
4254
- setUser(res);
4255
- onSuccess();
4256
- } catch (error) {
4257
- const err = error;
4258
- setErrorMessage(
4259
- err?.response?.data?.message || "Failed to login with OTP"
4260
- );
4261
- console.error(error);
4262
- } finally {
4263
- setIsLoading(false);
4264
- }
4265
- };
4266
- const handleOtpComplete = (value) => {
4267
- setOtp(value);
4268
- };
4269
- const handleResendOtp = async () => {
4270
- if (resendCooldown > 0 || isResending) return;
4271
- try {
4272
- setIsResending(true);
4273
- setErrorMessage("");
4274
- setSuccessMessage("");
4275
- await sendEmailOtp(linkToken, email, clientId);
4276
- setSuccessMessage("OTP sent successfully!");
4277
- setResendCooldown(60);
4278
- setOtp("");
4279
- setTimeout(() => {
4280
- setSuccessMessage("");
4281
- }, 3e3);
4282
- } catch (error) {
4283
- const err = error;
4284
- setErrorMessage(
4285
- err?.response?.data?.message || "Failed to resend OTP. Please try again."
4286
- );
4287
- console.error(error);
4288
- } finally {
4289
- setIsResending(false);
4290
- }
4291
- };
4292
- const handleClose = () => {
4293
- onClose();
4294
- setErrorMessage("");
4295
- setSuccessMessage("");
4296
- setOtp("");
4297
- };
4298
- React36.useEffect(() => {
4299
- if (resendCooldown > 0) {
4300
- const timer = setTimeout(() => {
4301
- setResendCooldown(resendCooldown - 1);
4302
- }, 1e3);
4303
- return () => clearTimeout(timer);
4304
- }
4305
- return void 0;
4306
- }, [resendCooldown]);
4307
- return /* @__PURE__ */ React36.createElement(Modal, { isOpen: open, onClose: handleClose, size: "lg" }, /* @__PURE__ */ React36.createElement(ModalHeader, { onClose: handleClose }, /* @__PURE__ */ React36.createElement(View18, { style: styles18.headerContent }, /* @__PURE__ */ React36.createElement(Text16, { style: [styles18.headerTitle, { color: theme.colors.text }] }, "OTP Verification"))), /* @__PURE__ */ React36.createElement(ModalBody, null, /* @__PURE__ */ React36.createElement(View18, { style: styles18.container }, /* @__PURE__ */ React36.createElement(ConnectLogo, null), /* @__PURE__ */ React36.createElement(View18, { style: styles18.emailInfo }, /* @__PURE__ */ React36.createElement(
4308
- Text16,
4309
- {
4310
- style: [styles18.infoText, { color: theme.colors.textSecondary }]
4311
- },
4312
- "We have sent a verification code to"
4313
- ), /* @__PURE__ */ React36.createElement(Text16, { style: [styles18.emailText, { color: theme.colors.text }] }, email)), /* @__PURE__ */ React36.createElement(
4314
- OTP,
4315
- {
4316
- onComplete: handleOtpComplete,
4317
- length: 6,
4318
- setErrorMessage
4319
- }
4320
- ), errorMessage ? /* @__PURE__ */ React36.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React36.createElement(AlertDescription, null, errorMessage)) : null, successMessage ? /* @__PURE__ */ React36.createElement(Alert, { variant: "default" }, /* @__PURE__ */ React36.createElement(AlertDescription, null, successMessage)) : null, /* @__PURE__ */ React36.createElement(
4321
- Button,
4322
- {
4323
- variant: "outline",
4324
- size: "lg",
4325
- onPress: handleSubmit,
4326
- loading: isLoading,
4327
- disabled: otp.length !== 6 || isLoading,
4328
- style: styles18.button
4329
- },
4330
- "Continue"
4331
- ), /* @__PURE__ */ React36.createElement(
4332
- TouchableOpacity8,
4333
- {
4334
- onPress: handleResendOtp,
4335
- disabled: resendCooldown > 0 || isResending,
4336
- style: styles18.resendContainer
4337
- },
4338
- isResending ? /* @__PURE__ */ React36.createElement(View18, { style: styles18.resendLoading }, /* @__PURE__ */ React36.createElement(LoaderIcon, { size: 16, color: theme.colors.primary }), /* @__PURE__ */ React36.createElement(
4339
- Text16,
4340
- {
4341
- style: [styles18.resendText, { color: theme.colors.primary }]
4342
- },
4343
- " ",
4344
- "Sending..."
4345
- )) : resendCooldown > 0 ? /* @__PURE__ */ React36.createElement(
4346
- Text16,
4347
- {
4348
- style: [
4349
- styles18.resendText,
4350
- { color: theme.colors.textSecondary }
4351
- ]
4352
- },
4353
- "Resend OTP in ",
4354
- resendCooldown,
4355
- "s"
4356
- ) : /* @__PURE__ */ React36.createElement(
4357
- Text16,
4358
- {
4359
- style: [styles18.resendText, { color: theme.colors.primary }]
4360
- },
4361
- "Resend OTP"
4362
- )
4363
- ))), /* @__PURE__ */ React36.createElement(ModalFooter, { style: { paddingVertical: 0 } }, /* @__PURE__ */ React36.createElement(Footer, null)));
4364
- };
4365
- var styles18 = StyleSheet18.create({
4366
- headerContent: {
4367
- flexDirection: "row",
4368
- alignItems: "center"
4369
- },
4370
- backButton: {
4371
- padding: 4,
4372
- // theme.spacing.xs
4373
- marginRight: 8
4374
- // theme.spacing.sm
4375
- },
4376
- headerTitle: {
4377
- fontSize: 16,
4378
- // theme.fontSize.lg
4379
- fontWeight: "600"
4380
- },
4381
- container: {
4382
- flex: 1,
4383
- alignItems: "center"
4384
- },
4385
- emailInfo: {
4386
- alignItems: "center",
4387
- marginBottom: 24
4388
- // theme.spacing.xxl
4389
- },
4390
- infoText: {
4391
- fontSize: 14,
4392
- // theme.fontSize.md
4393
- marginBottom: 4
4394
- // theme.spacing.xs
4395
- },
4396
- emailText: {
4397
- fontSize: 16,
4398
- // theme.fontSize.lg
4399
- fontWeight: "600"
4400
- },
4401
- button: {
4402
- width: "100%",
4403
- marginTop: 16
4404
- // theme.spacing.lg
4405
- },
4406
- resendContainer: {
4407
- marginTop: 16,
4408
- // theme.spacing.lg - consistent spacing
4409
- padding: 8
4410
- // theme.spacing.sm
4411
- },
4412
- resendLoading: {
4413
- flexDirection: "row",
4414
- alignItems: "center"
4415
- },
4416
- resendText: {
4417
- fontSize: 14,
4418
- // theme.fontSize.md
4419
- fontWeight: "500"
4420
- }
4421
- });
4422
-
4423
- // src/molecules/Permissions.tsx
4424
- import React37 from "react";
4425
- import { StyleSheet as StyleSheet19, Text as Text17, View as View19 } from "react-native";
4426
- var Permissions = ({
4427
- open,
4428
- onClose,
4429
- onSuccess
4430
- }) => {
4431
- const { appName, linkToken, setUserConsent } = useKryptosConnect();
4432
- const theme = useTheme();
4433
- const [isLoading, setIsLoading] = React37.useState(false);
4434
- const [errorMessage, setErrorMessage] = React37.useState("");
4435
- const handleConsentClick = async () => {
4436
- try {
4437
- setIsLoading(true);
4438
- setErrorMessage("");
4439
- const res = await giveUserConsent(linkToken);
4440
- setUserConsent(res);
4441
- onSuccess();
4442
- } catch (error) {
4443
- const err = error;
4444
- setErrorMessage(
4445
- err?.response?.data?.message || "Failed to give consent. Please try again."
4446
- );
4447
- console.error(error);
4448
- } finally {
4449
- setIsLoading(false);
4450
- }
4451
- };
4452
- const permissionItems = [
4453
- "View your crypto holdings, wallet balances, and portfolio data",
4454
- "Access your transaction history and trading activity",
4455
- "Keep this data updated and accessible when you're offline"
4456
- ];
4457
- return /* @__PURE__ */ React37.createElement(Modal, { isOpen: open, onClose, size: "xl" }, /* @__PURE__ */ React37.createElement(ModalHeader, { onClose }, "Permissions"), /* @__PURE__ */ React37.createElement(ModalBody, null, /* @__PURE__ */ React37.createElement(View19, { style: styles19.container }, /* @__PURE__ */ React37.createElement(ConnectLogo, null), errorMessage ? /* @__PURE__ */ React37.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React37.createElement(AlertDescription, null, errorMessage)) : null, /* @__PURE__ */ React37.createElement(View19, { style: styles19.permissionsList }, /* @__PURE__ */ React37.createElement(
4458
- Text17,
4459
- {
4460
- style: [styles19.subtitle, { color: theme.colors.textSecondary }]
4461
- },
4462
- "Allow ",
4463
- appName,
4464
- " to:"
4465
- ), permissionItems.map((item, index) => /* @__PURE__ */ React37.createElement(View19, { key: `permission-${index}`, style: styles19.permissionItem }, /* @__PURE__ */ React37.createElement(Text17, { style: [styles19.bullet, { color: theme.colors.primary }] }, index + 1, "."), /* @__PURE__ */ React37.createElement(
4466
- Text17,
4467
- {
4468
- style: [styles19.permissionText, { color: theme.colors.text }]
4469
- },
4470
- item
4471
- )))), /* @__PURE__ */ React37.createElement(
4472
- View19,
4473
- {
4474
- style: [
4475
- styles19.infoBox,
4476
- {
4477
- backgroundColor: theme.colors.surface,
4478
- borderColor: theme.colors.border
4479
- }
4480
- ]
4481
- },
4482
- /* @__PURE__ */ React37.createElement(
4483
- Text17,
4484
- {
4485
- style: [styles19.infoText, { color: theme.colors.textSecondary }]
4486
- },
4487
- "By selecting",
4488
- " ",
4489
- /* @__PURE__ */ React37.createElement(Text17, { style: { fontWeight: "600", color: theme.colors.text } }, "'Allow'"),
4490
- ", you agree to share this information and keep it updated."
4491
- )
4492
- ))), /* @__PURE__ */ React37.createElement(ModalFooter, { style: { paddingVertical: 8 } }, /* @__PURE__ */ React37.createElement(
4493
- Button,
4494
- {
4495
- variant: "outline",
4496
- size: "lg",
4497
- onPress: handleConsentClick,
4498
- loading: isLoading,
4499
- disabled: isLoading,
4500
- style: styles19.button
4501
- },
4502
- "Allow"
4503
- ), /* @__PURE__ */ React37.createElement(Footer, null)));
4504
- };
4505
- var styles19 = StyleSheet19.create({
4506
- container: {
4507
- flex: 1
4508
- },
4509
- permissionsList: {
4510
- marginBottom: 24
4511
- // theme.spacing.xxl
4512
- },
4513
- subtitle: {
4514
- fontSize: 14,
4515
- // theme.fontSize.md
4516
- fontWeight: "500",
4517
- marginBottom: 16
4518
- // theme.spacing.lg
4519
- },
4520
- permissionItem: {
4521
- flexDirection: "row",
4522
- marginBottom: 12,
4523
- // theme.spacing.md
4524
- paddingLeft: 4
4525
- // theme.spacing.xs
4526
- },
4527
- bullet: {
4528
- fontSize: 14,
4529
- // theme.fontSize.md
4530
- fontWeight: "600",
4531
- marginRight: 8,
4532
- // theme.spacing.sm
4533
- width: 20
4534
- // theme.spacing.xl
4535
- },
4536
- permissionText: {
4537
- fontSize: 14,
4538
- // theme.fontSize.md
4539
- flex: 1,
4540
- lineHeight: 20
4541
- // theme.spacing.xl
4542
- },
4543
- infoBox: {
4544
- padding: 16,
4545
- // theme.spacing.lg
4546
- borderRadius: 12,
4547
- // theme.borderRadius.md
4548
- borderWidth: 1,
4549
- marginBottom: 24
4550
- // theme.spacing.xxl
4551
- },
4552
- infoText: {
4553
- fontSize: 13,
4554
- lineHeight: 18,
4555
- textAlign: "center"
4556
- },
4557
- button: {
4558
- width: "100%"
4559
- }
4560
- });
4561
-
4562
- // src/molecules/StatusModal.tsx
4563
- import React38 from "react";
4564
- import { StyleSheet as StyleSheet20, Text as Text18, View as View20 } from "react-native";
4565
- var StatusModal = ({
4566
- open,
4567
- onClose,
4568
- status,
4569
- onSuccess,
4570
- onError
4571
- }) => {
4572
- const theme = useTheme();
4573
- const handleClose = () => {
4574
- if (status === "success") {
4575
- onSuccess();
4576
- } else {
4577
- onError();
4578
- }
4579
- onClose();
4580
- };
4581
- return /* @__PURE__ */ React38.createElement(Modal, { isOpen: open, onClose: handleClose, size: "xl" }, /* @__PURE__ */ React38.createElement(ModalHeader, { showCloseButton: false, onClose: handleClose }, ""), /* @__PURE__ */ React38.createElement(ModalBody, null, /* @__PURE__ */ React38.createElement(View20, { style: styles20.container }, /* @__PURE__ */ React38.createElement(View20, { style: styles20.iconContainer }, status === "success" ? /* @__PURE__ */ React38.createElement(SuccessIcon, { size: 80 }) : /* @__PURE__ */ React38.createElement(ErrorIcon, { size: 80 })), /* @__PURE__ */ React38.createElement(Text18, { style: [styles20.message, { color: theme.colors.text }] }, status === "success" ? "Connection successful" : "Connection failed"), /* @__PURE__ */ React38.createElement(Alert, null, /* @__PURE__ */ React38.createElement(AlertTitle, null, "Sync in Progress"), /* @__PURE__ */ React38.createElement(AlertDescription, null, "The syncing process might take a moment. Please wait, and your data will be updated soon.")))), /* @__PURE__ */ React38.createElement(ModalFooter, { style: { paddingVertical: 8 } }, /* @__PURE__ */ React38.createElement(
4582
- Button,
4583
- {
4584
- variant: "outline",
4585
- size: "lg",
4586
- onPress: status === "success" ? onSuccess : onError,
4587
- style: styles20.button
4588
- },
4589
- status === "success" ? "Continue" : "Try again later"
4590
- ), /* @__PURE__ */ React38.createElement(Footer, null)));
4591
- };
4592
- var styles20 = StyleSheet20.create({
4593
- container: {
4594
- flex: 1,
4595
- alignItems: "center",
4596
- paddingVertical: 24
4597
- // theme.spacing.xxl
4598
- },
4599
- iconContainer: {
4600
- marginBottom: 24
4601
- // theme.spacing.xxl
4602
- },
4603
- message: {
4604
- fontSize: 18,
4605
- // theme.fontSize.xl
4606
- fontWeight: "600",
4607
- marginBottom: 32,
4608
- // theme.spacing.xxxl
4609
- textAlign: "center"
4610
- },
4611
- button: {
4612
- width: "100%"
4613
- }
4614
- });
4615
-
4616
- // src/KryptosConnectButton.tsx
4617
- var KryptosConnectButton = ({
4618
- children,
4619
- onSuccess,
4620
- onError,
4621
- generateLinkToken,
4622
- style,
4623
- textStyle,
4624
- integrationName
4625
- }) => {
4626
- const { theme: themeName } = useKryptosConnect();
4627
- const [open, setOpen] = React39.useState(false);
4628
- const theme = useTheme();
4629
- return /* @__PURE__ */ React39.createElement(React39.Fragment, null, children ? /* @__PURE__ */ React39.createElement(TouchableOpacity9, { onPress: () => setOpen(true), style }, children) : /* @__PURE__ */ React39.createElement(
4630
- TouchableOpacity9,
4631
- {
4632
- onPress: () => setOpen(true),
4633
- style: [
4634
- styles21.defaultButton,
4635
- themeName === "light" ? {
4636
- backgroundColor: theme.colors.white,
4637
- borderRadius: theme.borderRadius.md,
4638
- borderWidth: 1,
4639
- borderColor: theme.colors.primary
4640
- } : {
4641
- backgroundColor: theme.colors.black,
4642
- borderRadius: theme.borderRadius.md
4643
- },
4644
- style
4645
- ],
4646
- activeOpacity: 0.8
4647
- },
4648
- /* @__PURE__ */ React39.createElement(
4649
- Text19,
4650
- {
4651
- style: [
4652
- styles21.buttonText,
4653
- {
4654
- color: themeName === "light" ? theme.colors.primary : theme.colors.white,
4655
- fontSize: theme.fontSize.lg
4656
- },
4657
- textStyle
4658
- ]
4659
- },
4660
- integrationName ? `Connect ${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)} Account` : "Connect With Kryptos"
4661
- )
4662
- ), open && /* @__PURE__ */ React39.createElement(
4663
- KryptosConnectModal,
4664
- {
4665
- open,
4666
- setOpen,
4667
- onSuccess,
4668
- onError,
4669
- generateLinkToken,
4670
- integrationName
4671
- }
4672
- ));
4673
- };
4674
- var KryptosConnectModal = ({
4675
- open,
4676
- setOpen,
4677
- onSuccess,
4678
- onError,
4679
- generateLinkToken,
4680
- integrationName
4681
- }) => {
4682
- const {
4683
- setIsInitialized,
4684
- userConsent,
4685
- setUserConsent,
4686
- setUser,
4687
- setEmail,
4688
- setLinkToken,
4689
- isAuthorized,
4690
- linkToken
4691
- } = useKryptosConnect();
4692
- const [step, setStep] = React39.useState("INIT" /* INIT */);
4693
- const [integrationDetails, setIntegrationDetails] = useState4(null);
4694
- useEffect5(() => {
4695
- if (integrationName && linkToken) {
4696
- const fetchIntegrationDetails = async () => {
4697
- const integrationDetails2 = await getSupportedProviders(
4698
- linkToken,
4699
- integrationName
4700
- );
4701
- setIntegrationDetails(integrationDetails2?.providers?.[0]);
4702
- };
4703
- fetchIntegrationDetails();
4704
- }
4705
- }, [integrationName, linkToken]);
4706
- const handleClose = () => {
4707
- setOpen(false);
4708
- setIsInitialized(false);
4709
- setStep("INIT" /* INIT */);
4710
- setUserConsent(null);
4711
- setUser(null);
4712
- setEmail("");
4713
- setLinkToken("");
4714
- };
4715
- const handleSuccess = () => {
4716
- onSuccess?.(userConsent);
4717
- handleClose();
4718
- };
4719
- const handleError = () => {
4720
- onError?.(userConsent);
4721
- handleClose();
4722
- };
4723
- const handleConsentClick = async () => {
4724
- try {
4725
- if (isAuthorized) {
4726
- setStep("END" /* END */);
4727
- return;
4728
- }
4729
- const res = await giveUserConsent(linkToken);
4730
- setUserConsent(res);
4731
- setStep("STATUS" /* STATUS */);
4732
- } catch (error) {
4733
- console.error(error);
4734
- }
4735
- };
4736
- const handleAbort = () => {
4737
- onError?.(new Error("User closed the modal"));
4738
- handleClose();
4739
- };
4740
- if (!open) return null;
4741
- return /* @__PURE__ */ React39.createElement(View21, { style: styles21.container }, step === "INIT" /* INIT */ && /* @__PURE__ */ React39.createElement(
4742
- Init,
4743
- {
4744
- open,
4745
- generateLinkToken,
4746
- onSuccess: (data) => {
4747
- if (data?.isAuthorized) {
4748
- setStep("INTEGRATION" /* INTEGRATION */);
4749
- } else {
4750
- setStep("AUTH" /* AUTH */);
4751
- }
4752
- },
4753
- onClose: handleAbort
4754
- }
4755
- ), step === "AUTH" /* AUTH */ && /* @__PURE__ */ React39.createElement(
4756
- Auth,
4757
- {
4758
- open,
4759
- onEmailSuccess: () => setStep("OTP" /* OTP */),
4760
- onGuestSuccess: () => setStep("INTEGRATION" /* INTEGRATION */),
4761
- onClose: handleAbort
4762
- }
4763
- ), step === "OTP" /* OTP */ && /* @__PURE__ */ React39.createElement(
4764
- OTPVerify,
4765
- {
4766
- open,
4767
- onSuccess: () => setStep("INTEGRATION" /* INTEGRATION */),
4768
- onClose: handleAbort
4769
- }
4770
- ), step === "INTEGRATION" /* INTEGRATION */ && /* @__PURE__ */ React39.createElement(
4771
- Integration,
4772
- {
4773
- open,
4774
- onSuccess: handleConsentClick,
4775
- onClose: handleAbort,
4776
- integrationDetails
4777
- }
4778
- ), step === "PERMISSIONS" /* PERMISSIONS */ && /* @__PURE__ */ React39.createElement(
4779
- Permissions,
4780
- {
4781
- open,
4782
- onClose: handleAbort,
4783
- onSuccess: () => setStep("STATUS" /* STATUS */)
4784
- }
4785
- ), step === "STATUS" /* STATUS */ && /* @__PURE__ */ React39.createElement(
4786
- StatusModal,
4787
- {
4788
- open,
4789
- onClose: handleAbort,
4790
- onSuccess: handleSuccess,
4791
- onError: handleError,
4792
- status: userConsent?.public_token ? "success" : "error"
4793
- }
4794
- ), step === "END" /* END */ && /* @__PURE__ */ React39.createElement(
4795
- EndModal,
4796
- {
4797
- open,
4798
- onClose: () => {
4799
- onSuccess?.(userConsent);
4800
- setStep("INIT" /* INIT */);
4801
- setOpen(false);
4802
- }
4803
- }
4804
- ));
4805
- };
4806
- var styles21 = StyleSheet21.create({
4807
- defaultButton: {
4808
- flexDirection: "row",
4809
- alignItems: "center",
4810
- justifyContent: "center",
4811
- paddingVertical: 14,
4812
- paddingHorizontal: 20,
4813
- minHeight: 48,
4814
- gap: 8
4815
- },
4816
- buttonText: {
4817
- fontWeight: "600"
4818
- },
4819
- container: {
4820
- position: "absolute",
4821
- top: 0,
4822
- left: 0,
4823
- right: 0,
4824
- bottom: 0
4825
- }
4826
- });
4827
- export {
4828
- KryptosConnectButton,
4829
- KryptosConnectModal,
4830
- KryptosConnectProvider,
4831
- useKryptosConnect
4832
- };