zopassport 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +407 -0
  3. package/app/.env.example +15 -0
  4. package/app/README.md +28 -0
  5. package/app/package.json +24 -0
  6. package/app/reanimated-mock.js +102 -0
  7. package/app/reanimated-mock.jsx +97 -0
  8. package/app/src/App.tsx +331 -0
  9. package/app/src/components/FounderBadge.tsx +26 -0
  10. package/app/src/components/OTPInput.tsx +149 -0
  11. package/app/src/components/PhoneInput.tsx +109 -0
  12. package/app/src/components/ZoAuth.tsx +320 -0
  13. package/app/src/components/ZoAvatar.tsx +87 -0
  14. package/app/src/components/ZoLanding.tsx +231 -0
  15. package/app/src/components/ZoOnboarding.tsx +524 -0
  16. package/app/src/components/ZoPassportCard.tsx +183 -0
  17. package/app/src/components/ZoProgressRing.tsx +57 -0
  18. package/app/src/components/index.ts +16 -0
  19. package/app/src/components/wallet/MovingShine.tsx +43 -0
  20. package/app/src/components/wallet/TransactionItem.tsx +84 -0
  21. package/app/src/components/wallet/TransactionList.tsx +65 -0
  22. package/app/src/components/wallet/WalletCard.tsx +152 -0
  23. package/app/src/components/wallet/WalletScreen.tsx +190 -0
  24. package/app/src/components/wallet/ZoToken.tsx +69 -0
  25. package/app/src/components/wallet/index.ts +8 -0
  26. package/app/src/components/wallet/styles/index.ts +4 -0
  27. package/app/src/components/wallet/styles/walletStyles.ts +210 -0
  28. package/app/src/sdk/ZoPassportSDK.ts +277 -0
  29. package/app/src/sdk/lib/api/auth.ts +223 -0
  30. package/app/src/sdk/lib/api/avatar.ts +155 -0
  31. package/app/src/sdk/lib/api/client.ts +135 -0
  32. package/app/src/sdk/lib/api/index.ts +8 -0
  33. package/app/src/sdk/lib/api/profile.ts +80 -0
  34. package/app/src/sdk/lib/api/wallet.ts +59 -0
  35. package/app/src/sdk/lib/types/auth.ts +78 -0
  36. package/app/src/sdk/lib/types/avatar.ts +22 -0
  37. package/app/src/sdk/lib/types/index.ts +8 -0
  38. package/app/src/sdk/lib/types/profile.ts +18 -0
  39. package/app/src/sdk/lib/types/wallet.ts +103 -0
  40. package/app/src/sdk/lib/types.ts +205 -0
  41. package/app/src/sdk/lib/utils/index.ts +6 -0
  42. package/app/src/sdk/lib/utils/phone.ts +71 -0
  43. package/app/src/sdk/lib/utils/storage.ts +116 -0
  44. package/app/src/sdk/lib/utils/wallet.ts +73 -0
  45. package/app/src/sdk/types.ts +205 -0
  46. package/app/src/styles.css +154 -0
  47. package/app/svg-mock.js +125 -0
  48. package/app/svg-mock.jsx +120 -0
  49. package/app/vite.config.ts +70 -0
  50. package/assets/ASSETS_MANIFEST.md +124 -0
  51. package/assets/bae.png +0 -0
  52. package/assets/bro.png +0 -0
  53. package/assets/cultural-stickers/Business.png +0 -0
  54. package/assets/cultural-stickers/Default (2).jpg +0 -0
  55. package/assets/cultural-stickers/Design.png +0 -0
  56. package/assets/cultural-stickers/FollowYourHeart.png +0 -0
  57. package/assets/cultural-stickers/Food.png +0 -0
  58. package/assets/cultural-stickers/Game.png +0 -0
  59. package/assets/cultural-stickers/Health&Fitness.png +0 -0
  60. package/assets/cultural-stickers/Home&Lifestyle.png +0 -0
  61. package/assets/cultural-stickers/Law.png +0 -0
  62. package/assets/cultural-stickers/Literature&Stories.png +0 -0
  63. package/assets/cultural-stickers/Music&Entertainment.png +0 -0
  64. package/assets/cultural-stickers/Nature&Wildlife.png +0 -0
  65. package/assets/cultural-stickers/Photography.png +0 -0
  66. package/assets/cultural-stickers/Science&Technology.png +0 -0
  67. package/assets/cultural-stickers/Spiritual.png +0 -0
  68. package/assets/cultural-stickers/Sport.png +0 -0
  69. package/assets/cultural-stickers/Stories&Journal.png +0 -0
  70. package/assets/cultural-stickers/Television&Cinema.png +0 -0
  71. package/assets/cultural-stickers/Travel&Adventure.png +0 -0
  72. package/assets/cultural-stickers/z.jpg (1).jpg +0 -0
  73. package/assets/figma-assets/landing-zo-logo.png +0 -0
  74. package/assets/images/rank1.jpeg +0 -0
  75. package/assets/index.ts +76 -0
  76. package/assets/lotties/loader.json +1216 -0
  77. package/assets/lotties/spinner.json +1 -0
  78. package/assets/videos/loading-screen-background.mp4 +0 -0
  79. package/assets/videos/opening-disks.mp4 +0 -0
  80. package/assets/wallet/constants.ts +38 -0
  81. package/assets/zo-coin.gif +0 -0
  82. package/assets/zo-fallback.png +0 -0
  83. package/dist/assets/index.d.mts +136 -0
  84. package/dist/assets/index.d.ts +136 -0
  85. package/dist/assets/index.js +133 -0
  86. package/dist/assets/index.js.map +1 -0
  87. package/dist/assets/index.mjs +100 -0
  88. package/dist/assets/index.mjs.map +1 -0
  89. package/dist/index.d.mts +789 -0
  90. package/dist/index.d.ts +789 -0
  91. package/dist/index.js +1118 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/index.mjs +1060 -0
  94. package/dist/index.mjs.map +1 -0
  95. package/dist/react-native.d.mts +537 -0
  96. package/dist/react-native.d.ts +537 -0
  97. package/dist/react-native.js +1617 -0
  98. package/dist/react-native.js.map +1 -0
  99. package/dist/react-native.mjs +1588 -0
  100. package/dist/react-native.mjs.map +1 -0
  101. package/dist/react.d.mts +824 -0
  102. package/dist/react.d.ts +824 -0
  103. package/dist/react.js +3856 -0
  104. package/dist/react.js.map +1 -0
  105. package/dist/react.mjs +3801 -0
  106. package/dist/react.mjs.map +1 -0
  107. package/package.json +112 -0
  108. package/scripts/init.js +196 -0
  109. package/scripts/postinstall.js +174 -0
  110. package/scripts/verify-build.js +121 -0
@@ -0,0 +1,1588 @@
1
+ // src/components/wallet/WalletScreen.tsx
2
+ import { useState, useCallback, useMemo, memo as memo5 } from "react";
3
+ import {
4
+ View as View6,
5
+ Text as Text4,
6
+ ScrollView as ScrollView2,
7
+ Pressable as Pressable2,
8
+ ActivityIndicator as ActivityIndicator2,
9
+ StyleSheet as StyleSheet7,
10
+ SafeAreaView
11
+ } from "react-native";
12
+ import Animated3, { FadeIn as FadeIn2, FadeOut, FadeInUp, FadeOutUp, FadeInDown } from "react-native-reanimated";
13
+
14
+ // src/components/wallet/WalletCard.tsx
15
+ import { memo as memo2, useEffect as useEffect2 } from "react";
16
+ import { View as View3, Text, Image as Image3, Pressable, StyleSheet as StyleSheet4 } from "react-native";
17
+ import Animated2, {
18
+ useSharedValue as useSharedValue2,
19
+ useAnimatedStyle as useAnimatedStyle2,
20
+ withTiming as withTiming2,
21
+ FadeIn
22
+ } from "react-native-reanimated";
23
+
24
+ // src/components/wallet/MovingShine.tsx
25
+ import { memo, useEffect } from "react";
26
+ import { Image, StyleSheet as StyleSheet2 } from "react-native";
27
+ import Animated, {
28
+ useSharedValue,
29
+ useAnimatedStyle,
30
+ withRepeat,
31
+ withTiming
32
+ } from "react-native-reanimated";
33
+
34
+ // assets/wallet/constants.ts
35
+ var WALLET_ASSETS = {
36
+ walletBackground: "https://proxy.cdn.zo.xyz/gallery/media/images/2e1fe74c-673c-4acd-a5aa-4ac13027dfb2_20250706072110.png",
37
+ walletCover: "https://proxy.cdn.zo.xyz/gallery/media/images/aeb1d508-c511-46a9-b4f8-260ea8825c6a_20250706072152.png",
38
+ shine: "https://proxy.cdn.zo.xyz/gallery/media/images/2a117a82-e399-4278-8eac-0d5b9209d150_20250706073538.png"
39
+ };
40
+ var WALLET_COLORS = {
41
+ background: "#111111",
42
+ cardBackground: "#222222",
43
+ cardInner: "rgba(255, 255, 255, 0.24)",
44
+ cardContent: "#111111",
45
+ cardBorder: "rgba(255, 255, 255, 0.16)",
46
+ textWhite: "#FFFFFF",
47
+ textGray: "rgba(255, 255, 255, 0.44)",
48
+ zoGreen: "#00C853",
49
+ shadowDark: "rgba(25, 25, 25, 1)"
50
+ };
51
+ var WALLET_DIMENSIONS = {
52
+ cardAspectRatio: 312 / 200,
53
+ coverAspectRatio: 312 / 120,
54
+ cardBorderRadius: 16,
55
+ innerBorderRadius: 12,
56
+ avatarSize: 32,
57
+ tokenSize: 16,
58
+ tokenVideoSize: 24
59
+ };
60
+ var ANIMATIONS = {
61
+ shineDuration: 1500,
62
+ cardTransition: 300,
63
+ fadeInDuration: 500
64
+ };
65
+
66
+ // src/components/wallet/styles/walletStyles.ts
67
+ import { StyleSheet } from "react-native";
68
+ var walletStyles = StyleSheet.create({
69
+ // Layout
70
+ flex: {
71
+ flex: 1
72
+ },
73
+ screen: {
74
+ flex: 1,
75
+ backgroundColor: WALLET_COLORS.background
76
+ },
77
+ container: {
78
+ flexDirection: "column-reverse"
79
+ },
80
+ // Header
81
+ header: {
82
+ width: "100%",
83
+ flexDirection: "row",
84
+ paddingVertical: 16,
85
+ paddingHorizontal: 24,
86
+ alignItems: "flex-start"
87
+ },
88
+ titleContainer: {
89
+ ...StyleSheet.absoluteFillObject,
90
+ alignItems: "center",
91
+ justifyContent: "center"
92
+ },
93
+ // Wallet Card
94
+ cardPressContainer: {
95
+ padding: 24,
96
+ paddingBottom: 0,
97
+ paddingTop: 8
98
+ },
99
+ card: {
100
+ aspectRatio: WALLET_DIMENSIONS.cardAspectRatio,
101
+ width: "100%",
102
+ borderRadius: WALLET_DIMENSIONS.cardBorderRadius,
103
+ backgroundColor: WALLET_COLORS.cardBackground
104
+ },
105
+ cardContainer: {
106
+ margin: 24,
107
+ flex: 1,
108
+ alignSelf: "stretch",
109
+ backgroundColor: WALLET_COLORS.cardInner,
110
+ borderRadius: WALLET_DIMENSIONS.innerBorderRadius,
111
+ paddingBottom: 4
112
+ },
113
+ cardContent: {
114
+ flex: 1,
115
+ padding: 16,
116
+ backgroundColor: WALLET_COLORS.cardContent,
117
+ borderRadius: WALLET_DIMENSIONS.innerBorderRadius,
118
+ borderWidth: 1,
119
+ borderColor: WALLET_COLORS.cardBorder
120
+ },
121
+ cardShadow: {
122
+ width: "80%",
123
+ height: 24,
124
+ backgroundColor: WALLET_COLORS.shadowDark,
125
+ position: "absolute",
126
+ top: "45%",
127
+ left: "10%",
128
+ shadowColor: "black",
129
+ shadowOffset: { width: 0, height: -10 },
130
+ shadowOpacity: 0.75,
131
+ shadowRadius: 16,
132
+ elevation: 5
133
+ },
134
+ // Balance
135
+ balanceRow: {
136
+ flexDirection: "row",
137
+ alignItems: "center",
138
+ justifyContent: "space-between"
139
+ },
140
+ balanceWrapper: {
141
+ flexDirection: "row",
142
+ alignItems: "baseline",
143
+ gap: 4
144
+ },
145
+ // User Info
146
+ avatarInfo: {
147
+ flexDirection: "row",
148
+ gap: 8
149
+ },
150
+ // Card Cover
151
+ cardCover: {
152
+ aspectRatio: WALLET_DIMENSIONS.coverAspectRatio,
153
+ width: "100%",
154
+ position: "absolute",
155
+ bottom: 0,
156
+ left: 0,
157
+ right: 0,
158
+ borderBottomLeftRadius: WALLET_DIMENSIONS.innerBorderRadius,
159
+ borderBottomRightRadius: WALLET_DIMENSIONS.innerBorderRadius,
160
+ overflow: "hidden"
161
+ },
162
+ cardCoverTextContainer: {
163
+ position: "absolute",
164
+ bottom: 0,
165
+ left: 0,
166
+ right: 0,
167
+ top: 0,
168
+ justifyContent: "center",
169
+ alignItems: "center"
170
+ },
171
+ // Shine Effect
172
+ shineContainer: {
173
+ ...StyleSheet.absoluteFillObject,
174
+ overflow: "hidden"
175
+ },
176
+ shineEffect: {
177
+ position: "absolute",
178
+ width: 150,
179
+ left: -60,
180
+ top: -140,
181
+ bottom: 0
182
+ },
183
+ // Text Styles
184
+ whiteText: {
185
+ color: WALLET_COLORS.textWhite
186
+ },
187
+ grayText: {
188
+ color: WALLET_COLORS.textGray
189
+ },
190
+ // Token
191
+ tokenVideo: {
192
+ width: WALLET_DIMENSIONS.tokenVideoSize,
193
+ height: WALLET_DIMENSIONS.tokenVideoSize,
194
+ borderRadius: WALLET_DIMENSIONS.tokenVideoSize,
195
+ overflow: "hidden"
196
+ },
197
+ token: {
198
+ width: WALLET_DIMENSIONS.tokenSize,
199
+ height: WALLET_DIMENSIONS.tokenSize
200
+ },
201
+ tokenContainer: {
202
+ flexDirection: "row",
203
+ gap: 4,
204
+ alignItems: "center",
205
+ justifyContent: "center"
206
+ },
207
+ textShadow: {
208
+ shadowRadius: 8,
209
+ shadowOpacity: 1
210
+ },
211
+ // Transactions
212
+ txnContent: {
213
+ alignSelf: "stretch",
214
+ paddingHorizontal: 24,
215
+ gap: 32,
216
+ paddingBottom: 24,
217
+ paddingTop: 40
218
+ },
219
+ txnRow: {
220
+ minHeight: 40,
221
+ flexDirection: "row",
222
+ gap: 12,
223
+ justifyContent: "space-between",
224
+ alignItems: "center"
225
+ },
226
+ iconBgTilted: {
227
+ transform: [{ rotate: "-45deg" }],
228
+ width: 40,
229
+ height: 40,
230
+ borderRadius: 20,
231
+ backgroundColor: "#202020",
232
+ alignItems: "center",
233
+ justifyContent: "center"
234
+ },
235
+ // States
236
+ loader: {
237
+ flex: 1,
238
+ alignItems: "center",
239
+ justifyContent: "center",
240
+ marginTop: 240
241
+ },
242
+ openBg: {
243
+ ...StyleSheet.absoluteFillObject,
244
+ backgroundColor: WALLET_COLORS.background,
245
+ opacity: 0.8
246
+ },
247
+ zoDescriptionContainer: {
248
+ position: "absolute",
249
+ bottom: 140
250
+ },
251
+ description: {
252
+ paddingHorizontal: 24,
253
+ color: WALLET_COLORS.textWhite
254
+ },
255
+ // Spacing
256
+ bar: {
257
+ height: 56
258
+ }
259
+ });
260
+
261
+ // src/components/wallet/MovingShine.tsx
262
+ import { jsx } from "react/jsx-runtime";
263
+ var MovingShine = memo(
264
+ ({ duration = ANIMATIONS.shineDuration }) => {
265
+ const tx = useSharedValue(-100);
266
+ const animatedStyle = useAnimatedStyle(() => ({
267
+ transform: [{ rotate: "30deg" }, { translateX: tx.value }]
268
+ }));
269
+ useEffect(() => {
270
+ tx.value = withRepeat(withTiming(420, { duration }), -1, false);
271
+ }, [duration]);
272
+ return /* @__PURE__ */ jsx(Animated.View, { style: [styles.shineEffect, animatedStyle], children: /* @__PURE__ */ jsx(
273
+ Image,
274
+ {
275
+ source: { uri: WALLET_ASSETS.shine },
276
+ style: StyleSheet2.absoluteFillObject,
277
+ resizeMode: "cover"
278
+ }
279
+ ) });
280
+ }
281
+ );
282
+ MovingShine.displayName = "MovingShine";
283
+ var styles = StyleSheet2.create({
284
+ shineEffect: walletStyles.shineEffect
285
+ });
286
+
287
+ // src/components/wallet/ZoToken.tsx
288
+ import { View as View2, Image as Image2, StyleSheet as StyleSheet3 } from "react-native";
289
+ import { jsx as jsx2 } from "react/jsx-runtime";
290
+ var ZO_COIN_GIF = "/zo-coin.gif";
291
+ var ZoToken = ({ size = 16, style, source = ZO_COIN_GIF }) => {
292
+ return /* @__PURE__ */ jsx2(View2, { style: [{ width: size, height: size }, style], children: /* @__PURE__ */ jsx2(
293
+ Image2,
294
+ {
295
+ source: { uri: source },
296
+ style: {
297
+ width: size,
298
+ height: size,
299
+ borderRadius: size / 2
300
+ },
301
+ resizeMode: "cover"
302
+ }
303
+ ) });
304
+ };
305
+ var ZoTokenVideo = ({ size = 24, style, source = ZO_COIN_GIF }) => {
306
+ return /* @__PURE__ */ jsx2(
307
+ View2,
308
+ {
309
+ style: [
310
+ {
311
+ width: size,
312
+ height: size,
313
+ borderRadius: size / 2,
314
+ overflow: "hidden"
315
+ },
316
+ style
317
+ ],
318
+ children: /* @__PURE__ */ jsx2(
319
+ Image2,
320
+ {
321
+ source: { uri: source },
322
+ style: {
323
+ width: size,
324
+ height: size
325
+ },
326
+ resizeMode: "cover"
327
+ }
328
+ )
329
+ }
330
+ );
331
+ };
332
+ var styles2 = StyleSheet3.create({
333
+ container: {
334
+ alignItems: "center",
335
+ justifyContent: "center"
336
+ }
337
+ });
338
+
339
+ // src/lib/utils/wallet.ts
340
+ var formatBalance = (balance) => {
341
+ if (balance === 0) return "0";
342
+ const formatted = balance.toLocaleString("en-US", {
343
+ minimumFractionDigits: 0,
344
+ maximumFractionDigits: 2
345
+ });
346
+ return formatted;
347
+ };
348
+ var formatWalletAddress = (address) => {
349
+ if (!address || address.length < 8) return address;
350
+ return `${address.slice(0, 4)}...${address.slice(-4)}`;
351
+ };
352
+ var formatNickname = (nickname) => {
353
+ if (!nickname) return "";
354
+ return nickname.startsWith("@") ? nickname : `@${nickname}`;
355
+ };
356
+ var getTransactionColor = (action) => {
357
+ return action === "spend" ? "#FF4444" : "#00C853";
358
+ };
359
+
360
+ // src/components/wallet/WalletCard.tsx
361
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
362
+ var WalletCard = memo2(
363
+ ({ balance, user, isOpen = false, onToggle, isLoading }) => {
364
+ const bgY = useSharedValue2(0);
365
+ const cardY = useSharedValue2(0);
366
+ const animatedBackgroundStyle = useAnimatedStyle2(() => ({
367
+ transform: [{ translateY: bgY.value }]
368
+ }));
369
+ const animatedCardStyle = useAnimatedStyle2(() => ({
370
+ transform: [{ translateY: cardY.value }]
371
+ }));
372
+ useEffect2(() => {
373
+ bgY.value = withTiming2(isOpen ? 200 : 0, { duration: ANIMATIONS.cardTransition });
374
+ cardY.value = withTiming2(isOpen ? -150 : 0, { duration: ANIMATIONS.cardTransition });
375
+ }, [isOpen]);
376
+ const displayName = user.nickname ? formatNickname(user.nickname) : user.first_name || "You";
377
+ const walletText = `${displayName}'s wallet`;
378
+ return /* @__PURE__ */ jsx3(
379
+ Pressable,
380
+ {
381
+ style: styles3.cardPressContainer,
382
+ onPress: onToggle,
383
+ children: /* @__PURE__ */ jsxs(Animated2.View, { style: [styles3.card, animatedBackgroundStyle], children: [
384
+ /* @__PURE__ */ jsx3(
385
+ Image3,
386
+ {
387
+ source: { uri: WALLET_ASSETS.walletBackground },
388
+ style: StyleSheet4.absoluteFillObject,
389
+ resizeMode: "contain"
390
+ }
391
+ ),
392
+ /* @__PURE__ */ jsx3(
393
+ Animated2.View,
394
+ {
395
+ style: styles3.cardShadow,
396
+ entering: FadeIn.duration(ANIMATIONS.fadeInDuration)
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsx3(Animated2.View, { style: [styles3.cardContainer, animatedCardStyle], children: /* @__PURE__ */ jsxs(View3, { style: styles3.cardContent, children: [
400
+ /* @__PURE__ */ jsxs(View3, { style: styles3.balanceRow, children: [
401
+ /* @__PURE__ */ jsxs(View3, { style: styles3.balanceWrapper, children: [
402
+ /* @__PURE__ */ jsx3(Text, { style: [styles3.whiteText, styles3.balanceAmount], children: formatBalance(balance) }),
403
+ /* @__PURE__ */ jsx3(Text, { style: [styles3.grayText, styles3.currency], children: "$Zo" })
404
+ ] }),
405
+ /* @__PURE__ */ jsx3(ZoTokenVideo, { size: 24 })
406
+ ] }),
407
+ /* @__PURE__ */ jsx3(View3, { style: styles3.flex }),
408
+ user && /* @__PURE__ */ jsxs(View3, { style: styles3.avatarInfo, children: [
409
+ user.avatar?.image && /* @__PURE__ */ jsx3(
410
+ Image3,
411
+ {
412
+ source: { uri: user.avatar.image },
413
+ style: styles3.avatar
414
+ }
415
+ ),
416
+ /* @__PURE__ */ jsxs(View3, { style: styles3.flex, children: [
417
+ /* @__PURE__ */ jsx3(Text, { style: [styles3.whiteText, styles3.userName], children: displayName }),
418
+ /* @__PURE__ */ jsx3(Text, { style: [styles3.grayText, styles3.walletAddress], children: formatWalletAddress(user.wallet_address || "") })
419
+ ] })
420
+ ] }),
421
+ /* @__PURE__ */ jsx3(View3, { style: styles3.shineContainer, children: /* @__PURE__ */ jsx3(MovingShine, {}) })
422
+ ] }) }),
423
+ /* @__PURE__ */ jsxs(View3, { style: styles3.cardCover, children: [
424
+ /* @__PURE__ */ jsx3(
425
+ Image3,
426
+ {
427
+ source: { uri: WALLET_ASSETS.walletCover },
428
+ style: StyleSheet4.absoluteFillObject,
429
+ resizeMode: "cover"
430
+ }
431
+ ),
432
+ /* @__PURE__ */ jsx3(View3, { style: styles3.cardCoverTextContainer, children: /* @__PURE__ */ jsx3(Text, { style: [styles3.grayText, styles3.cardCoverText], children: walletText }) })
433
+ ] })
434
+ ] })
435
+ }
436
+ );
437
+ }
438
+ );
439
+ WalletCard.displayName = "WalletCard";
440
+ var styles3 = StyleSheet4.create({
441
+ ...walletStyles,
442
+ balanceAmount: {
443
+ fontSize: 24,
444
+ fontWeight: "700"
445
+ },
446
+ currency: {
447
+ fontSize: 12
448
+ },
449
+ userName: {
450
+ fontSize: 16,
451
+ fontWeight: "600"
452
+ },
453
+ walletAddress: {
454
+ fontSize: 12
455
+ },
456
+ avatar: {
457
+ width: 32,
458
+ height: 32,
459
+ borderRadius: 16
460
+ },
461
+ cardCoverText: {
462
+ fontSize: 16,
463
+ fontWeight: "600"
464
+ }
465
+ });
466
+
467
+ // src/components/wallet/TransactionList.tsx
468
+ import { memo as memo4 } from "react";
469
+ import { View as View5, Text as Text3, ActivityIndicator, StyleSheet as StyleSheet6 } from "react-native";
470
+
471
+ // src/components/wallet/TransactionItem.tsx
472
+ import { memo as memo3 } from "react";
473
+ import { View as View4, Text as Text2, StyleSheet as StyleSheet5 } from "react-native";
474
+ import moment from "moment";
475
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
476
+ var StatusIcon = memo3(() => {
477
+ return /* @__PURE__ */ jsx4(View4, { style: styles4.iconBgTilted, children: /* @__PURE__ */ jsx4(Text2, { style: styles4.iconText, children: "\u2190" }) });
478
+ });
479
+ StatusIcon.displayName = "StatusIcon";
480
+ var TransactionItem = memo3(
481
+ ({ transaction, showDate = true }) => {
482
+ const color = getTransactionColor(transaction.action);
483
+ const formattedAmount = formatBalance(transaction.amount);
484
+ return /* @__PURE__ */ jsxs2(View4, { style: styles4.txnRow, children: [
485
+ /* @__PURE__ */ jsx4(StatusIcon, {}),
486
+ /* @__PURE__ */ jsxs2(View4, { style: styles4.flex, children: [
487
+ /* @__PURE__ */ jsx4(Text2, { style: styles4.description, children: transaction.description }),
488
+ showDate && transaction.claimed_at && /* @__PURE__ */ jsx4(Text2, { style: styles4.date, children: moment(transaction.claimed_at).format("DD MMM hh:mm A") })
489
+ ] }),
490
+ /* @__PURE__ */ jsxs2(View4, { style: styles4.tokenContainer, children: [
491
+ /* @__PURE__ */ jsxs2(
492
+ Text2,
493
+ {
494
+ style: [
495
+ styles4.textShadow,
496
+ {
497
+ shadowColor: color,
498
+ color,
499
+ fontWeight: "600",
500
+ fontSize: 16
501
+ }
502
+ ],
503
+ children: [
504
+ "+ ",
505
+ formattedAmount
506
+ ]
507
+ }
508
+ ),
509
+ /* @__PURE__ */ jsx4(ZoToken, { size: 16 })
510
+ ] })
511
+ ] });
512
+ }
513
+ );
514
+ TransactionItem.displayName = "TransactionItem";
515
+ var styles4 = StyleSheet5.create({
516
+ txnRow: walletStyles.txnRow,
517
+ iconBgTilted: walletStyles.iconBgTilted,
518
+ tokenContainer: walletStyles.tokenContainer,
519
+ textShadow: walletStyles.textShadow,
520
+ flex: {
521
+ flex: 1
522
+ },
523
+ iconText: {
524
+ color: "rgba(255, 255, 255, 0.6)",
525
+ fontSize: 16
526
+ },
527
+ description: {
528
+ color: "#FFFFFF",
529
+ fontSize: 16,
530
+ fontWeight: "500"
531
+ },
532
+ date: {
533
+ color: "rgba(255, 255, 255, 0.44)",
534
+ fontSize: 12,
535
+ marginTop: 2
536
+ }
537
+ });
538
+
539
+ // src/components/wallet/TransactionList.tsx
540
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
541
+ var TransactionList = memo4(
542
+ ({ transactions, isLoading, onEndReached }) => {
543
+ if (isLoading && !transactions?.length) {
544
+ return /* @__PURE__ */ jsx5(View5, { style: styles5.loader, children: /* @__PURE__ */ jsx5(ActivityIndicator, { size: "large", color: "#00C853" }) });
545
+ }
546
+ if (!transactions?.length) {
547
+ return /* @__PURE__ */ jsxs3(View5, { style: styles5.emptyState, children: [
548
+ /* @__PURE__ */ jsx5(Text3, { style: styles5.emptyText, children: "No transactions yet" }),
549
+ /* @__PURE__ */ jsx5(Text3, { style: styles5.emptySubtext, children: "Complete quests to earn $Zo tokens" })
550
+ ] });
551
+ }
552
+ return /* @__PURE__ */ jsx5(View5, { style: styles5.txnContent, children: transactions.map((transaction, index) => /* @__PURE__ */ jsx5(TransactionItem, { transaction }, transaction.id || index)) });
553
+ }
554
+ );
555
+ TransactionList.displayName = "TransactionList";
556
+ var styles5 = StyleSheet6.create({
557
+ txnContent: walletStyles.txnContent,
558
+ loader: {
559
+ padding: 40,
560
+ alignItems: "center",
561
+ justifyContent: "center"
562
+ },
563
+ emptyState: {
564
+ padding: 40,
565
+ alignItems: "center",
566
+ justifyContent: "center"
567
+ },
568
+ emptyText: {
569
+ color: "#FFFFFF",
570
+ fontSize: 18,
571
+ fontWeight: "600",
572
+ marginBottom: 8
573
+ },
574
+ emptySubtext: {
575
+ color: "rgba(255, 255, 255, 0.44)",
576
+ fontSize: 14,
577
+ textAlign: "center"
578
+ }
579
+ });
580
+
581
+ // src/components/wallet/WalletScreen.tsx
582
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
583
+ var WalletScreen = memo5(({
584
+ user,
585
+ balance,
586
+ transactions,
587
+ isLoading: isLoadingProp = false,
588
+ onBack
589
+ }) => {
590
+ const [isOpen, setIsOpen] = useState(false);
591
+ const [isTitleVisible, setIsTitleVisible] = useState(false);
592
+ const isLoading = isLoadingProp;
593
+ const toggleView = useCallback(() => {
594
+ setIsOpen((prev) => !prev);
595
+ }, []);
596
+ const handleScroll = useCallback((event) => {
597
+ const scrollY = event.nativeEvent.contentOffset.y;
598
+ setIsTitleVisible(scrollY > 200);
599
+ }, []);
600
+ const backdrop = useMemo(
601
+ () => isOpen ? /* @__PURE__ */ jsx6(Pressable2, { onPress: toggleView, style: styles6.openBg, children: /* @__PURE__ */ jsx6(View6, {}) }) : null,
602
+ [isOpen, toggleView]
603
+ );
604
+ const description = useMemo(
605
+ () => isOpen ? /* @__PURE__ */ jsx6(Animated3.View, { entering: FadeIn2, exiting: FadeOut, style: styles6.zoDescriptionContainer, children: /* @__PURE__ */ jsx6(Text4, { style: styles6.description, children: "Get Zo World coins as airdrop by completing quests, stay & trips." }) }) : null,
606
+ [isOpen]
607
+ );
608
+ const walletText = `${user.nickname || user.first_name}'s wallet`;
609
+ return /* @__PURE__ */ jsxs4(View6, { style: styles6.screen, children: [
610
+ /* @__PURE__ */ jsxs4(View6, { style: styles6.header, children: [
611
+ /* @__PURE__ */ jsx6(Pressable2, { onPress: onBack, children: /* @__PURE__ */ jsx6(Text4, { style: styles6.backButton, children: "\u2190" }) }),
612
+ /* @__PURE__ */ jsx6(Animated3.View, { pointerEvents: "none", style: styles6.titleContainer, children: isTitleVisible && /* @__PURE__ */ jsx6(Animated3.View, { entering: FadeInUp, exiting: FadeOutUp, children: /* @__PURE__ */ jsx6(Text4, { style: [styles6.whiteText, styles6.headerTitle], children: walletText }) }) })
613
+ ] }),
614
+ /* @__PURE__ */ jsx6(
615
+ ScrollView2,
616
+ {
617
+ contentContainerStyle: styles6.container,
618
+ onScroll: handleScroll,
619
+ scrollEventThrottle: 16,
620
+ showsVerticalScrollIndicator: false,
621
+ children: isLoading ? /* @__PURE__ */ jsx6(Animated3.View, { entering: FadeIn2, exiting: FadeOut, style: styles6.loader, children: /* @__PURE__ */ jsx6(ActivityIndicator2, { size: "large", color: "#00C853" }) }, "loader") : /* @__PURE__ */ jsxs4(Animated3.View, { entering: FadeInDown, style: styles6.container, children: [
622
+ /* @__PURE__ */ jsx6(SafeAreaView, {}),
623
+ /* @__PURE__ */ jsx6(TransactionList, { transactions, isLoading: false }),
624
+ backdrop,
625
+ /* @__PURE__ */ jsx6(
626
+ WalletCard,
627
+ {
628
+ balance,
629
+ user,
630
+ isOpen,
631
+ onToggle: toggleView,
632
+ isLoading
633
+ }
634
+ ),
635
+ /* @__PURE__ */ jsx6(View6, { style: styles6.bar }),
636
+ /* @__PURE__ */ jsx6(SafeAreaView, {})
637
+ ] })
638
+ }
639
+ ),
640
+ description
641
+ ] });
642
+ });
643
+ WalletScreen.displayName = "WalletScreen";
644
+ var styles6 = StyleSheet7.create({
645
+ ...walletStyles,
646
+ backButton: {
647
+ color: "#FFFFFF",
648
+ fontSize: 24,
649
+ fontWeight: "600"
650
+ },
651
+ headerTitle: {
652
+ fontSize: 18,
653
+ fontWeight: "600"
654
+ }
655
+ });
656
+
657
+ // src/lib/api/client.ts
658
+ import axios from "axios";
659
+
660
+ // src/lib/utils/logger.ts
661
+ var LOG_LEVELS = {
662
+ debug: 0,
663
+ info: 1,
664
+ warn: 2,
665
+ error: 3,
666
+ none: 4
667
+ };
668
+ var Logger = class {
669
+ constructor() {
670
+ this.config = {
671
+ enabled: false,
672
+ level: "warn",
673
+ prefix: "[ZoPassport]"
674
+ };
675
+ }
676
+ /**
677
+ * Configure the logger
678
+ * @param options - Logger configuration
679
+ */
680
+ configure(options) {
681
+ this.config = { ...this.config, ...options };
682
+ }
683
+ /**
684
+ * Enable debug logging
685
+ */
686
+ enable() {
687
+ this.config.enabled = true;
688
+ }
689
+ /**
690
+ * Disable all logging
691
+ */
692
+ disable() {
693
+ this.config.enabled = false;
694
+ }
695
+ /**
696
+ * Set log level
697
+ */
698
+ setLevel(level) {
699
+ this.config.level = level;
700
+ }
701
+ shouldLog(level) {
702
+ if (!this.config.enabled) return false;
703
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.config.level];
704
+ }
705
+ debug(...args) {
706
+ if (this.shouldLog("debug")) {
707
+ console.log(this.config.prefix, ...args);
708
+ }
709
+ }
710
+ info(...args) {
711
+ if (this.shouldLog("info")) {
712
+ console.info(this.config.prefix, ...args);
713
+ }
714
+ }
715
+ warn(...args) {
716
+ if (this.shouldLog("warn")) {
717
+ console.warn(this.config.prefix, ...args);
718
+ }
719
+ }
720
+ error(...args) {
721
+ if (this.shouldLog("error")) {
722
+ console.error(this.config.prefix, ...args);
723
+ }
724
+ }
725
+ };
726
+ var logger = new Logger();
727
+
728
+ // src/lib/utils/storage.ts
729
+ var STORAGE_KEYS = {
730
+ ACCESS_TOKEN: "zo_access_token",
731
+ REFRESH_TOKEN: "zo_refresh_token",
732
+ TOKEN_EXPIRY: "zo_token_expiry",
733
+ REFRESH_EXPIRY: "zo_refresh_expiry",
734
+ USER: "zo_user",
735
+ CLIENT_DEVICE_ID: "zo_device_id",
736
+ CLIENT_DEVICE_SECRET: "zo_device_secret",
737
+ AVATAR_URL: "zo_avatar_url",
738
+ NICKNAME: "zo_nickname",
739
+ CITY: "zo_city",
740
+ BODY_TYPE: "zo_body_type"
741
+ };
742
+ var LocalStorageAdapter = class {
743
+ async getItem(key) {
744
+ if (typeof window === "undefined") return null;
745
+ try {
746
+ return localStorage.getItem(key);
747
+ } catch (error) {
748
+ logger.warn(`LocalStorage.getItem failed for key "${key}":`, error);
749
+ return null;
750
+ }
751
+ }
752
+ async setItem(key, value) {
753
+ if (typeof window === "undefined") return;
754
+ try {
755
+ localStorage.setItem(key, value);
756
+ } catch (error) {
757
+ logger.warn(`LocalStorage.setItem failed for key "${key}":`, error);
758
+ }
759
+ }
760
+ async removeItem(key) {
761
+ if (typeof window === "undefined") return;
762
+ try {
763
+ localStorage.removeItem(key);
764
+ } catch (error) {
765
+ logger.warn(`LocalStorage.removeItem failed for key "${key}":`, error);
766
+ }
767
+ }
768
+ };
769
+ var AsyncStorageAdapter = class {
770
+ constructor(asyncStorage) {
771
+ this.storage = asyncStorage;
772
+ }
773
+ async getItem(key) {
774
+ try {
775
+ return await this.storage.getItem(key);
776
+ } catch (error) {
777
+ logger.warn(`AsyncStorage.getItem failed for key "${key}":`, error);
778
+ return null;
779
+ }
780
+ }
781
+ async setItem(key, value) {
782
+ try {
783
+ await this.storage.setItem(key, value);
784
+ } catch (error) {
785
+ logger.warn(`AsyncStorage.setItem failed for key "${key}":`, error);
786
+ }
787
+ }
788
+ async removeItem(key) {
789
+ try {
790
+ await this.storage.removeItem(key);
791
+ } catch (error) {
792
+ logger.warn(`AsyncStorage.removeItem failed for key "${key}":`, error);
793
+ }
794
+ }
795
+ };
796
+ var MemoryStorageAdapter = class {
797
+ constructor() {
798
+ this.store = /* @__PURE__ */ new Map();
799
+ }
800
+ async getItem(key) {
801
+ return this.store.get(key) || null;
802
+ }
803
+ async setItem(key, value) {
804
+ this.store.set(key, value);
805
+ }
806
+ async removeItem(key) {
807
+ this.store.delete(key);
808
+ }
809
+ /** Clear all stored data (useful for testing) */
810
+ clear() {
811
+ this.store.clear();
812
+ }
813
+ };
814
+
815
+ // src/lib/api/client.ts
816
+ function generateDeviceCredentials() {
817
+ const deviceId = `web-${Date.now()}-${Math.random().toString(36).substring(7)}`;
818
+ const deviceSecret = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
819
+ return { deviceId, deviceSecret };
820
+ }
821
+ var ZoApiClient = class {
822
+ constructor(config) {
823
+ this.config = config;
824
+ this.storage = config.storageAdapter || new LocalStorageAdapter();
825
+ this.client = axios.create({
826
+ baseURL: config.baseUrl || "https://api.io.zo.xyz",
827
+ timeout: config.timeout || 1e4,
828
+ headers: {
829
+ "Content-Type": "application/json",
830
+ "Accept": "application/json"
831
+ }
832
+ });
833
+ this.setupInterceptors();
834
+ }
835
+ async setupInterceptors() {
836
+ this.client.interceptors.request.use(async (config) => {
837
+ config.headers["client-key"] = this.config.clientKey;
838
+ const credentials = await this.getOrCreateDeviceCredentials();
839
+ config.headers["client-device-id"] = credentials.deviceId;
840
+ config.headers["client-device-secret"] = credentials.deviceSecret;
841
+ const token = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
842
+ if (token) {
843
+ config.headers["Authorization"] = `Bearer ${token}`;
844
+ }
845
+ return config;
846
+ });
847
+ this.client.interceptors.response.use(
848
+ (response) => response,
849
+ async (error) => {
850
+ const originalRequest = error.config;
851
+ if (error.response?.status === 401 && !originalRequest._retry) {
852
+ originalRequest._retry = true;
853
+ const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
854
+ if (refreshToken) {
855
+ try {
856
+ const response = await this.client.post("/api/v1/auth/token/refresh/", {
857
+ refresh_token: refreshToken
858
+ });
859
+ if (response.data?.access) {
860
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.data.access);
861
+ if (response.data.refresh) {
862
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, response.data.refresh);
863
+ }
864
+ originalRequest.headers["Authorization"] = `Bearer ${response.data.access}`;
865
+ return this.client(originalRequest);
866
+ }
867
+ } catch (refreshError) {
868
+ await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
869
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
870
+ }
871
+ }
872
+ }
873
+ return Promise.reject(error);
874
+ }
875
+ );
876
+ }
877
+ async getOrCreateDeviceCredentials() {
878
+ const storedId = await this.storage.getItem(STORAGE_KEYS.CLIENT_DEVICE_ID);
879
+ const storedSecret = await this.storage.getItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET);
880
+ if (storedId && storedSecret) {
881
+ return { deviceId: storedId, deviceSecret: storedSecret };
882
+ }
883
+ const credentials = generateDeviceCredentials();
884
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_ID, credentials.deviceId);
885
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET, credentials.deviceSecret);
886
+ return credentials;
887
+ }
888
+ get axiosInstance() {
889
+ return this.client;
890
+ }
891
+ getStorage() {
892
+ return this.storage;
893
+ }
894
+ };
895
+
896
+ // src/lib/api/auth.ts
897
+ var ZoAuth = class {
898
+ constructor(client) {
899
+ this.client = client;
900
+ }
901
+ /**
902
+ * Send OTP to phone number
903
+ * Step 1 of ZO phone authentication
904
+ */
905
+ async sendOTP(countryCode, phoneNumber) {
906
+ try {
907
+ const payload = {
908
+ mobile_country_code: countryCode,
909
+ mobile_number: phoneNumber,
910
+ message_channel: ""
911
+ // Empty string as per ZO API spec
912
+ };
913
+ const response = await this.client.axiosInstance.post(
914
+ "/api/v1/auth/login/mobile/otp/",
915
+ payload
916
+ );
917
+ if (response.status >= 200 && response.status < 300) {
918
+ return {
919
+ success: true,
920
+ message: response.data?.message || "OTP sent successfully"
921
+ };
922
+ }
923
+ return {
924
+ success: false,
925
+ message: response.data?.message || `Unexpected status: ${response.status}`
926
+ };
927
+ } catch (error) {
928
+ const errorData = error.response?.data;
929
+ const errorMessage = errorData?.detail || errorData?.message || errorData?.error || error.message || "Failed to send OTP";
930
+ return {
931
+ success: false,
932
+ message: errorMessage
933
+ };
934
+ }
935
+ }
936
+ /**
937
+ * Verify OTP and authenticate user
938
+ * Step 2 of ZO phone authentication
939
+ * Returns full auth response with tokens and user profile
940
+ */
941
+ async verifyOTP(countryCode, phoneNumber, otp) {
942
+ try {
943
+ const payload = {
944
+ mobile_country_code: countryCode,
945
+ mobile_number: phoneNumber,
946
+ otp
947
+ };
948
+ const response = await this.client.axiosInstance.post(
949
+ "/api/v1/auth/login/mobile/",
950
+ payload
951
+ );
952
+ let responseData;
953
+ if (typeof response.data === "string") {
954
+ try {
955
+ responseData = JSON.parse(response.data);
956
+ } catch {
957
+ return {
958
+ success: false,
959
+ error: "Invalid response format from authentication service"
960
+ };
961
+ }
962
+ } else {
963
+ responseData = response.data;
964
+ }
965
+ if (!responseData || !responseData.user || !responseData.access_token) {
966
+ return {
967
+ success: false,
968
+ error: "Invalid response structure from authentication service"
969
+ };
970
+ }
971
+ return {
972
+ success: true,
973
+ data: responseData
974
+ };
975
+ } catch (error) {
976
+ const errorMessage = this.extractErrorMessage(error);
977
+ return {
978
+ success: false,
979
+ error: errorMessage
980
+ };
981
+ }
982
+ }
983
+ /**
984
+ * Refresh access token using refresh token
985
+ */
986
+ async refreshAccessToken(refreshToken) {
987
+ try {
988
+ const response = await this.client.axiosInstance.post("/api/v1/auth/token/refresh/", {
989
+ refresh_token: refreshToken
990
+ });
991
+ return {
992
+ success: true,
993
+ tokens: response.data
994
+ };
995
+ } catch (error) {
996
+ return {
997
+ success: false,
998
+ error: "Failed to refresh authentication"
999
+ };
1000
+ }
1001
+ }
1002
+ /**
1003
+ * Check if user is authenticated
1004
+ */
1005
+ async checkLoginStatus(accessToken) {
1006
+ try {
1007
+ const response = await this.client.axiosInstance.get("/api/v1/auth/login/check/", {
1008
+ headers: {
1009
+ Authorization: `Bearer ${accessToken}`
1010
+ }
1011
+ });
1012
+ return {
1013
+ success: true,
1014
+ isAuthenticated: response.data.authenticated === true
1015
+ };
1016
+ } catch {
1017
+ return {
1018
+ success: false,
1019
+ isAuthenticated: false
1020
+ };
1021
+ }
1022
+ }
1023
+ /**
1024
+ * Extract error message from various ZO API error formats
1025
+ */
1026
+ extractErrorMessage(error) {
1027
+ const errorData = error.response?.data;
1028
+ if (errorData) {
1029
+ if (errorData.errors && Array.isArray(errorData.errors)) {
1030
+ return errorData.errors[0] || "Invalid OTP";
1031
+ }
1032
+ if (errorData.detail) return errorData.detail;
1033
+ if (errorData.message) return errorData.message;
1034
+ if (errorData.error) return errorData.error;
1035
+ }
1036
+ return "Authentication failed";
1037
+ }
1038
+ };
1039
+
1040
+ // src/lib/api/profile.ts
1041
+ var ZoProfile = class {
1042
+ constructor(client) {
1043
+ this.client = client;
1044
+ }
1045
+ /**
1046
+ * Get user profile
1047
+ */
1048
+ async getProfile(accessToken) {
1049
+ try {
1050
+ const response = await this.client.axiosInstance.get(
1051
+ "/api/v1/profile/me/",
1052
+ {
1053
+ headers: {
1054
+ Authorization: `Bearer ${accessToken}`
1055
+ }
1056
+ }
1057
+ );
1058
+ return {
1059
+ success: true,
1060
+ profile: response.data
1061
+ };
1062
+ } catch (error) {
1063
+ const errorData = error.response?.data;
1064
+ return {
1065
+ success: false,
1066
+ error: errorData?.detail || errorData?.message || "Failed to fetch profile"
1067
+ };
1068
+ }
1069
+ }
1070
+ /**
1071
+ * Update user profile (partial updates supported)
1072
+ */
1073
+ async updateProfile(accessToken, updates) {
1074
+ try {
1075
+ const response = await this.client.axiosInstance.post(
1076
+ "/api/v1/profile/me/",
1077
+ updates,
1078
+ {
1079
+ headers: {
1080
+ Authorization: `Bearer ${accessToken}`
1081
+ }
1082
+ }
1083
+ );
1084
+ return {
1085
+ success: true,
1086
+ profile: response.data
1087
+ };
1088
+ } catch (error) {
1089
+ const errorData = error.response?.data;
1090
+ return {
1091
+ success: false,
1092
+ error: errorData?.detail || errorData?.message || "Failed to update profile"
1093
+ };
1094
+ }
1095
+ }
1096
+ };
1097
+
1098
+ // src/lib/api/avatar.ts
1099
+ var ZoAvatar = class {
1100
+ constructor(client) {
1101
+ this.client = client;
1102
+ }
1103
+ /**
1104
+ * Generate avatar for user
1105
+ */
1106
+ async generateAvatar(accessToken, bodyType) {
1107
+ try {
1108
+ const payload = {
1109
+ body_type: bodyType
1110
+ };
1111
+ const response = await this.client.axiosInstance.post(
1112
+ "/api/v1/avatar/generate/",
1113
+ payload,
1114
+ {
1115
+ headers: {
1116
+ Authorization: `Bearer ${accessToken}`
1117
+ }
1118
+ }
1119
+ );
1120
+ return {
1121
+ success: true,
1122
+ task_id: response.data.task_id,
1123
+ status: response.data.status
1124
+ };
1125
+ } catch (error) {
1126
+ const errorData = error.response?.data;
1127
+ return {
1128
+ success: false,
1129
+ error: errorData?.detail || errorData?.message || "Failed to generate avatar"
1130
+ };
1131
+ }
1132
+ }
1133
+ /**
1134
+ * Check avatar generation status
1135
+ */
1136
+ async getAvatarStatus(accessToken, taskId) {
1137
+ try {
1138
+ const response = await this.client.axiosInstance.get(
1139
+ `/api/v1/avatar/status/${taskId}/`,
1140
+ {
1141
+ headers: {
1142
+ Authorization: `Bearer ${accessToken}`
1143
+ }
1144
+ }
1145
+ );
1146
+ return {
1147
+ success: true,
1148
+ status: response.data.status,
1149
+ avatarUrl: response.data.result?.avatar_url
1150
+ };
1151
+ } catch (error) {
1152
+ const errorData = error.response?.data;
1153
+ return {
1154
+ success: false,
1155
+ error: errorData?.detail || errorData?.message || "Failed to get avatar status"
1156
+ };
1157
+ }
1158
+ }
1159
+ /**
1160
+ * Poll avatar status until completion
1161
+ */
1162
+ async pollAvatarStatus(accessToken, taskId, options = {}) {
1163
+ const {
1164
+ onProgress,
1165
+ onComplete,
1166
+ onError,
1167
+ maxAttempts = 30,
1168
+ interval = 2e3
1169
+ } = options;
1170
+ let attempts = 0;
1171
+ const poll = async () => {
1172
+ attempts++;
1173
+ if (attempts > maxAttempts) {
1174
+ const timeoutError = "Avatar generation timed out";
1175
+ onError?.(timeoutError);
1176
+ return;
1177
+ }
1178
+ const result = await this.getAvatarStatus(accessToken, taskId);
1179
+ if (!result.success) {
1180
+ onError?.(result.error || "Unknown error");
1181
+ return;
1182
+ }
1183
+ onProgress?.(result.status || "unknown");
1184
+ if (result.status === "completed" && result.avatarUrl) {
1185
+ onComplete?.(result.avatarUrl);
1186
+ return;
1187
+ }
1188
+ if (result.status === "failed") {
1189
+ onError?.("Avatar generation failed");
1190
+ return;
1191
+ }
1192
+ setTimeout(poll, interval);
1193
+ };
1194
+ poll();
1195
+ }
1196
+ };
1197
+
1198
+ // src/lib/api/wallet.ts
1199
+ var ZO_TOKEN_CONFIG = {
1200
+ base: {
1201
+ rpc: "https://mainnet.base.org",
1202
+ contractAddress: "0x111142c7ecaf39797b7865b82034269962142069",
1203
+ // $Zo token on Base
1204
+ decimals: 18
1205
+ },
1206
+ avalanche: {
1207
+ rpc: "https://api.avax.network/ext/bc/C/rpc",
1208
+ contractAddress: "0x111142c7ecaf39797b7865b82034269962142069",
1209
+ // $Zo token on Avalanche (update if different)
1210
+ decimals: 18
1211
+ }
1212
+ };
1213
+ var ERC20_BALANCE_ABI = "0x70a08231";
1214
+ var ZoWallet = class {
1215
+ constructor(client) {
1216
+ this.cachedBalance = 0;
1217
+ this.userWalletAddress = null;
1218
+ this.network = "base";
1219
+ this.client = client;
1220
+ }
1221
+ /**
1222
+ * Set the user's wallet address for on-chain queries
1223
+ */
1224
+ setWalletAddress(address, network = "base") {
1225
+ this.userWalletAddress = address;
1226
+ this.network = network;
1227
+ logger.debug(`Wallet address set: ${address} on ${network}`);
1228
+ }
1229
+ /**
1230
+ * Get wallet balance - tries on-chain first, then API fallback
1231
+ * @returns Wallet balance amount
1232
+ */
1233
+ async getBalance() {
1234
+ if (this.userWalletAddress) {
1235
+ try {
1236
+ const onChainBalance = await this.getOnChainBalance();
1237
+ if (onChainBalance !== null) {
1238
+ this.cachedBalance = onChainBalance;
1239
+ return onChainBalance;
1240
+ }
1241
+ } catch (error) {
1242
+ logger.warn("On-chain balance check failed, falling back to API:", error);
1243
+ }
1244
+ }
1245
+ const apiBalance = await this.getBalanceFromAPI();
1246
+ if (apiBalance !== null) {
1247
+ return apiBalance;
1248
+ }
1249
+ logger.debug("Returning cached/default balance:", this.cachedBalance);
1250
+ return this.cachedBalance;
1251
+ }
1252
+ /**
1253
+ * Fetch balance directly from blockchain via JSON-RPC
1254
+ */
1255
+ async getOnChainBalance() {
1256
+ if (!this.userWalletAddress) {
1257
+ logger.warn("No wallet address set for on-chain query");
1258
+ return null;
1259
+ }
1260
+ const config = ZO_TOKEN_CONFIG[this.network];
1261
+ try {
1262
+ const paddedAddress = this.userWalletAddress.toLowerCase().replace("0x", "").padStart(64, "0");
1263
+ const data = ERC20_BALANCE_ABI + paddedAddress;
1264
+ const response = await fetch(config.rpc, {
1265
+ method: "POST",
1266
+ headers: { "Content-Type": "application/json" },
1267
+ body: JSON.stringify({
1268
+ jsonrpc: "2.0",
1269
+ id: 1,
1270
+ method: "eth_call",
1271
+ params: [
1272
+ {
1273
+ to: config.contractAddress,
1274
+ data
1275
+ },
1276
+ "latest"
1277
+ ]
1278
+ })
1279
+ });
1280
+ const result = await response.json();
1281
+ if (result.error) {
1282
+ logger.warn("RPC error:", result.error);
1283
+ return null;
1284
+ }
1285
+ const rawBalance = BigInt(result.result || "0x0");
1286
+ const balance = Number(rawBalance) / Math.pow(10, config.decimals);
1287
+ logger.debug(`On-chain balance fetched: ${balance} $Zo`);
1288
+ return balance;
1289
+ } catch (error) {
1290
+ logger.warn("Failed to fetch on-chain balance:", error);
1291
+ return null;
1292
+ }
1293
+ }
1294
+ /**
1295
+ * Fetch balance from Zo API endpoints (tries multiple endpoints with fallback)
1296
+ */
1297
+ async getBalanceFromAPI() {
1298
+ const endpoints = [
1299
+ "/api/v1/web3/token/airdrops/summary",
1300
+ "/api/v1/wallet/balance",
1301
+ "/api/v1/profile/wallet"
1302
+ ];
1303
+ const errors = [];
1304
+ for (const endpoint of endpoints) {
1305
+ try {
1306
+ const response = await this.client.axiosInstance.get(endpoint);
1307
+ const balance = response.data?.data?.total_amount ?? response.data?.balance ?? response.data?.total_amount;
1308
+ if (typeof balance === "number") {
1309
+ logger.debug(`Balance fetched from API ${endpoint}:`, balance);
1310
+ this.cachedBalance = balance;
1311
+ return balance;
1312
+ }
1313
+ } catch (error) {
1314
+ if (error?.response?.status !== 404) {
1315
+ errors.push({
1316
+ endpoint,
1317
+ status: error?.response?.status,
1318
+ message: error?.message || "Unknown error"
1319
+ });
1320
+ }
1321
+ }
1322
+ }
1323
+ if (errors.length > 0) {
1324
+ logger.warn("All balance API endpoints failed:", errors);
1325
+ }
1326
+ return null;
1327
+ }
1328
+ /**
1329
+ * Get transaction history
1330
+ * @param page - Optional page number for pagination
1331
+ * @returns Array of transactions
1332
+ */
1333
+ async getTransactions(page) {
1334
+ const endpoints = [
1335
+ page ? `/api/v1/profile/completion-grants/claims?page=${page}` : "/api/v1/profile/completion-grants/claims",
1336
+ page ? `/api/v1/wallet/transactions?page=${page}` : "/api/v1/wallet/transactions"
1337
+ ];
1338
+ const errors = [];
1339
+ for (const url of endpoints) {
1340
+ try {
1341
+ const response = await this.client.axiosInstance.get(url);
1342
+ const data = response.data?.data || response.data;
1343
+ return {
1344
+ transactions: data?.results ?? data?.transactions ?? [],
1345
+ next: data?.next,
1346
+ previous: data?.previous,
1347
+ count: data?.count ?? 0
1348
+ };
1349
+ } catch (error) {
1350
+ if (error?.response?.status !== 404) {
1351
+ errors.push({
1352
+ endpoint: url,
1353
+ status: error?.response?.status,
1354
+ message: error?.message || "Unknown error"
1355
+ });
1356
+ }
1357
+ }
1358
+ }
1359
+ if (errors.length > 0) {
1360
+ logger.warn("All transaction API endpoints failed:", errors);
1361
+ }
1362
+ return {
1363
+ transactions: [],
1364
+ next: void 0,
1365
+ previous: void 0,
1366
+ count: 0
1367
+ };
1368
+ }
1369
+ };
1370
+
1371
+ // src/ZoPassportSDK.ts
1372
+ var ZoPassportSDK = class {
1373
+ constructor(config) {
1374
+ this.refreshTimer = null;
1375
+ this._user = null;
1376
+ this._isAuthenticated = false;
1377
+ if (config.debug) {
1378
+ logger.enable();
1379
+ logger.setLevel("debug");
1380
+ }
1381
+ this.storage = config.storageAdapter || new LocalStorageAdapter();
1382
+ this.client = new ZoApiClient({
1383
+ ...config,
1384
+ storageAdapter: this.storage
1385
+ });
1386
+ this.auth = new ZoAuth(this.client);
1387
+ this.profile = new ZoProfile(this.client);
1388
+ this.avatar = new ZoAvatar(this.client);
1389
+ this.wallet = new ZoWallet(this.client);
1390
+ if (config.autoRefresh !== false) {
1391
+ this.startAutoRefresh(config.refreshInterval || 6e4);
1392
+ }
1393
+ this._readyPromise = this.loadSession();
1394
+ logger.debug("SDK initialized with config:", {
1395
+ baseUrl: config.baseUrl,
1396
+ autoRefresh: config.autoRefresh !== false
1397
+ });
1398
+ }
1399
+ /**
1400
+ * Wait for the SDK to be ready (session loaded from storage)
1401
+ * Use this if you need to check isAuthenticated immediately after construction
1402
+ */
1403
+ async ready() {
1404
+ return this._readyPromise;
1405
+ }
1406
+ // =====================
1407
+ // Session Management
1408
+ // =====================
1409
+ async loadSession() {
1410
+ try {
1411
+ const userJson = await this.storage.getItem(STORAGE_KEYS.USER);
1412
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
1413
+ if (userJson && accessToken) {
1414
+ this._user = JSON.parse(userJson);
1415
+ this._isAuthenticated = true;
1416
+ }
1417
+ } catch (error) {
1418
+ logger.warn("Failed to load session:", error);
1419
+ }
1420
+ }
1421
+ async saveSession(authResponse) {
1422
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, authResponse.access_token);
1423
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, authResponse.refresh_token);
1424
+ await this.storage.setItem(STORAGE_KEYS.TOKEN_EXPIRY, authResponse.access_token_expiry);
1425
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_EXPIRY, authResponse.refresh_token_expiry);
1426
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(authResponse.user));
1427
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_ID, authResponse.device_id || "");
1428
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET, authResponse.device_secret || "");
1429
+ this._user = authResponse.user;
1430
+ this._isAuthenticated = true;
1431
+ }
1432
+ async clearSession() {
1433
+ await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
1434
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
1435
+ await this.storage.removeItem(STORAGE_KEYS.TOKEN_EXPIRY);
1436
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_EXPIRY);
1437
+ await this.storage.removeItem(STORAGE_KEYS.USER);
1438
+ await this.storage.removeItem(STORAGE_KEYS.CLIENT_DEVICE_ID);
1439
+ await this.storage.removeItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET);
1440
+ this._user = null;
1441
+ this._isAuthenticated = false;
1442
+ }
1443
+ // =====================
1444
+ // Auto Token Refresh
1445
+ // =====================
1446
+ startAutoRefresh(interval) {
1447
+ this.refreshTimer = setInterval(async () => {
1448
+ await this.refreshTokenIfNeeded();
1449
+ }, interval);
1450
+ }
1451
+ stopAutoRefresh() {
1452
+ if (this.refreshTimer) {
1453
+ clearInterval(this.refreshTimer);
1454
+ this.refreshTimer = null;
1455
+ }
1456
+ }
1457
+ async refreshTokenIfNeeded() {
1458
+ const tokenExpiry = await this.storage.getItem(STORAGE_KEYS.TOKEN_EXPIRY);
1459
+ const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
1460
+ if (!tokenExpiry || !refreshToken) return;
1461
+ const expiryDate = new Date(tokenExpiry);
1462
+ const now = /* @__PURE__ */ new Date();
1463
+ const twoMinutes = 2 * 60 * 1e3;
1464
+ if (expiryDate.getTime() - now.getTime() < twoMinutes) {
1465
+ const result = await this.auth.refreshAccessToken(refreshToken);
1466
+ if (result.success && result.tokens) {
1467
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, result.tokens.access);
1468
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, result.tokens.refresh);
1469
+ await this.storage.setItem(STORAGE_KEYS.TOKEN_EXPIRY, result.tokens.access_expiry);
1470
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_EXPIRY, result.tokens.refresh_expiry);
1471
+ }
1472
+ }
1473
+ }
1474
+ // =====================
1475
+ // Public API
1476
+ // =====================
1477
+ get user() {
1478
+ return this._user;
1479
+ }
1480
+ get isAuthenticated() {
1481
+ return this._isAuthenticated;
1482
+ }
1483
+ /**
1484
+ * Complete phone authentication flow
1485
+ */
1486
+ async loginWithPhone(countryCode, phoneNumber, otp) {
1487
+ const result = await this.auth.verifyOTP(countryCode, phoneNumber, otp);
1488
+ if (result.success && result.data) {
1489
+ await this.saveSession(result.data);
1490
+ if (result.data.user?.wallet_address) {
1491
+ this.wallet.setWalletAddress(result.data.user.wallet_address, "base");
1492
+ }
1493
+ return { success: true, user: result.data.user };
1494
+ }
1495
+ return { success: false, error: result.error };
1496
+ }
1497
+ /**
1498
+ * Logout and clear session
1499
+ */
1500
+ async logout() {
1501
+ await this.clearSession();
1502
+ this.stopAutoRefresh();
1503
+ }
1504
+ /**
1505
+ * Get current user profile
1506
+ */
1507
+ async getProfile() {
1508
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
1509
+ if (!accessToken) return null;
1510
+ const result = await this.profile.getProfile(accessToken);
1511
+ if (result.success && result.profile) {
1512
+ this._user = result.profile;
1513
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(result.profile));
1514
+ if (result.profile.wallet_address) {
1515
+ this.wallet.setWalletAddress(result.profile.wallet_address, "base");
1516
+ }
1517
+ return result.profile;
1518
+ }
1519
+ return null;
1520
+ }
1521
+ /**
1522
+ * Update user profile
1523
+ */
1524
+ async updateProfile(updates) {
1525
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
1526
+ if (!accessToken) return { success: false, error: "Not authenticated" };
1527
+ const result = await this.profile.updateProfile(accessToken, updates);
1528
+ if (result.success && result.profile) {
1529
+ this._user = result.profile;
1530
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(result.profile));
1531
+ return { success: true, profile: result.profile };
1532
+ }
1533
+ return { success: false, error: result.error };
1534
+ }
1535
+ /**
1536
+ * Generate avatar
1537
+ */
1538
+ async generateAvatar(bodyType) {
1539
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
1540
+ if (!accessToken) return { success: false, error: "Not authenticated" };
1541
+ const startResult = await this.avatar.generateAvatar(accessToken, bodyType);
1542
+ if (!startResult.success || !startResult.task_id) {
1543
+ return { success: false, error: startResult.error };
1544
+ }
1545
+ return new Promise((resolve) => {
1546
+ this.avatar.pollAvatarStatus(accessToken, startResult.task_id, {
1547
+ onComplete: (avatarUrl) => {
1548
+ resolve({ success: true, avatarUrl });
1549
+ },
1550
+ onError: (error) => {
1551
+ resolve({ success: false, error });
1552
+ }
1553
+ });
1554
+ });
1555
+ }
1556
+ /**
1557
+ * Get wallet balance
1558
+ */
1559
+ async getWalletBalance() {
1560
+ return this.wallet.getBalance();
1561
+ }
1562
+ /**
1563
+ * Get wallet transactions
1564
+ */
1565
+ async getWalletTransactions(page) {
1566
+ return this.wallet.getTransactions(page);
1567
+ }
1568
+ /**
1569
+ * Cleanup
1570
+ */
1571
+ destroy() {
1572
+ this.stopAutoRefresh();
1573
+ }
1574
+ };
1575
+ export {
1576
+ AsyncStorageAdapter,
1577
+ MemoryStorageAdapter,
1578
+ MovingShine,
1579
+ STORAGE_KEYS,
1580
+ TransactionItem,
1581
+ TransactionList,
1582
+ WalletCard,
1583
+ WalletScreen,
1584
+ ZoPassportSDK,
1585
+ ZoToken,
1586
+ ZoTokenVideo
1587
+ };
1588
+ //# sourceMappingURL=react-native.mjs.map