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
package/dist/react.js ADDED
@@ -0,0 +1,3856 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/react.tsx
31
+ var react_exports = {};
32
+ __export(react_exports, {
33
+ FounderBadge: () => FounderBadge,
34
+ OTPInput: () => OTPInput,
35
+ PhoneInput: () => PhoneInput,
36
+ WalletCard: () => WalletCardWeb,
37
+ WalletFullPage: () => WalletFullPage,
38
+ ZoAuth: () => ZoAuth2,
39
+ ZoAvatar: () => ZoAvatar2,
40
+ ZoLanding: () => ZoLanding,
41
+ ZoOnboarding: () => ZoOnboarding,
42
+ ZoPassportCard: () => ZoPassportCard,
43
+ ZoPassportPage: () => ZoPassportPage,
44
+ ZoPassportProvider: () => ZoPassportProvider,
45
+ ZoPassportSDK: () => ZoPassportSDK,
46
+ ZoProgressRing: () => ZoProgressRing,
47
+ useAuth: () => useAuth,
48
+ useAvatar: () => useAvatar,
49
+ useProfile: () => useProfile,
50
+ useTransactions: () => useTransactions,
51
+ useWallet: () => useWallet,
52
+ useWalletBalance: () => useWalletBalance,
53
+ useZoPassport: () => useZoPassport
54
+ });
55
+ module.exports = __toCommonJS(react_exports);
56
+ var import_react18 = require("react");
57
+
58
+ // src/lib/api/client.ts
59
+ var import_axios = __toESM(require("axios"));
60
+
61
+ // src/lib/utils/logger.ts
62
+ var LOG_LEVELS = {
63
+ debug: 0,
64
+ info: 1,
65
+ warn: 2,
66
+ error: 3,
67
+ none: 4
68
+ };
69
+ var Logger = class {
70
+ constructor() {
71
+ this.config = {
72
+ enabled: false,
73
+ level: "warn",
74
+ prefix: "[ZoPassport]"
75
+ };
76
+ }
77
+ /**
78
+ * Configure the logger
79
+ * @param options - Logger configuration
80
+ */
81
+ configure(options) {
82
+ this.config = { ...this.config, ...options };
83
+ }
84
+ /**
85
+ * Enable debug logging
86
+ */
87
+ enable() {
88
+ this.config.enabled = true;
89
+ }
90
+ /**
91
+ * Disable all logging
92
+ */
93
+ disable() {
94
+ this.config.enabled = false;
95
+ }
96
+ /**
97
+ * Set log level
98
+ */
99
+ setLevel(level) {
100
+ this.config.level = level;
101
+ }
102
+ shouldLog(level) {
103
+ if (!this.config.enabled) return false;
104
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.config.level];
105
+ }
106
+ debug(...args) {
107
+ if (this.shouldLog("debug")) {
108
+ console.log(this.config.prefix, ...args);
109
+ }
110
+ }
111
+ info(...args) {
112
+ if (this.shouldLog("info")) {
113
+ console.info(this.config.prefix, ...args);
114
+ }
115
+ }
116
+ warn(...args) {
117
+ if (this.shouldLog("warn")) {
118
+ console.warn(this.config.prefix, ...args);
119
+ }
120
+ }
121
+ error(...args) {
122
+ if (this.shouldLog("error")) {
123
+ console.error(this.config.prefix, ...args);
124
+ }
125
+ }
126
+ };
127
+ var logger = new Logger();
128
+
129
+ // src/lib/utils/storage.ts
130
+ var STORAGE_KEYS = {
131
+ ACCESS_TOKEN: "zo_access_token",
132
+ REFRESH_TOKEN: "zo_refresh_token",
133
+ TOKEN_EXPIRY: "zo_token_expiry",
134
+ REFRESH_EXPIRY: "zo_refresh_expiry",
135
+ USER: "zo_user",
136
+ CLIENT_DEVICE_ID: "zo_device_id",
137
+ CLIENT_DEVICE_SECRET: "zo_device_secret",
138
+ AVATAR_URL: "zo_avatar_url",
139
+ NICKNAME: "zo_nickname",
140
+ CITY: "zo_city",
141
+ BODY_TYPE: "zo_body_type"
142
+ };
143
+ var LocalStorageAdapter = class {
144
+ async getItem(key) {
145
+ if (typeof window === "undefined") return null;
146
+ try {
147
+ return localStorage.getItem(key);
148
+ } catch (error) {
149
+ logger.warn(`LocalStorage.getItem failed for key "${key}":`, error);
150
+ return null;
151
+ }
152
+ }
153
+ async setItem(key, value) {
154
+ if (typeof window === "undefined") return;
155
+ try {
156
+ localStorage.setItem(key, value);
157
+ } catch (error) {
158
+ logger.warn(`LocalStorage.setItem failed for key "${key}":`, error);
159
+ }
160
+ }
161
+ async removeItem(key) {
162
+ if (typeof window === "undefined") return;
163
+ try {
164
+ localStorage.removeItem(key);
165
+ } catch (error) {
166
+ logger.warn(`LocalStorage.removeItem failed for key "${key}":`, error);
167
+ }
168
+ }
169
+ };
170
+
171
+ // src/lib/api/client.ts
172
+ function generateDeviceCredentials() {
173
+ const deviceId = `web-${Date.now()}-${Math.random().toString(36).substring(7)}`;
174
+ const deviceSecret = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
175
+ return { deviceId, deviceSecret };
176
+ }
177
+ var ZoApiClient = class {
178
+ constructor(config) {
179
+ this.config = config;
180
+ this.storage = config.storageAdapter || new LocalStorageAdapter();
181
+ this.client = import_axios.default.create({
182
+ baseURL: config.baseUrl || "https://api.io.zo.xyz",
183
+ timeout: config.timeout || 1e4,
184
+ headers: {
185
+ "Content-Type": "application/json",
186
+ "Accept": "application/json"
187
+ }
188
+ });
189
+ this.setupInterceptors();
190
+ }
191
+ async setupInterceptors() {
192
+ this.client.interceptors.request.use(async (config) => {
193
+ config.headers["client-key"] = this.config.clientKey;
194
+ const credentials = await this.getOrCreateDeviceCredentials();
195
+ config.headers["client-device-id"] = credentials.deviceId;
196
+ config.headers["client-device-secret"] = credentials.deviceSecret;
197
+ const token = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
198
+ if (token) {
199
+ config.headers["Authorization"] = `Bearer ${token}`;
200
+ }
201
+ return config;
202
+ });
203
+ this.client.interceptors.response.use(
204
+ (response) => response,
205
+ async (error) => {
206
+ const originalRequest = error.config;
207
+ if (error.response?.status === 401 && !originalRequest._retry) {
208
+ originalRequest._retry = true;
209
+ const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
210
+ if (refreshToken) {
211
+ try {
212
+ const response = await this.client.post("/api/v1/auth/token/refresh/", {
213
+ refresh_token: refreshToken
214
+ });
215
+ if (response.data?.access) {
216
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, response.data.access);
217
+ if (response.data.refresh) {
218
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, response.data.refresh);
219
+ }
220
+ originalRequest.headers["Authorization"] = `Bearer ${response.data.access}`;
221
+ return this.client(originalRequest);
222
+ }
223
+ } catch (refreshError) {
224
+ await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
225
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
226
+ }
227
+ }
228
+ }
229
+ return Promise.reject(error);
230
+ }
231
+ );
232
+ }
233
+ async getOrCreateDeviceCredentials() {
234
+ const storedId = await this.storage.getItem(STORAGE_KEYS.CLIENT_DEVICE_ID);
235
+ const storedSecret = await this.storage.getItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET);
236
+ if (storedId && storedSecret) {
237
+ return { deviceId: storedId, deviceSecret: storedSecret };
238
+ }
239
+ const credentials = generateDeviceCredentials();
240
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_ID, credentials.deviceId);
241
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET, credentials.deviceSecret);
242
+ return credentials;
243
+ }
244
+ get axiosInstance() {
245
+ return this.client;
246
+ }
247
+ getStorage() {
248
+ return this.storage;
249
+ }
250
+ };
251
+
252
+ // src/lib/api/auth.ts
253
+ var ZoAuth = class {
254
+ constructor(client) {
255
+ this.client = client;
256
+ }
257
+ /**
258
+ * Send OTP to phone number
259
+ * Step 1 of ZO phone authentication
260
+ */
261
+ async sendOTP(countryCode, phoneNumber) {
262
+ try {
263
+ const payload = {
264
+ mobile_country_code: countryCode,
265
+ mobile_number: phoneNumber,
266
+ message_channel: ""
267
+ // Empty string as per ZO API spec
268
+ };
269
+ const response = await this.client.axiosInstance.post(
270
+ "/api/v1/auth/login/mobile/otp/",
271
+ payload
272
+ );
273
+ if (response.status >= 200 && response.status < 300) {
274
+ return {
275
+ success: true,
276
+ message: response.data?.message || "OTP sent successfully"
277
+ };
278
+ }
279
+ return {
280
+ success: false,
281
+ message: response.data?.message || `Unexpected status: ${response.status}`
282
+ };
283
+ } catch (error) {
284
+ const errorData = error.response?.data;
285
+ const errorMessage = errorData?.detail || errorData?.message || errorData?.error || error.message || "Failed to send OTP";
286
+ return {
287
+ success: false,
288
+ message: errorMessage
289
+ };
290
+ }
291
+ }
292
+ /**
293
+ * Verify OTP and authenticate user
294
+ * Step 2 of ZO phone authentication
295
+ * Returns full auth response with tokens and user profile
296
+ */
297
+ async verifyOTP(countryCode, phoneNumber, otp) {
298
+ try {
299
+ const payload = {
300
+ mobile_country_code: countryCode,
301
+ mobile_number: phoneNumber,
302
+ otp
303
+ };
304
+ const response = await this.client.axiosInstance.post(
305
+ "/api/v1/auth/login/mobile/",
306
+ payload
307
+ );
308
+ let responseData;
309
+ if (typeof response.data === "string") {
310
+ try {
311
+ responseData = JSON.parse(response.data);
312
+ } catch {
313
+ return {
314
+ success: false,
315
+ error: "Invalid response format from authentication service"
316
+ };
317
+ }
318
+ } else {
319
+ responseData = response.data;
320
+ }
321
+ if (!responseData || !responseData.user || !responseData.access_token) {
322
+ return {
323
+ success: false,
324
+ error: "Invalid response structure from authentication service"
325
+ };
326
+ }
327
+ return {
328
+ success: true,
329
+ data: responseData
330
+ };
331
+ } catch (error) {
332
+ const errorMessage = this.extractErrorMessage(error);
333
+ return {
334
+ success: false,
335
+ error: errorMessage
336
+ };
337
+ }
338
+ }
339
+ /**
340
+ * Refresh access token using refresh token
341
+ */
342
+ async refreshAccessToken(refreshToken) {
343
+ try {
344
+ const response = await this.client.axiosInstance.post("/api/v1/auth/token/refresh/", {
345
+ refresh_token: refreshToken
346
+ });
347
+ return {
348
+ success: true,
349
+ tokens: response.data
350
+ };
351
+ } catch (error) {
352
+ return {
353
+ success: false,
354
+ error: "Failed to refresh authentication"
355
+ };
356
+ }
357
+ }
358
+ /**
359
+ * Check if user is authenticated
360
+ */
361
+ async checkLoginStatus(accessToken) {
362
+ try {
363
+ const response = await this.client.axiosInstance.get("/api/v1/auth/login/check/", {
364
+ headers: {
365
+ Authorization: `Bearer ${accessToken}`
366
+ }
367
+ });
368
+ return {
369
+ success: true,
370
+ isAuthenticated: response.data.authenticated === true
371
+ };
372
+ } catch {
373
+ return {
374
+ success: false,
375
+ isAuthenticated: false
376
+ };
377
+ }
378
+ }
379
+ /**
380
+ * Extract error message from various ZO API error formats
381
+ */
382
+ extractErrorMessage(error) {
383
+ const errorData = error.response?.data;
384
+ if (errorData) {
385
+ if (errorData.errors && Array.isArray(errorData.errors)) {
386
+ return errorData.errors[0] || "Invalid OTP";
387
+ }
388
+ if (errorData.detail) return errorData.detail;
389
+ if (errorData.message) return errorData.message;
390
+ if (errorData.error) return errorData.error;
391
+ }
392
+ return "Authentication failed";
393
+ }
394
+ };
395
+
396
+ // src/lib/api/profile.ts
397
+ var ZoProfile = class {
398
+ constructor(client) {
399
+ this.client = client;
400
+ }
401
+ /**
402
+ * Get user profile
403
+ */
404
+ async getProfile(accessToken) {
405
+ try {
406
+ const response = await this.client.axiosInstance.get(
407
+ "/api/v1/profile/me/",
408
+ {
409
+ headers: {
410
+ Authorization: `Bearer ${accessToken}`
411
+ }
412
+ }
413
+ );
414
+ return {
415
+ success: true,
416
+ profile: response.data
417
+ };
418
+ } catch (error) {
419
+ const errorData = error.response?.data;
420
+ return {
421
+ success: false,
422
+ error: errorData?.detail || errorData?.message || "Failed to fetch profile"
423
+ };
424
+ }
425
+ }
426
+ /**
427
+ * Update user profile (partial updates supported)
428
+ */
429
+ async updateProfile(accessToken, updates) {
430
+ try {
431
+ const response = await this.client.axiosInstance.post(
432
+ "/api/v1/profile/me/",
433
+ updates,
434
+ {
435
+ headers: {
436
+ Authorization: `Bearer ${accessToken}`
437
+ }
438
+ }
439
+ );
440
+ return {
441
+ success: true,
442
+ profile: response.data
443
+ };
444
+ } catch (error) {
445
+ const errorData = error.response?.data;
446
+ return {
447
+ success: false,
448
+ error: errorData?.detail || errorData?.message || "Failed to update profile"
449
+ };
450
+ }
451
+ }
452
+ };
453
+
454
+ // src/lib/api/avatar.ts
455
+ var ZoAvatar = class {
456
+ constructor(client) {
457
+ this.client = client;
458
+ }
459
+ /**
460
+ * Generate avatar for user
461
+ */
462
+ async generateAvatar(accessToken, bodyType) {
463
+ try {
464
+ const payload = {
465
+ body_type: bodyType
466
+ };
467
+ const response = await this.client.axiosInstance.post(
468
+ "/api/v1/avatar/generate/",
469
+ payload,
470
+ {
471
+ headers: {
472
+ Authorization: `Bearer ${accessToken}`
473
+ }
474
+ }
475
+ );
476
+ return {
477
+ success: true,
478
+ task_id: response.data.task_id,
479
+ status: response.data.status
480
+ };
481
+ } catch (error) {
482
+ const errorData = error.response?.data;
483
+ return {
484
+ success: false,
485
+ error: errorData?.detail || errorData?.message || "Failed to generate avatar"
486
+ };
487
+ }
488
+ }
489
+ /**
490
+ * Check avatar generation status
491
+ */
492
+ async getAvatarStatus(accessToken, taskId) {
493
+ try {
494
+ const response = await this.client.axiosInstance.get(
495
+ `/api/v1/avatar/status/${taskId}/`,
496
+ {
497
+ headers: {
498
+ Authorization: `Bearer ${accessToken}`
499
+ }
500
+ }
501
+ );
502
+ return {
503
+ success: true,
504
+ status: response.data.status,
505
+ avatarUrl: response.data.result?.avatar_url
506
+ };
507
+ } catch (error) {
508
+ const errorData = error.response?.data;
509
+ return {
510
+ success: false,
511
+ error: errorData?.detail || errorData?.message || "Failed to get avatar status"
512
+ };
513
+ }
514
+ }
515
+ /**
516
+ * Poll avatar status until completion
517
+ */
518
+ async pollAvatarStatus(accessToken, taskId, options = {}) {
519
+ const {
520
+ onProgress,
521
+ onComplete,
522
+ onError,
523
+ maxAttempts = 30,
524
+ interval = 2e3
525
+ } = options;
526
+ let attempts = 0;
527
+ const poll = async () => {
528
+ attempts++;
529
+ if (attempts > maxAttempts) {
530
+ const timeoutError = "Avatar generation timed out";
531
+ onError?.(timeoutError);
532
+ return;
533
+ }
534
+ const result = await this.getAvatarStatus(accessToken, taskId);
535
+ if (!result.success) {
536
+ onError?.(result.error || "Unknown error");
537
+ return;
538
+ }
539
+ onProgress?.(result.status || "unknown");
540
+ if (result.status === "completed" && result.avatarUrl) {
541
+ onComplete?.(result.avatarUrl);
542
+ return;
543
+ }
544
+ if (result.status === "failed") {
545
+ onError?.("Avatar generation failed");
546
+ return;
547
+ }
548
+ setTimeout(poll, interval);
549
+ };
550
+ poll();
551
+ }
552
+ };
553
+
554
+ // src/lib/api/wallet.ts
555
+ var ZO_TOKEN_CONFIG = {
556
+ base: {
557
+ rpc: "https://mainnet.base.org",
558
+ contractAddress: "0x111142c7ecaf39797b7865b82034269962142069",
559
+ // $Zo token on Base
560
+ decimals: 18
561
+ },
562
+ avalanche: {
563
+ rpc: "https://api.avax.network/ext/bc/C/rpc",
564
+ contractAddress: "0x111142c7ecaf39797b7865b82034269962142069",
565
+ // $Zo token on Avalanche (update if different)
566
+ decimals: 18
567
+ }
568
+ };
569
+ var ERC20_BALANCE_ABI = "0x70a08231";
570
+ var ZoWallet = class {
571
+ constructor(client) {
572
+ this.cachedBalance = 0;
573
+ this.userWalletAddress = null;
574
+ this.network = "base";
575
+ this.client = client;
576
+ }
577
+ /**
578
+ * Set the user's wallet address for on-chain queries
579
+ */
580
+ setWalletAddress(address, network = "base") {
581
+ this.userWalletAddress = address;
582
+ this.network = network;
583
+ logger.debug(`Wallet address set: ${address} on ${network}`);
584
+ }
585
+ /**
586
+ * Get wallet balance - tries on-chain first, then API fallback
587
+ * @returns Wallet balance amount
588
+ */
589
+ async getBalance() {
590
+ if (this.userWalletAddress) {
591
+ try {
592
+ const onChainBalance = await this.getOnChainBalance();
593
+ if (onChainBalance !== null) {
594
+ this.cachedBalance = onChainBalance;
595
+ return onChainBalance;
596
+ }
597
+ } catch (error) {
598
+ logger.warn("On-chain balance check failed, falling back to API:", error);
599
+ }
600
+ }
601
+ const apiBalance = await this.getBalanceFromAPI();
602
+ if (apiBalance !== null) {
603
+ return apiBalance;
604
+ }
605
+ logger.debug("Returning cached/default balance:", this.cachedBalance);
606
+ return this.cachedBalance;
607
+ }
608
+ /**
609
+ * Fetch balance directly from blockchain via JSON-RPC
610
+ */
611
+ async getOnChainBalance() {
612
+ if (!this.userWalletAddress) {
613
+ logger.warn("No wallet address set for on-chain query");
614
+ return null;
615
+ }
616
+ const config = ZO_TOKEN_CONFIG[this.network];
617
+ try {
618
+ const paddedAddress = this.userWalletAddress.toLowerCase().replace("0x", "").padStart(64, "0");
619
+ const data = ERC20_BALANCE_ABI + paddedAddress;
620
+ const response = await fetch(config.rpc, {
621
+ method: "POST",
622
+ headers: { "Content-Type": "application/json" },
623
+ body: JSON.stringify({
624
+ jsonrpc: "2.0",
625
+ id: 1,
626
+ method: "eth_call",
627
+ params: [
628
+ {
629
+ to: config.contractAddress,
630
+ data
631
+ },
632
+ "latest"
633
+ ]
634
+ })
635
+ });
636
+ const result = await response.json();
637
+ if (result.error) {
638
+ logger.warn("RPC error:", result.error);
639
+ return null;
640
+ }
641
+ const rawBalance = BigInt(result.result || "0x0");
642
+ const balance = Number(rawBalance) / Math.pow(10, config.decimals);
643
+ logger.debug(`On-chain balance fetched: ${balance} $Zo`);
644
+ return balance;
645
+ } catch (error) {
646
+ logger.warn("Failed to fetch on-chain balance:", error);
647
+ return null;
648
+ }
649
+ }
650
+ /**
651
+ * Fetch balance from Zo API endpoints (tries multiple endpoints with fallback)
652
+ */
653
+ async getBalanceFromAPI() {
654
+ const endpoints = [
655
+ "/api/v1/web3/token/airdrops/summary",
656
+ "/api/v1/wallet/balance",
657
+ "/api/v1/profile/wallet"
658
+ ];
659
+ const errors = [];
660
+ for (const endpoint of endpoints) {
661
+ try {
662
+ const response = await this.client.axiosInstance.get(endpoint);
663
+ const balance = response.data?.data?.total_amount ?? response.data?.balance ?? response.data?.total_amount;
664
+ if (typeof balance === "number") {
665
+ logger.debug(`Balance fetched from API ${endpoint}:`, balance);
666
+ this.cachedBalance = balance;
667
+ return balance;
668
+ }
669
+ } catch (error) {
670
+ if (error?.response?.status !== 404) {
671
+ errors.push({
672
+ endpoint,
673
+ status: error?.response?.status,
674
+ message: error?.message || "Unknown error"
675
+ });
676
+ }
677
+ }
678
+ }
679
+ if (errors.length > 0) {
680
+ logger.warn("All balance API endpoints failed:", errors);
681
+ }
682
+ return null;
683
+ }
684
+ /**
685
+ * Get transaction history
686
+ * @param page - Optional page number for pagination
687
+ * @returns Array of transactions
688
+ */
689
+ async getTransactions(page) {
690
+ const endpoints = [
691
+ page ? `/api/v1/profile/completion-grants/claims?page=${page}` : "/api/v1/profile/completion-grants/claims",
692
+ page ? `/api/v1/wallet/transactions?page=${page}` : "/api/v1/wallet/transactions"
693
+ ];
694
+ const errors = [];
695
+ for (const url of endpoints) {
696
+ try {
697
+ const response = await this.client.axiosInstance.get(url);
698
+ const data = response.data?.data || response.data;
699
+ return {
700
+ transactions: data?.results ?? data?.transactions ?? [],
701
+ next: data?.next,
702
+ previous: data?.previous,
703
+ count: data?.count ?? 0
704
+ };
705
+ } catch (error) {
706
+ if (error?.response?.status !== 404) {
707
+ errors.push({
708
+ endpoint: url,
709
+ status: error?.response?.status,
710
+ message: error?.message || "Unknown error"
711
+ });
712
+ }
713
+ }
714
+ }
715
+ if (errors.length > 0) {
716
+ logger.warn("All transaction API endpoints failed:", errors);
717
+ }
718
+ return {
719
+ transactions: [],
720
+ next: void 0,
721
+ previous: void 0,
722
+ count: 0
723
+ };
724
+ }
725
+ };
726
+
727
+ // src/ZoPassportSDK.ts
728
+ var ZoPassportSDK = class {
729
+ constructor(config) {
730
+ this.refreshTimer = null;
731
+ this._user = null;
732
+ this._isAuthenticated = false;
733
+ if (config.debug) {
734
+ logger.enable();
735
+ logger.setLevel("debug");
736
+ }
737
+ this.storage = config.storageAdapter || new LocalStorageAdapter();
738
+ this.client = new ZoApiClient({
739
+ ...config,
740
+ storageAdapter: this.storage
741
+ });
742
+ this.auth = new ZoAuth(this.client);
743
+ this.profile = new ZoProfile(this.client);
744
+ this.avatar = new ZoAvatar(this.client);
745
+ this.wallet = new ZoWallet(this.client);
746
+ if (config.autoRefresh !== false) {
747
+ this.startAutoRefresh(config.refreshInterval || 6e4);
748
+ }
749
+ this._readyPromise = this.loadSession();
750
+ logger.debug("SDK initialized with config:", {
751
+ baseUrl: config.baseUrl,
752
+ autoRefresh: config.autoRefresh !== false
753
+ });
754
+ }
755
+ /**
756
+ * Wait for the SDK to be ready (session loaded from storage)
757
+ * Use this if you need to check isAuthenticated immediately after construction
758
+ */
759
+ async ready() {
760
+ return this._readyPromise;
761
+ }
762
+ // =====================
763
+ // Session Management
764
+ // =====================
765
+ async loadSession() {
766
+ try {
767
+ const userJson = await this.storage.getItem(STORAGE_KEYS.USER);
768
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
769
+ if (userJson && accessToken) {
770
+ this._user = JSON.parse(userJson);
771
+ this._isAuthenticated = true;
772
+ }
773
+ } catch (error) {
774
+ logger.warn("Failed to load session:", error);
775
+ }
776
+ }
777
+ async saveSession(authResponse) {
778
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, authResponse.access_token);
779
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, authResponse.refresh_token);
780
+ await this.storage.setItem(STORAGE_KEYS.TOKEN_EXPIRY, authResponse.access_token_expiry);
781
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_EXPIRY, authResponse.refresh_token_expiry);
782
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(authResponse.user));
783
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_ID, authResponse.device_id || "");
784
+ await this.storage.setItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET, authResponse.device_secret || "");
785
+ this._user = authResponse.user;
786
+ this._isAuthenticated = true;
787
+ }
788
+ async clearSession() {
789
+ await this.storage.removeItem(STORAGE_KEYS.ACCESS_TOKEN);
790
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_TOKEN);
791
+ await this.storage.removeItem(STORAGE_KEYS.TOKEN_EXPIRY);
792
+ await this.storage.removeItem(STORAGE_KEYS.REFRESH_EXPIRY);
793
+ await this.storage.removeItem(STORAGE_KEYS.USER);
794
+ await this.storage.removeItem(STORAGE_KEYS.CLIENT_DEVICE_ID);
795
+ await this.storage.removeItem(STORAGE_KEYS.CLIENT_DEVICE_SECRET);
796
+ this._user = null;
797
+ this._isAuthenticated = false;
798
+ }
799
+ // =====================
800
+ // Auto Token Refresh
801
+ // =====================
802
+ startAutoRefresh(interval) {
803
+ this.refreshTimer = setInterval(async () => {
804
+ await this.refreshTokenIfNeeded();
805
+ }, interval);
806
+ }
807
+ stopAutoRefresh() {
808
+ if (this.refreshTimer) {
809
+ clearInterval(this.refreshTimer);
810
+ this.refreshTimer = null;
811
+ }
812
+ }
813
+ async refreshTokenIfNeeded() {
814
+ const tokenExpiry = await this.storage.getItem(STORAGE_KEYS.TOKEN_EXPIRY);
815
+ const refreshToken = await this.storage.getItem(STORAGE_KEYS.REFRESH_TOKEN);
816
+ if (!tokenExpiry || !refreshToken) return;
817
+ const expiryDate = new Date(tokenExpiry);
818
+ const now = /* @__PURE__ */ new Date();
819
+ const twoMinutes = 2 * 60 * 1e3;
820
+ if (expiryDate.getTime() - now.getTime() < twoMinutes) {
821
+ const result = await this.auth.refreshAccessToken(refreshToken);
822
+ if (result.success && result.tokens) {
823
+ await this.storage.setItem(STORAGE_KEYS.ACCESS_TOKEN, result.tokens.access);
824
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_TOKEN, result.tokens.refresh);
825
+ await this.storage.setItem(STORAGE_KEYS.TOKEN_EXPIRY, result.tokens.access_expiry);
826
+ await this.storage.setItem(STORAGE_KEYS.REFRESH_EXPIRY, result.tokens.refresh_expiry);
827
+ }
828
+ }
829
+ }
830
+ // =====================
831
+ // Public API
832
+ // =====================
833
+ get user() {
834
+ return this._user;
835
+ }
836
+ get isAuthenticated() {
837
+ return this._isAuthenticated;
838
+ }
839
+ /**
840
+ * Complete phone authentication flow
841
+ */
842
+ async loginWithPhone(countryCode, phoneNumber, otp) {
843
+ const result = await this.auth.verifyOTP(countryCode, phoneNumber, otp);
844
+ if (result.success && result.data) {
845
+ await this.saveSession(result.data);
846
+ if (result.data.user?.wallet_address) {
847
+ this.wallet.setWalletAddress(result.data.user.wallet_address, "base");
848
+ }
849
+ return { success: true, user: result.data.user };
850
+ }
851
+ return { success: false, error: result.error };
852
+ }
853
+ /**
854
+ * Logout and clear session
855
+ */
856
+ async logout() {
857
+ await this.clearSession();
858
+ this.stopAutoRefresh();
859
+ }
860
+ /**
861
+ * Get current user profile
862
+ */
863
+ async getProfile() {
864
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
865
+ if (!accessToken) return null;
866
+ const result = await this.profile.getProfile(accessToken);
867
+ if (result.success && result.profile) {
868
+ this._user = result.profile;
869
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(result.profile));
870
+ if (result.profile.wallet_address) {
871
+ this.wallet.setWalletAddress(result.profile.wallet_address, "base");
872
+ }
873
+ return result.profile;
874
+ }
875
+ return null;
876
+ }
877
+ /**
878
+ * Update user profile
879
+ */
880
+ async updateProfile(updates) {
881
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
882
+ if (!accessToken) return { success: false, error: "Not authenticated" };
883
+ const result = await this.profile.updateProfile(accessToken, updates);
884
+ if (result.success && result.profile) {
885
+ this._user = result.profile;
886
+ await this.storage.setItem(STORAGE_KEYS.USER, JSON.stringify(result.profile));
887
+ return { success: true, profile: result.profile };
888
+ }
889
+ return { success: false, error: result.error };
890
+ }
891
+ /**
892
+ * Generate avatar
893
+ */
894
+ async generateAvatar(bodyType) {
895
+ const accessToken = await this.storage.getItem(STORAGE_KEYS.ACCESS_TOKEN);
896
+ if (!accessToken) return { success: false, error: "Not authenticated" };
897
+ const startResult = await this.avatar.generateAvatar(accessToken, bodyType);
898
+ if (!startResult.success || !startResult.task_id) {
899
+ return { success: false, error: startResult.error };
900
+ }
901
+ return new Promise((resolve) => {
902
+ this.avatar.pollAvatarStatus(accessToken, startResult.task_id, {
903
+ onComplete: (avatarUrl) => {
904
+ resolve({ success: true, avatarUrl });
905
+ },
906
+ onError: (error) => {
907
+ resolve({ success: false, error });
908
+ }
909
+ });
910
+ });
911
+ }
912
+ /**
913
+ * Get wallet balance
914
+ */
915
+ async getWalletBalance() {
916
+ return this.wallet.getBalance();
917
+ }
918
+ /**
919
+ * Get wallet transactions
920
+ */
921
+ async getWalletTransactions(page) {
922
+ return this.wallet.getTransactions(page);
923
+ }
924
+ /**
925
+ * Cleanup
926
+ */
927
+ destroy() {
928
+ this.stopAutoRefresh();
929
+ }
930
+ };
931
+
932
+ // src/components/ZoProgressRing.tsx
933
+ var import_jsx_runtime = require("react/jsx-runtime");
934
+ var ZoProgressRing = ({
935
+ progress,
936
+ size = 140,
937
+ strokeWidth = 4,
938
+ primaryColor = "#FFFFFF",
939
+ secondaryColor = "rgba(255,255,255,0.2)"
940
+ }) => {
941
+ const radius = (size - strokeWidth) / 2;
942
+ const circumference = radius * 2 * Math.PI;
943
+ const strokeDashoffset = circumference - progress / 100 * circumference;
944
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: size, height: size, style: { transform: "rotate(-90deg)" }, children: [
945
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
946
+ "circle",
947
+ {
948
+ cx: size / 2,
949
+ cy: size / 2,
950
+ r: radius,
951
+ strokeWidth,
952
+ fill: "none",
953
+ stroke: secondaryColor
954
+ }
955
+ ),
956
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
957
+ "circle",
958
+ {
959
+ cx: size / 2,
960
+ cy: size / 2,
961
+ r: radius,
962
+ stroke: primaryColor,
963
+ strokeWidth,
964
+ fill: "none",
965
+ strokeDasharray: circumference,
966
+ strokeDashoffset,
967
+ strokeLinecap: "round",
968
+ style: { transition: "stroke-dashoffset 0.5s ease-in-out" }
969
+ }
970
+ )
971
+ ] });
972
+ };
973
+
974
+ // src/components/FounderBadge.tsx
975
+ var import_jsx_runtime2 = require("react/jsx-runtime");
976
+ var FounderBadge = ({ size = 32 }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: size, height: size, viewBox: "0 0 28 28", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
977
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
978
+ "path",
979
+ {
980
+ d: "M12.0117 3.15234C13.1449 2.14828 14.8551 2.14828 15.9883 3.15234L16.0996 3.25684L17.7715 4.89453L20.1123 4.91895L20.2646 4.92383C21.7758 5.01516 22.9848 6.22423 23.0762 7.73535L23.0811 7.8877L23.1045 10.2275L24.7432 11.9004L24.8477 12.0117C25.8517 13.1449 25.8517 14.8551 24.8477 15.9883L24.7432 16.0996L23.1045 17.7715L23.0811 20.1123C23.0646 21.6938 21.8262 22.9818 20.2646 23.0762L20.1123 23.0811L17.7715 23.1045L16.0996 24.7432C14.9697 25.8498 13.1826 25.8852 12.0117 24.8477L11.9004 24.7432L10.2275 23.1045L7.8877 23.0811C6.30625 23.0646 5.01821 21.8262 4.92383 20.2646L4.91895 20.1123L4.89453 17.7715L3.25684 16.0996C2.11446 14.9333 2.11446 13.0667 3.25684 11.9004L4.89453 10.2275L4.91895 7.8877L4.92383 7.73535C5.01821 6.17382 6.30624 4.93536 7.8877 4.91895L10.2275 4.89453L11.9004 3.25684L12.0117 3.15234Z",
981
+ fill: "#FF2F8E",
982
+ stroke: "#111111",
983
+ strokeWidth: "4",
984
+ strokeLinejoin: "round"
985
+ }
986
+ ),
987
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
988
+ "path",
989
+ {
990
+ d: "M13.5008 16.1741H15.8997C16.4443 16.1741 16.8858 16.6156 16.8858 17.1602C16.8858 17.7048 16.4443 18.1463 15.8997 18.1463H12.2286C11.4558 18.1463 10.8293 17.5199 10.8293 16.7471C10.8293 16.4219 10.9425 16.1069 11.1495 15.8562L14.0743 12.3137H11.8434C11.2988 12.3137 10.8573 11.8722 10.8573 11.3276C10.8573 10.783 11.2988 10.3415 11.8434 10.3415H15.4226C16.1921 10.3415 16.8158 10.9652 16.8158 11.7347C16.8158 12.0634 16.6996 12.3816 16.4876 12.6329L13.5008 16.1741Z",
991
+ fill: "white"
992
+ }
993
+ )
994
+ ] });
995
+
996
+ // src/components/ZoAvatar.tsx
997
+ var import_react = require("react");
998
+ var import_jsx_runtime3 = require("react/jsx-runtime");
999
+ var DEFAULT_FALLBACK = "/images/rank1.jpeg";
1000
+ var ZoAvatar2 = ({
1001
+ src,
1002
+ name = "User",
1003
+ size = 120,
1004
+ fallbackUrl = DEFAULT_FALLBACK,
1005
+ className = ""
1006
+ }) => {
1007
+ const [imgSrc, setImgSrc] = (0, import_react.useState)(src || fallbackUrl);
1008
+ const [hasError, setHasError] = (0, import_react.useState)(false);
1009
+ const handleError = () => {
1010
+ if (!hasError) {
1011
+ setHasError(true);
1012
+ setImgSrc(fallbackUrl);
1013
+ }
1014
+ };
1015
+ const initials = name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2);
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1017
+ "div",
1018
+ {
1019
+ className: `zo-avatar ${className}`,
1020
+ style: {
1021
+ width: size,
1022
+ height: size,
1023
+ borderRadius: "50%",
1024
+ overflow: "hidden",
1025
+ backgroundColor: "#1a1a1a",
1026
+ display: "flex",
1027
+ alignItems: "center",
1028
+ justifyContent: "center"
1029
+ },
1030
+ children: imgSrc ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1031
+ "img",
1032
+ {
1033
+ src: imgSrc,
1034
+ alt: name,
1035
+ onError: handleError,
1036
+ style: {
1037
+ width: "100%",
1038
+ height: "100%",
1039
+ objectFit: "cover"
1040
+ }
1041
+ }
1042
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1043
+ "span",
1044
+ {
1045
+ style: {
1046
+ fontSize: size * 0.4,
1047
+ fontWeight: 700,
1048
+ color: "#ffffff",
1049
+ fontFamily: "Rubik, system-ui, sans-serif"
1050
+ },
1051
+ children: initials
1052
+ }
1053
+ )
1054
+ }
1055
+ );
1056
+ };
1057
+
1058
+ // src/components/ZoPassportCard.tsx
1059
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1060
+ var FOUNDER_BG = "https://proxy.cdn.zo.xyz/gallery/media/images/a1659b07-94f0-4490-9b3c-3366715d9717_20250515053726.png";
1061
+ var CITIZEN_BG = "https://proxy.cdn.zo.xyz/gallery/media/images/bda9da5a-eefe-411d-8d90-667c80024463_20250515053805.png";
1062
+ var ZoPassportCard = ({
1063
+ profile,
1064
+ completion,
1065
+ className = "",
1066
+ founderBgUrl = FOUNDER_BG,
1067
+ citizenBgUrl = CITIZEN_BG,
1068
+ defaultAvatarUrl = "/images/rank1.jpeg"
1069
+ }) => {
1070
+ const isFounder = profile?.isFounder || false;
1071
+ const name = profile?.name || "New Citizen";
1072
+ const avatar = profile?.avatar || defaultAvatarUrl;
1073
+ const done = completion?.done || 0;
1074
+ const total = completion?.total || 1;
1075
+ const progress = Math.min(100, Math.max(0, done / total * 100));
1076
+ const bgImage = isFounder ? founderBgUrl : citizenBgUrl;
1077
+ const textColor = isFounder ? "white" : "#111111";
1078
+ const shadowStyle = isFounder ? "0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 8px 10px -6px rgba(0, 0, 0, 0.1)" : "0 20px 25px -5px rgba(241, 86, 63, 0.5), 0 8px 10px -6px rgba(241, 86, 63, 0.1)";
1079
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1080
+ "div",
1081
+ {
1082
+ className: `zo-passport-card ${className}`,
1083
+ style: {
1084
+ position: "relative",
1085
+ width: "234px",
1086
+ height: "300px",
1087
+ borderRadius: "0 20px 20px 0",
1088
+ overflow: "hidden",
1089
+ fontFamily: "Rubik, system-ui, sans-serif",
1090
+ boxShadow: shadowStyle
1091
+ },
1092
+ children: [
1093
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { position: "absolute", inset: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1094
+ "img",
1095
+ {
1096
+ src: bgImage,
1097
+ alt: "Passport Background",
1098
+ style: {
1099
+ width: "100%",
1100
+ height: "100%",
1101
+ objectFit: "cover"
1102
+ }
1103
+ }
1104
+ ) }),
1105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1106
+ "div",
1107
+ {
1108
+ style: {
1109
+ position: "absolute",
1110
+ inset: 0,
1111
+ display: "flex",
1112
+ alignItems: "center",
1113
+ justifyContent: "center",
1114
+ top: "-10px"
1115
+ },
1116
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1117
+ ZoProgressRing,
1118
+ {
1119
+ progress,
1120
+ size: 140,
1121
+ strokeWidth: 4,
1122
+ primaryColor: isFounder ? "#FFFFFF" : "#111111",
1123
+ secondaryColor: isFounder ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.1)"
1124
+ }
1125
+ )
1126
+ }
1127
+ ),
1128
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1129
+ "div",
1130
+ {
1131
+ style: {
1132
+ position: "absolute",
1133
+ inset: 0,
1134
+ display: "flex",
1135
+ alignItems: "center",
1136
+ justifyContent: "center",
1137
+ top: "-10px"
1138
+ },
1139
+ children: [
1140
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1141
+ "div",
1142
+ {
1143
+ style: {
1144
+ width: "120px",
1145
+ height: "120px",
1146
+ borderRadius: "50%",
1147
+ overflow: "hidden"
1148
+ },
1149
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ZoAvatar2, { src: avatar, name, size: 120 })
1150
+ }
1151
+ ),
1152
+ isFounder && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1153
+ "div",
1154
+ {
1155
+ style: {
1156
+ position: "absolute",
1157
+ bottom: "84px",
1158
+ right: "60px"
1159
+ },
1160
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FounderBadge, { size: 32 })
1161
+ }
1162
+ )
1163
+ ]
1164
+ }
1165
+ ),
1166
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1167
+ "div",
1168
+ {
1169
+ style: {
1170
+ position: "absolute",
1171
+ left: 0,
1172
+ right: 0,
1173
+ bottom: "16px",
1174
+ textAlign: "center",
1175
+ padding: "0 16px",
1176
+ display: "flex",
1177
+ flexDirection: "column",
1178
+ gap: "4px"
1179
+ },
1180
+ children: [
1181
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1182
+ "p",
1183
+ {
1184
+ style: {
1185
+ margin: 0,
1186
+ fontWeight: 700,
1187
+ fontSize: "18px",
1188
+ lineHeight: "24px",
1189
+ color: textColor,
1190
+ overflow: "hidden",
1191
+ textOverflow: "ellipsis",
1192
+ whiteSpace: "nowrap"
1193
+ },
1194
+ children: name
1195
+ }
1196
+ ),
1197
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1198
+ "p",
1199
+ {
1200
+ style: {
1201
+ margin: 0,
1202
+ fontSize: "10px",
1203
+ lineHeight: "14px",
1204
+ letterSpacing: "0.05em",
1205
+ textTransform: "uppercase",
1206
+ color: textColor,
1207
+ opacity: 0.7
1208
+ },
1209
+ children: isFounder ? "Founder of Zo World" : "Citizen of Zo World"
1210
+ }
1211
+ )
1212
+ ]
1213
+ }
1214
+ )
1215
+ ]
1216
+ }
1217
+ );
1218
+ };
1219
+
1220
+ // src/components/ZoPassportPage.tsx
1221
+ var import_react4 = require("react");
1222
+
1223
+ // src/components/wallet/WalletCardWeb.tsx
1224
+ var import_react2 = require("react");
1225
+
1226
+ // src/lib/utils/wallet.ts
1227
+ var formatBalance = (balance) => {
1228
+ if (balance === 0) return "0";
1229
+ const formatted = balance.toLocaleString("en-US", {
1230
+ minimumFractionDigits: 0,
1231
+ maximumFractionDigits: 2
1232
+ });
1233
+ return formatted;
1234
+ };
1235
+ var formatWalletAddress = (address) => {
1236
+ if (!address || address.length < 8) return address;
1237
+ return `${address.slice(0, 4)}...${address.slice(-4)}`;
1238
+ };
1239
+ var formatNickname = (nickname) => {
1240
+ if (!nickname) return "";
1241
+ return nickname.startsWith("@") ? nickname : `@${nickname}`;
1242
+ };
1243
+
1244
+ // src/lib/utils/styles.ts
1245
+ var styleRefCounts = /* @__PURE__ */ new Map();
1246
+ function injectStyles(styleId, css) {
1247
+ if (typeof document === "undefined") {
1248
+ return () => {
1249
+ };
1250
+ }
1251
+ const currentCount = styleRefCounts.get(styleId) || 0;
1252
+ if (currentCount === 0) {
1253
+ const style = document.createElement("style");
1254
+ style.id = styleId;
1255
+ style.textContent = css;
1256
+ document.head.appendChild(style);
1257
+ }
1258
+ styleRefCounts.set(styleId, currentCount + 1);
1259
+ return () => {
1260
+ const count = styleRefCounts.get(styleId) || 0;
1261
+ if (count <= 1) {
1262
+ const style = document.getElementById(styleId);
1263
+ if (style) {
1264
+ style.remove();
1265
+ }
1266
+ styleRefCounts.delete(styleId);
1267
+ } else {
1268
+ styleRefCounts.set(styleId, count - 1);
1269
+ }
1270
+ };
1271
+ }
1272
+
1273
+ // src/components/wallet/WalletCardWeb.tsx
1274
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1275
+ var ZO_COIN_URL = "/zo-coin.gif";
1276
+ var STYLE_ID = "zo-wallet-card-web-styles";
1277
+ var CSS = `
1278
+ .zo-wallet-card-web {
1279
+ position: relative;
1280
+ width: 100%;
1281
+ max-width: 320px;
1282
+ aspect-ratio: 1.6;
1283
+ border-radius: 16px;
1284
+ overflow: hidden;
1285
+ background: linear-gradient(145deg, #1a1a1a 0%, #0d0d0d 100%);
1286
+ border: 1px solid rgba(255,255,255,0.1);
1287
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
1288
+ cursor: pointer;
1289
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
1290
+ font-family: 'Rubik', system-ui, sans-serif;
1291
+ }
1292
+
1293
+ .zo-wallet-card-web:hover {
1294
+ transform: translateY(-2px);
1295
+ box-shadow: 0 12px 40px rgba(0,0,0,0.5);
1296
+ }
1297
+
1298
+ .zo-wallet-card-web.is-open {
1299
+ transform: translateY(-20px);
1300
+ }
1301
+
1302
+ .zo-wallet-card-web-inner {
1303
+ position: relative;
1304
+ height: 100%;
1305
+ display: flex;
1306
+ flex-direction: column;
1307
+ padding: 20px;
1308
+ z-index: 1;
1309
+ }
1310
+
1311
+ .zo-wallet-card-web-shine {
1312
+ position: absolute;
1313
+ top: 0;
1314
+ left: -100%;
1315
+ width: 50%;
1316
+ height: 100%;
1317
+ background: linear-gradient(
1318
+ 90deg,
1319
+ transparent,
1320
+ rgba(255,255,255,0.05),
1321
+ transparent
1322
+ );
1323
+ animation: zo-wallet-shine 3s ease-in-out infinite;
1324
+ pointer-events: none;
1325
+ }
1326
+
1327
+ @keyframes zo-wallet-shine {
1328
+ 0% { left: -100%; }
1329
+ 50% { left: 100%; }
1330
+ 100% { left: 100%; }
1331
+ }
1332
+
1333
+ .zo-wallet-card-web-balance-row {
1334
+ display: flex;
1335
+ align-items: center;
1336
+ justify-content: space-between;
1337
+ }
1338
+
1339
+ .zo-wallet-card-web-balance-wrapper {
1340
+ display: flex;
1341
+ align-items: baseline;
1342
+ gap: 6px;
1343
+ }
1344
+
1345
+ .zo-wallet-card-web-balance {
1346
+ font-size: 28px;
1347
+ font-weight: 700;
1348
+ color: #fff;
1349
+ margin: 0;
1350
+ }
1351
+
1352
+ .zo-wallet-card-web-currency {
1353
+ font-size: 14px;
1354
+ color: rgba(255,255,255,0.5);
1355
+ }
1356
+
1357
+ .zo-wallet-card-web-coin {
1358
+ width: 32px;
1359
+ height: 32px;
1360
+ border-radius: 50%;
1361
+ }
1362
+
1363
+ .zo-wallet-card-web-spacer {
1364
+ flex: 1;
1365
+ }
1366
+
1367
+ .zo-wallet-card-web-user {
1368
+ display: flex;
1369
+ align-items: center;
1370
+ gap: 12px;
1371
+ padding-top: 12px;
1372
+ border-top: 1px solid rgba(255,255,255,0.08);
1373
+ }
1374
+
1375
+ .zo-wallet-card-web-avatar {
1376
+ width: 36px;
1377
+ height: 36px;
1378
+ border-radius: 50%;
1379
+ object-fit: cover;
1380
+ border: 2px solid rgba(255,255,255,0.15);
1381
+ }
1382
+
1383
+ .zo-wallet-card-web-avatar-placeholder {
1384
+ width: 36px;
1385
+ height: 36px;
1386
+ border-radius: 50%;
1387
+ background: linear-gradient(135deg, #333 0%, #222 100%);
1388
+ display: flex;
1389
+ align-items: center;
1390
+ justify-content: center;
1391
+ font-size: 14px;
1392
+ color: rgba(255,255,255,0.5);
1393
+ border: 2px solid rgba(255,255,255,0.15);
1394
+ }
1395
+
1396
+ .zo-wallet-card-web-user-info {
1397
+ flex: 1;
1398
+ min-width: 0;
1399
+ }
1400
+
1401
+ .zo-wallet-card-web-username {
1402
+ font-size: 14px;
1403
+ font-weight: 600;
1404
+ color: #fff;
1405
+ margin: 0 0 2px 0;
1406
+ white-space: nowrap;
1407
+ overflow: hidden;
1408
+ text-overflow: ellipsis;
1409
+ }
1410
+
1411
+ .zo-wallet-card-web-address {
1412
+ font-size: 11px;
1413
+ color: rgba(255,255,255,0.4);
1414
+ margin: 0;
1415
+ font-family: 'SF Mono', Monaco, monospace;
1416
+ }
1417
+
1418
+ .zo-wallet-card-web-loading {
1419
+ opacity: 0.5;
1420
+ }
1421
+
1422
+ .zo-wallet-card-web-cover {
1423
+ position: absolute;
1424
+ inset: 0;
1425
+ background: linear-gradient(145deg, #2d2d2d 0%, #1a1a1a 100%);
1426
+ display: flex;
1427
+ align-items: center;
1428
+ justify-content: center;
1429
+ transition: opacity 0.3s ease;
1430
+ z-index: 2;
1431
+ }
1432
+
1433
+ .zo-wallet-card-web.is-open .zo-wallet-card-web-cover {
1434
+ opacity: 0;
1435
+ pointer-events: none;
1436
+ }
1437
+
1438
+ .zo-wallet-card-web-cover-text {
1439
+ font-size: 14px;
1440
+ color: rgba(255,255,255,0.6);
1441
+ font-weight: 500;
1442
+ }
1443
+
1444
+ .zo-wallet-card-web-stitch {
1445
+ position: absolute;
1446
+ inset: 6px;
1447
+ border: 1px dashed rgba(255,255,255,0.15);
1448
+ border-radius: 12px;
1449
+ pointer-events: none;
1450
+ }
1451
+ `;
1452
+ var WalletCardWeb = (0, import_react2.memo)(({
1453
+ balance,
1454
+ user,
1455
+ isOpen = false,
1456
+ onToggle,
1457
+ isLoading = false
1458
+ }) => {
1459
+ (0, import_react2.useEffect)(() => {
1460
+ const cleanup = injectStyles(STYLE_ID, CSS);
1461
+ return cleanup;
1462
+ }, []);
1463
+ const displayName = user.nickname ? formatNickname(user.nickname) : user.first_name || "You";
1464
+ const walletText = `${user.first_name || "Your"}'s wallet`;
1465
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1466
+ "div",
1467
+ {
1468
+ className: `zo-wallet-card-web ${isOpen ? "is-open" : ""} ${isLoading ? "zo-wallet-card-web-loading" : ""}`,
1469
+ onClick: onToggle,
1470
+ children: [
1471
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zo-wallet-card-web-shine" }),
1472
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zo-wallet-card-web-stitch" }),
1473
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "zo-wallet-card-web-inner", children: [
1474
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "zo-wallet-card-web-balance-row", children: [
1475
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "zo-wallet-card-web-balance-wrapper", children: [
1476
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "zo-wallet-card-web-balance", children: isLoading ? "..." : formatBalance(balance) }),
1477
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "zo-wallet-card-web-currency", children: "$Zo" })
1478
+ ] }),
1479
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1480
+ "img",
1481
+ {
1482
+ src: ZO_COIN_URL,
1483
+ alt: "Zo Coin",
1484
+ className: "zo-wallet-card-web-coin"
1485
+ }
1486
+ )
1487
+ ] }),
1488
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zo-wallet-card-web-spacer" }),
1489
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "zo-wallet-card-web-user", children: [
1490
+ user.avatar?.image ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1491
+ "img",
1492
+ {
1493
+ src: user.avatar.image,
1494
+ alt: displayName,
1495
+ className: "zo-wallet-card-web-avatar"
1496
+ }
1497
+ ) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zo-wallet-card-web-avatar-placeholder", children: (user.first_name?.[0] || "Z").toUpperCase() }),
1498
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "zo-wallet-card-web-user-info", children: [
1499
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "zo-wallet-card-web-username", children: displayName }),
1500
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "zo-wallet-card-web-address", children: formatWalletAddress(user.wallet_address || "") })
1501
+ ] })
1502
+ ] })
1503
+ ] }),
1504
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zo-wallet-card-web-cover", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "zo-wallet-card-web-cover-text", children: walletText }) })
1505
+ ]
1506
+ }
1507
+ );
1508
+ });
1509
+ WalletCardWeb.displayName = "WalletCardWeb";
1510
+
1511
+ // src/components/wallet/WalletFullPage.tsx
1512
+ var import_react3 = require("react");
1513
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1514
+ var ZO_COIN_URL2 = "/zo-coin.gif";
1515
+ var STYLE_ID2 = "zo-wallet-fullpage-styles";
1516
+ var CSS2 = `
1517
+ .zo-wallet-fullpage {
1518
+ position: fixed;
1519
+ inset: 0;
1520
+ background: #000;
1521
+ z-index: 1000;
1522
+ display: flex;
1523
+ flex-direction: column;
1524
+ font-family: 'Rubik', system-ui, sans-serif;
1525
+ color: #fff;
1526
+ overflow-y: auto;
1527
+ -webkit-overflow-scrolling: touch;
1528
+ }
1529
+
1530
+ .zo-wallet-header {
1531
+ display: flex;
1532
+ align-items: center;
1533
+ padding: 16px 20px;
1534
+ border-bottom: 1px solid rgba(255,255,255,0.1);
1535
+ position: sticky;
1536
+ top: 0;
1537
+ background: #000;
1538
+ z-index: 10;
1539
+ }
1540
+
1541
+ .zo-wallet-back {
1542
+ background: none;
1543
+ border: none;
1544
+ color: #fff;
1545
+ font-size: 24px;
1546
+ cursor: pointer;
1547
+ padding: 8px;
1548
+ margin-right: 12px;
1549
+ display: flex;
1550
+ align-items: center;
1551
+ justify-content: center;
1552
+ }
1553
+
1554
+ .zo-wallet-header-title {
1555
+ font-size: 18px;
1556
+ font-weight: 600;
1557
+ margin: 0;
1558
+ }
1559
+
1560
+ .zo-wallet-content {
1561
+ flex: 1;
1562
+ display: flex;
1563
+ flex-direction: column;
1564
+ align-items: center;
1565
+ padding: 24px 16px 60px;
1566
+ }
1567
+
1568
+ /* Wallet Card - Leather Style */
1569
+ .zo-wallet-leather-card {
1570
+ position: relative;
1571
+ width: 100%;
1572
+ max-width: 360px;
1573
+ aspect-ratio: 1.6;
1574
+ border-radius: 16px;
1575
+ overflow: hidden;
1576
+ background: linear-gradient(145deg, #2d2d2d 0%, #1a1a1a 100%);
1577
+ border: 2px dashed rgba(255,255,255,0.15);
1578
+ box-shadow:
1579
+ 0 20px 40px rgba(0,0,0,0.5),
1580
+ inset 0 1px 0 rgba(255,255,255,0.1);
1581
+ margin-bottom: 24px;
1582
+ }
1583
+
1584
+ .zo-wallet-leather-bg {
1585
+ position: absolute;
1586
+ inset: 0;
1587
+ background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" patternUnits="userSpaceOnUse" width="100" height="100"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.03)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.03)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.02)"/><circle cx="10" cy="60" r="0.5" fill="rgba(255,255,255,0.02)"/><circle cx="90" cy="40" r="0.5" fill="rgba(255,255,255,0.02)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
1588
+ opacity: 0.5;
1589
+ }
1590
+
1591
+ .zo-wallet-leather-content {
1592
+ position: relative;
1593
+ z-index: 1;
1594
+ height: 100%;
1595
+ display: flex;
1596
+ flex-direction: column;
1597
+ padding: 24px;
1598
+ }
1599
+
1600
+ /* Balance Section */
1601
+ .zo-wallet-balance-section {
1602
+ display: flex;
1603
+ align-items: center;
1604
+ justify-content: space-between;
1605
+ margin-bottom: auto;
1606
+ }
1607
+
1608
+ .zo-wallet-balance-left {
1609
+ display: flex;
1610
+ align-items: baseline;
1611
+ gap: 8px;
1612
+ }
1613
+
1614
+ .zo-wallet-balance-amount {
1615
+ font-size: 42px;
1616
+ font-weight: 700;
1617
+ margin: 0;
1618
+ color: #fff;
1619
+ }
1620
+
1621
+ .zo-wallet-balance-currency {
1622
+ font-size: 16px;
1623
+ color: rgba(255,255,255,0.6);
1624
+ }
1625
+
1626
+ .zo-wallet-coin {
1627
+ width: 48px;
1628
+ height: 48px;
1629
+ border-radius: 50%;
1630
+ }
1631
+
1632
+ /* User Section */
1633
+ .zo-wallet-user-section {
1634
+ display: flex;
1635
+ align-items: center;
1636
+ gap: 12px;
1637
+ padding-top: 16px;
1638
+ border-top: 1px solid rgba(255,255,255,0.1);
1639
+ }
1640
+
1641
+ .zo-wallet-avatar {
1642
+ width: 40px;
1643
+ height: 40px;
1644
+ border-radius: 50%;
1645
+ object-fit: cover;
1646
+ border: 2px solid rgba(255,255,255,0.2);
1647
+ }
1648
+
1649
+ .zo-wallet-user-details {
1650
+ flex: 1;
1651
+ }
1652
+
1653
+ .zo-wallet-user-name {
1654
+ font-size: 14px;
1655
+ font-weight: 600;
1656
+ margin: 0 0 2px 0;
1657
+ color: #fff;
1658
+ }
1659
+
1660
+ .zo-wallet-user-address {
1661
+ font-size: 11px;
1662
+ color: rgba(255,255,255,0.5);
1663
+ margin: 0;
1664
+ font-family: monospace;
1665
+ }
1666
+
1667
+ /* Wallet Label */
1668
+ .zo-wallet-label {
1669
+ text-align: center;
1670
+ padding: 20px;
1671
+ background: rgba(255,255,255,0.03);
1672
+ border: 1px solid rgba(255,255,255,0.08);
1673
+ border-radius: 12px;
1674
+ margin-top: 8px;
1675
+ }
1676
+
1677
+ .zo-wallet-label-text {
1678
+ font-size: 14px;
1679
+ color: rgba(255,255,255,0.6);
1680
+ margin: 0;
1681
+ }
1682
+
1683
+ /* Info Section */
1684
+ .zo-wallet-info-section {
1685
+ width: 100%;
1686
+ max-width: 360px;
1687
+ margin-top: 24px;
1688
+ }
1689
+
1690
+ .zo-wallet-info-title {
1691
+ font-size: 12px;
1692
+ font-weight: 600;
1693
+ color: rgba(255,255,255,0.5);
1694
+ margin: 0 0 12px 0;
1695
+ text-transform: uppercase;
1696
+ letter-spacing: 0.1em;
1697
+ }
1698
+
1699
+ .zo-wallet-info-card {
1700
+ background: rgba(255,255,255,0.03);
1701
+ border: 1px solid rgba(255,255,255,0.08);
1702
+ border-radius: 12px;
1703
+ padding: 16px;
1704
+ }
1705
+
1706
+ .zo-wallet-info-text {
1707
+ font-size: 13px;
1708
+ color: rgba(255,255,255,0.6);
1709
+ line-height: 1.6;
1710
+ margin: 0;
1711
+ }
1712
+
1713
+ /* Stitching Effect */
1714
+ .zo-wallet-stitch {
1715
+ position: absolute;
1716
+ border: 1px dashed rgba(255,255,255,0.2);
1717
+ border-radius: 12px;
1718
+ inset: 8px;
1719
+ pointer-events: none;
1720
+ }
1721
+
1722
+ @media (min-width: 768px) {
1723
+ .zo-wallet-leather-card {
1724
+ max-width: 420px;
1725
+ }
1726
+
1727
+ .zo-wallet-balance-amount {
1728
+ font-size: 56px;
1729
+ }
1730
+
1731
+ .zo-wallet-coin {
1732
+ width: 56px;
1733
+ height: 56px;
1734
+ }
1735
+
1736
+ .zo-wallet-leather-content {
1737
+ padding: 32px;
1738
+ }
1739
+ }
1740
+ `;
1741
+ var WalletFullPage = ({
1742
+ user,
1743
+ balance,
1744
+ onClose,
1745
+ zoCoinUrl = ZO_COIN_URL2,
1746
+ isLoading = false,
1747
+ onRefresh
1748
+ }) => {
1749
+ (0, import_react3.useEffect)(() => {
1750
+ const cleanupStyles = injectStyles(STYLE_ID2, CSS2);
1751
+ document.body.style.overflow = "hidden";
1752
+ return () => {
1753
+ cleanupStyles();
1754
+ document.body.style.overflow = "";
1755
+ };
1756
+ }, []);
1757
+ const displayName = user?.first_name || "Citizen";
1758
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-fullpage", children: [
1759
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-header", children: [
1760
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "zo-wallet-back", onClick: onClose, children: "\u2190" }),
1761
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("h1", { className: "zo-wallet-header-title", children: [
1762
+ displayName,
1763
+ "'s Wallet"
1764
+ ] }),
1765
+ onRefresh && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1766
+ "button",
1767
+ {
1768
+ className: "zo-wallet-refresh",
1769
+ onClick: onRefresh,
1770
+ disabled: isLoading,
1771
+ style: {
1772
+ marginLeft: "auto",
1773
+ background: "none",
1774
+ border: "1px solid rgba(255,255,255,0.2)",
1775
+ borderRadius: "8px",
1776
+ padding: "8px 12px",
1777
+ color: "#fff",
1778
+ cursor: isLoading ? "wait" : "pointer",
1779
+ fontSize: "12px",
1780
+ opacity: isLoading ? 0.5 : 1
1781
+ },
1782
+ children: isLoading ? "..." : "\u21BB Refresh"
1783
+ }
1784
+ )
1785
+ ] }),
1786
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-content", children: [
1787
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-leather-card", children: [
1788
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "zo-wallet-leather-bg" }),
1789
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "zo-wallet-stitch" }),
1790
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-leather-content", children: [
1791
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-balance-section", children: [
1792
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-balance-left", children: [
1793
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "zo-wallet-balance-amount", style: { opacity: isLoading ? 0.5 : 1 }, children: isLoading ? "..." : balance }),
1794
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "zo-wallet-balance-currency", children: "$Zo" })
1795
+ ] }),
1796
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1797
+ "img",
1798
+ {
1799
+ src: zoCoinUrl,
1800
+ alt: "Zo Coin",
1801
+ className: "zo-wallet-coin",
1802
+ style: {
1803
+ animation: isLoading ? "spin 1s linear infinite" : "none"
1804
+ }
1805
+ }
1806
+ )
1807
+ ] }),
1808
+ user && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-user-section", children: [
1809
+ (user.avatar?.image || user.pfp_image) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1810
+ "img",
1811
+ {
1812
+ src: user.avatar?.image || user.pfp_image,
1813
+ alt: displayName,
1814
+ className: "zo-wallet-avatar"
1815
+ }
1816
+ ),
1817
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-user-details", children: [
1818
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "zo-wallet-user-name", children: displayName }),
1819
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "zo-wallet-user-address", children: formatWalletAddress(user.wallet_address || "") })
1820
+ ] })
1821
+ ] })
1822
+ ] })
1823
+ ] }),
1824
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "zo-wallet-label", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "zo-wallet-label-text", children: [
1825
+ displayName,
1826
+ "'s wallet"
1827
+ ] }) }),
1828
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "zo-wallet-info-section", children: [
1829
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "zo-wallet-info-title", children: "About $Zo" }),
1830
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "zo-wallet-info-card", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "zo-wallet-info-text", children: "Earn $Zo tokens by completing quests, exploring locations, booking stays, and participating in the Zo World ecosystem." }) })
1831
+ ] })
1832
+ ] })
1833
+ ] });
1834
+ };
1835
+
1836
+ // assets/wallet/constants.ts
1837
+ var WALLET_DIMENSIONS = {
1838
+ cardAspectRatio: 312 / 200,
1839
+ coverAspectRatio: 312 / 120,
1840
+ cardBorderRadius: 16,
1841
+ innerBorderRadius: 12,
1842
+ avatarSize: 32,
1843
+ tokenSize: 16,
1844
+ tokenVideoSize: 24
1845
+ };
1846
+
1847
+ // assets/index.ts
1848
+ var CULTURE_STICKERS = {
1849
+ travel: "/cultural-stickers/Travel&Adventure.png",
1850
+ design: "/cultural-stickers/Design.png",
1851
+ tech: "/cultural-stickers/Science&Technology.png",
1852
+ food: "/cultural-stickers/Food.png",
1853
+ music: "/cultural-stickers/Music&Entertainment.png",
1854
+ photography: "/cultural-stickers/Photography.png",
1855
+ fitness: "/cultural-stickers/Health&Fitness.png",
1856
+ sports: "/cultural-stickers/Sport.png",
1857
+ literature: "/cultural-stickers/Literature&Stories.png",
1858
+ cinema: "/cultural-stickers/Television&Cinema.png",
1859
+ spiritual: "/cultural-stickers/Spiritual.png",
1860
+ nature: "/cultural-stickers/Nature&Wildlife.png",
1861
+ business: "/cultural-stickers/Business.png",
1862
+ law: "/cultural-stickers/Law.png",
1863
+ lifestyle: "/cultural-stickers/Home&Lifestyle.png",
1864
+ gaming: "/cultural-stickers/Game.png",
1865
+ stories: "/cultural-stickers/Stories&Journal.png"
1866
+ };
1867
+ var CULTURES = [
1868
+ { id: "travel", name: "Travel & Adventure", icon: CULTURE_STICKERS.travel },
1869
+ { id: "design", name: "Design", icon: CULTURE_STICKERS.design },
1870
+ { id: "tech", name: "Science & Technology", icon: CULTURE_STICKERS.tech },
1871
+ { id: "food", name: "Food", icon: CULTURE_STICKERS.food },
1872
+ { id: "music", name: "Music & Entertainment", icon: CULTURE_STICKERS.music },
1873
+ { id: "photography", name: "Photography", icon: CULTURE_STICKERS.photography },
1874
+ { id: "fitness", name: "Health & Fitness", icon: CULTURE_STICKERS.fitness },
1875
+ { id: "sports", name: "Sport", icon: CULTURE_STICKERS.sports },
1876
+ { id: "literature", name: "Literature & Stories", icon: CULTURE_STICKERS.literature },
1877
+ { id: "cinema", name: "Television & Cinema", icon: CULTURE_STICKERS.cinema },
1878
+ { id: "spiritual", name: "Spiritual", icon: CULTURE_STICKERS.spiritual },
1879
+ { id: "nature", name: "Nature & Wildlife", icon: CULTURE_STICKERS.nature },
1880
+ { id: "business", name: "Business", icon: CULTURE_STICKERS.business },
1881
+ { id: "law", name: "Law", icon: CULTURE_STICKERS.law },
1882
+ { id: "lifestyle", name: "Home & Lifestyle", icon: CULTURE_STICKERS.lifestyle },
1883
+ { id: "gaming", name: "Game", icon: CULTURE_STICKERS.gaming },
1884
+ { id: "stories", name: "Stories & Journal", icon: CULTURE_STICKERS.stories }
1885
+ ];
1886
+
1887
+ // src/components/ZoPassportPage.tsx
1888
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1889
+ var CULTURE_STICKER_MAP = {
1890
+ // Base mappings from CULTURE_STICKERS
1891
+ ...CULTURE_STICKERS,
1892
+ // Aliases for common variations
1893
+ "game": CULTURE_STICKERS.gaming,
1894
+ "games": CULTURE_STICKERS.gaming,
1895
+ "sport": CULTURE_STICKERS.sports,
1896
+ // Full name variants (for when API returns full names)
1897
+ "travel & adventure": CULTURE_STICKERS.travel,
1898
+ "science & technology": CULTURE_STICKERS.tech,
1899
+ "music & entertainment": CULTURE_STICKERS.music,
1900
+ "health & fitness": CULTURE_STICKERS.fitness,
1901
+ "literature & stories": CULTURE_STICKERS.literature,
1902
+ "television & cinema": CULTURE_STICKERS.cinema,
1903
+ "nature & wildlife": CULTURE_STICKERS.nature,
1904
+ "home & lifestyle": CULTURE_STICKERS.lifestyle,
1905
+ "stories & journal": CULTURE_STICKERS.stories,
1906
+ // Special cultures
1907
+ "follow-your-heart": "/cultural-stickers/FollowYourHeart.png",
1908
+ "followyourheart": "/cultural-stickers/FollowYourHeart.png",
1909
+ "follow your heart": "/cultural-stickers/FollowYourHeart.png",
1910
+ "law & order": CULTURE_STICKERS.law
1911
+ };
1912
+ var getCultureSticker = (culture) => {
1913
+ if (typeof culture === "string") {
1914
+ const keyMatch = CULTURE_STICKER_MAP[culture.toLowerCase()];
1915
+ if (keyMatch) return keyMatch;
1916
+ return "/cultural-stickers/FollowYourHeart.png";
1917
+ }
1918
+ if (culture.key) {
1919
+ const keyMatch = CULTURE_STICKER_MAP[culture.key.toLowerCase()];
1920
+ if (keyMatch) return keyMatch;
1921
+ }
1922
+ if (culture.name) {
1923
+ const nameMatch = CULTURE_STICKER_MAP[culture.name.toLowerCase()];
1924
+ if (nameMatch) return nameMatch;
1925
+ const nameLower = culture.name.toLowerCase();
1926
+ for (const [mapKey, path] of Object.entries(CULTURE_STICKER_MAP)) {
1927
+ if (nameLower.includes(mapKey) || mapKey.includes(nameLower)) {
1928
+ return path;
1929
+ }
1930
+ }
1931
+ }
1932
+ if (culture.icon && !culture.icon.includes("proxy.cdn.zo.xyz/profile/culture")) {
1933
+ return culture.icon;
1934
+ }
1935
+ return "/cultural-stickers/FollowYourHeart.png";
1936
+ };
1937
+ var getCultureName = (culture) => {
1938
+ if (typeof culture === "string") return culture;
1939
+ return culture.name || culture.key || "Culture";
1940
+ };
1941
+ var getCultureKey = (culture) => {
1942
+ if (typeof culture === "string") return culture;
1943
+ return culture.key || culture.name || "";
1944
+ };
1945
+ var formatDate = (dateStr) => {
1946
+ if (!dateStr) return "Not set";
1947
+ try {
1948
+ const date = new Date(dateStr);
1949
+ return date.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
1950
+ } catch {
1951
+ return dateStr;
1952
+ }
1953
+ };
1954
+ var formatPhone = (phone, countryCode) => {
1955
+ if (!phone) return "Not set";
1956
+ return countryCode ? `${countryCode}${phone}` : phone;
1957
+ };
1958
+ var STYLE_ID3 = "zo-passport-responsive-styles";
1959
+ var CSS3 = `
1960
+ .zo-passport-container {
1961
+ width: 100%;
1962
+ min-height: auto;
1963
+ background: #000000;
1964
+ color: #FFFFFF;
1965
+ font-family: 'Rubik', system-ui, sans-serif;
1966
+ padding: 16px;
1967
+ padding-bottom: 60px;
1968
+ box-sizing: border-box;
1969
+ }
1970
+
1971
+ .zo-passport-header {
1972
+ margin-bottom: 24px;
1973
+ text-align: center;
1974
+ }
1975
+
1976
+ .zo-passport-title {
1977
+ font-size: 24px;
1978
+ font-weight: 700;
1979
+ margin: 0;
1980
+ font-family: 'Syne', system-ui, sans-serif;
1981
+ }
1982
+
1983
+ .zo-passport-subtitle {
1984
+ font-size: 12px;
1985
+ color: rgba(255, 255, 255, 0.6);
1986
+ margin: 4px 0 0 0;
1987
+ }
1988
+
1989
+ .zo-passport-grid {
1990
+ display: flex;
1991
+ flex-direction: column;
1992
+ gap: 24px;
1993
+ max-width: 1200px;
1994
+ margin: 0 auto;
1995
+ }
1996
+
1997
+ .zo-passport-card-section {
1998
+ display: flex;
1999
+ justify-content: center;
2000
+ }
2001
+
2002
+ .zo-passport-section {
2003
+ background: rgba(255, 255, 255, 0.03);
2004
+ border-radius: 16px;
2005
+ padding: 20px;
2006
+ }
2007
+
2008
+ .zo-passport-section-title {
2009
+ font-size: 14px;
2010
+ font-weight: 600;
2011
+ margin: 0 0 16px 0;
2012
+ letter-spacing: 0.02em;
2013
+ color: rgba(255, 255, 255, 0.9);
2014
+ }
2015
+
2016
+ .zo-passport-info-list {
2017
+ display: flex;
2018
+ flex-direction: column;
2019
+ gap: 16px;
2020
+ }
2021
+
2022
+ .zo-passport-info-row {
2023
+ display: flex;
2024
+ align-items: flex-start;
2025
+ gap: 12px;
2026
+ }
2027
+
2028
+ .zo-passport-info-icon {
2029
+ font-size: 16px;
2030
+ width: 20px;
2031
+ text-align: center;
2032
+ flex-shrink: 0;
2033
+ }
2034
+
2035
+ .zo-passport-info-content {
2036
+ display: flex;
2037
+ flex-direction: column;
2038
+ gap: 2px;
2039
+ min-width: 0;
2040
+ }
2041
+
2042
+ .zo-passport-info-label {
2043
+ font-size: 10px;
2044
+ color: rgba(255, 255, 255, 0.5);
2045
+ text-transform: uppercase;
2046
+ letter-spacing: 0.05em;
2047
+ }
2048
+
2049
+ .zo-passport-info-value {
2050
+ font-size: 13px;
2051
+ color: #FFFFFF;
2052
+ word-break: break-word;
2053
+ }
2054
+
2055
+ .zo-passport-wallet-wrapper {
2056
+ margin-bottom: 16px;
2057
+ }
2058
+
2059
+ .zo-passport-cultures {
2060
+ background: rgba(255, 255, 255, 0.05);
2061
+ border: 1px solid rgba(255, 255, 255, 0.1);
2062
+ border-radius: 12px;
2063
+ padding: 16px;
2064
+ }
2065
+
2066
+ .zo-passport-cultures-header {
2067
+ margin-bottom: 12px;
2068
+ }
2069
+
2070
+ .zo-passport-cultures-title {
2071
+ font-size: 12px;
2072
+ color: rgba(255, 255, 255, 0.7);
2073
+ }
2074
+
2075
+ .zo-passport-culture-tags {
2076
+ display: flex;
2077
+ flex-wrap: wrap;
2078
+ gap: 8px;
2079
+ }
2080
+
2081
+ .zo-passport-culture-tag {
2082
+ display: flex;
2083
+ align-items: center;
2084
+ gap: 6px;
2085
+ background: rgba(255, 255, 255, 0.08);
2086
+ border: 1px solid rgba(255, 255, 255, 0.15);
2087
+ border-radius: 20px;
2088
+ padding: 6px 10px;
2089
+ }
2090
+
2091
+ .zo-passport-culture-sticker {
2092
+ width: 16px;
2093
+ height: 16px;
2094
+ object-fit: contain;
2095
+ }
2096
+
2097
+ .zo-passport-culture-name {
2098
+ font-size: 11px;
2099
+ color: #FFFFFF;
2100
+ font-weight: 500;
2101
+ }
2102
+
2103
+ .zo-passport-culture-remove {
2104
+ background: none;
2105
+ border: none;
2106
+ color: rgba(255, 255, 255, 0.5);
2107
+ font-size: 14px;
2108
+ cursor: pointer;
2109
+ padding: 0;
2110
+ line-height: 1;
2111
+ margin-left: 2px;
2112
+ }
2113
+
2114
+ .zo-passport-loading {
2115
+ color: rgba(255, 255, 255, 0.6);
2116
+ font-size: 14px;
2117
+ text-align: center;
2118
+ padding: 40px;
2119
+ }
2120
+
2121
+ /* Tablet (768px+) */
2122
+ @media (min-width: 768px) {
2123
+ .zo-passport-container {
2124
+ padding: 24px;
2125
+ }
2126
+
2127
+ .zo-passport-header {
2128
+ text-align: left;
2129
+ margin-bottom: 32px;
2130
+ }
2131
+
2132
+ .zo-passport-title {
2133
+ font-size: 28px;
2134
+ }
2135
+
2136
+ .zo-passport-subtitle {
2137
+ font-size: 14px;
2138
+ }
2139
+
2140
+ .zo-passport-grid {
2141
+ display: grid;
2142
+ grid-template-columns: auto 1fr;
2143
+ gap: 32px;
2144
+ }
2145
+
2146
+ .zo-passport-card-section {
2147
+ justify-content: flex-start;
2148
+ }
2149
+
2150
+ .zo-passport-content-columns {
2151
+ display: grid;
2152
+ grid-template-columns: 1fr 1fr;
2153
+ gap: 24px;
2154
+ }
2155
+
2156
+ .zo-passport-section-title {
2157
+ font-size: 15px;
2158
+ margin-bottom: 20px;
2159
+ }
2160
+
2161
+ .zo-passport-info-icon {
2162
+ font-size: 18px;
2163
+ width: 24px;
2164
+ }
2165
+
2166
+ .zo-passport-info-label {
2167
+ font-size: 11px;
2168
+ }
2169
+
2170
+ .zo-passport-info-value {
2171
+ font-size: 14px;
2172
+ }
2173
+
2174
+ .zo-passport-culture-sticker {
2175
+ width: 18px;
2176
+ height: 18px;
2177
+ }
2178
+
2179
+ .zo-passport-culture-name {
2180
+ font-size: 12px;
2181
+ }
2182
+ }
2183
+
2184
+ /* Desktop (1024px+) */
2185
+ @media (min-width: 1024px) {
2186
+ .zo-passport-container {
2187
+ padding: 40px;
2188
+ }
2189
+
2190
+ .zo-passport-header {
2191
+ margin-bottom: 40px;
2192
+ }
2193
+
2194
+ .zo-passport-title {
2195
+ font-size: 32px;
2196
+ }
2197
+
2198
+ .zo-passport-grid {
2199
+ grid-template-columns: auto 1fr 1fr;
2200
+ gap: 48px;
2201
+ }
2202
+
2203
+ .zo-passport-content-columns {
2204
+ display: contents;
2205
+ }
2206
+
2207
+ .zo-passport-section {
2208
+ padding: 24px;
2209
+ }
2210
+
2211
+ .zo-passport-info-list {
2212
+ gap: 20px;
2213
+ }
2214
+
2215
+ .zo-passport-culture-sticker {
2216
+ width: 20px;
2217
+ height: 20px;
2218
+ }
2219
+
2220
+ .zo-passport-culture-name {
2221
+ font-size: 13px;
2222
+ }
2223
+ }
2224
+ `;
2225
+ var ZoPassportPage = ({
2226
+ user,
2227
+ balance = 0,
2228
+ completion = { done: 0, total: 10 },
2229
+ walletOpen = false,
2230
+ onWalletToggle,
2231
+ onRemoveCulture
2232
+ }) => {
2233
+ (0, import_react4.useEffect)(() => {
2234
+ const cleanup = injectStyles(STYLE_ID3, CSS3);
2235
+ return cleanup;
2236
+ }, []);
2237
+ if (!user) {
2238
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "zo-passport-container", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "zo-passport-loading", children: "Loading passport..." }) });
2239
+ }
2240
+ const isFounder = user.membership === "founder";
2241
+ const fullName = [user.first_name, user.last_name].filter(Boolean).join(" ") || "Not set";
2242
+ const cultures = user.cultures || [];
2243
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-container", children: [
2244
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-header", children: [
2245
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { className: "zo-passport-title", children: "Zo Passport" }),
2246
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "zo-passport-subtitle", children: "Your identity in Zo World" })
2247
+ ] }),
2248
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-grid", children: [
2249
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "zo-passport-card-section", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2250
+ ZoPassportCard,
2251
+ {
2252
+ profile: {
2253
+ avatar: user.avatar?.image || user.pfp_image,
2254
+ name: user.first_name || "Citizen",
2255
+ isFounder
2256
+ },
2257
+ completion
2258
+ }
2259
+ ) }),
2260
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-content-columns", children: [
2261
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-section", children: [
2262
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { className: "zo-passport-section-title", children: "General Information" }),
2263
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-info-list", children: [
2264
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u270F\uFE0F", label: "Full Name", value: fullName }),
2265
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u{1F464}", label: "Short Bio", value: user.bio || "Not set" }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u{1F382}", label: "Born on", value: formatDate(user.date_of_birth) }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u{1F9EC}", label: "Body Type", value: user.body_type ? user.body_type.charAt(0).toUpperCase() + user.body_type.slice(1) : "Not set" }),
2268
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u{1F4CD}", label: "Location", value: user.place_name || "Not set" })
2269
+ ] }),
2270
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { className: "zo-passport-section-title", style: { marginTop: 24 }, children: "Communication" }),
2271
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-info-list", children: [
2272
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u{1F4E7}", label: "Email", value: user.email_address || "Not set" }),
2273
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(InfoRow, { icon: "\u{1F4F1}", label: "Phone", value: formatPhone(user.mobile_number) })
2274
+ ] })
2275
+ ] }),
2276
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-section", children: [
2277
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { className: "zo-passport-section-title", children: "For the Culture" }),
2278
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "zo-passport-wallet-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2279
+ WalletCardWeb,
2280
+ {
2281
+ balance,
2282
+ user: {
2283
+ avatar: user.avatar?.image ? { image: user.avatar.image } : void 0,
2284
+ first_name: user.first_name || "Citizen",
2285
+ nickname: user.first_name,
2286
+ wallet_address: user.wallet_address || ""
2287
+ },
2288
+ isOpen: walletOpen,
2289
+ onToggle: onWalletToggle
2290
+ }
2291
+ ) }),
2292
+ cultures.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-cultures", children: [
2293
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "zo-passport-cultures-header", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "zo-passport-cultures-title", children: [
2294
+ "Selected Cultures (",
2295
+ cultures.length,
2296
+ ")"
2297
+ ] }) }),
2298
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "zo-passport-culture-tags", children: cultures.map((culture, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2299
+ CultureTag,
2300
+ {
2301
+ stickerUrl: getCultureSticker(culture),
2302
+ name: getCultureName(culture),
2303
+ onRemove: onRemoveCulture ? () => onRemoveCulture(getCultureKey(culture)) : void 0
2304
+ },
2305
+ getCultureKey(culture) || index
2306
+ )) })
2307
+ ] })
2308
+ ] })
2309
+ ] })
2310
+ ] })
2311
+ ] });
2312
+ };
2313
+ var InfoRow = ({ icon, label, value }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-info-row", children: [
2314
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "zo-passport-info-icon", children: icon }),
2315
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-info-content", children: [
2316
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "zo-passport-info-label", children: label }),
2317
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "zo-passport-info-value", children: value })
2318
+ ] })
2319
+ ] });
2320
+ var CultureTag = ({ stickerUrl, name, onRemove }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "zo-passport-culture-tag", children: [
2321
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2322
+ "img",
2323
+ {
2324
+ src: stickerUrl,
2325
+ alt: name,
2326
+ className: "zo-passport-culture-sticker",
2327
+ onError: (e) => {
2328
+ e.target.style.display = "none";
2329
+ }
2330
+ }
2331
+ ),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "zo-passport-culture-name", children: name }),
2333
+ onRemove && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "zo-passport-culture-remove", onClick: onRemove, children: "\xD7" })
2334
+ ] });
2335
+
2336
+ // src/lib/utils/phone.ts
2337
+ var COUNTRY_CODES = [
2338
+ { code: "1", country: "US", flag: "\u{1F1FA}\u{1F1F8}", name: "United States" },
2339
+ { code: "91", country: "IN", flag: "\u{1F1EE}\u{1F1F3}", name: "India" },
2340
+ { code: "44", country: "GB", flag: "\u{1F1EC}\u{1F1E7}", name: "United Kingdom" },
2341
+ { code: "86", country: "CN", flag: "\u{1F1E8}\u{1F1F3}", name: "China" },
2342
+ { code: "81", country: "JP", flag: "\u{1F1EF}\u{1F1F5}", name: "Japan" },
2343
+ { code: "82", country: "KR", flag: "\u{1F1F0}\u{1F1F7}", name: "South Korea" },
2344
+ { code: "33", country: "FR", flag: "\u{1F1EB}\u{1F1F7}", name: "France" },
2345
+ { code: "49", country: "DE", flag: "\u{1F1E9}\u{1F1EA}", name: "Germany" },
2346
+ { code: "7", country: "RU", flag: "\u{1F1F7}\u{1F1FA}", name: "Russia" },
2347
+ { code: "55", country: "BR", flag: "\u{1F1E7}\u{1F1F7}", name: "Brazil" },
2348
+ { code: "61", country: "AU", flag: "\u{1F1E6}\u{1F1FA}", name: "Australia" },
2349
+ { code: "65", country: "SG", flag: "\u{1F1F8}\u{1F1EC}", name: "Singapore" },
2350
+ { code: "971", country: "AE", flag: "\u{1F1E6}\u{1F1EA}", name: "UAE" },
2351
+ { code: "966", country: "SA", flag: "\u{1F1F8}\u{1F1E6}", name: "Saudi Arabia" },
2352
+ { code: "62", country: "ID", flag: "\u{1F1EE}\u{1F1E9}", name: "Indonesia" },
2353
+ { code: "60", country: "MY", flag: "\u{1F1F2}\u{1F1FE}", name: "Malaysia" },
2354
+ { code: "66", country: "TH", flag: "\u{1F1F9}\u{1F1ED}", name: "Thailand" },
2355
+ { code: "84", country: "VN", flag: "\u{1F1FB}\u{1F1F3}", name: "Vietnam" },
2356
+ { code: "63", country: "PH", flag: "\u{1F1F5}\u{1F1ED}", name: "Philippines" },
2357
+ { code: "31", country: "NL", flag: "\u{1F1F3}\u{1F1F1}", name: "Netherlands" }
2358
+ ];
2359
+ function parsePhoneNumber(phone) {
2360
+ return phone.replace(/\D/g, "");
2361
+ }
2362
+
2363
+ // src/components/PhoneInput.tsx
2364
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2365
+ var PhoneInput = ({
2366
+ value,
2367
+ countryCode,
2368
+ onChange,
2369
+ onCountryChange,
2370
+ placeholder = "555-123-4567",
2371
+ disabled = false,
2372
+ error,
2373
+ className = ""
2374
+ }) => {
2375
+ const handlePhoneChange = (e) => {
2376
+ const digits = parsePhoneNumber(e.target.value);
2377
+ onChange(digits);
2378
+ };
2379
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: `zo-phone-input ${className}`, children: [
2380
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
2381
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2382
+ "select",
2383
+ {
2384
+ value: countryCode,
2385
+ onChange: (e) => onCountryChange(e.target.value),
2386
+ disabled,
2387
+ style: {
2388
+ flex: 1,
2389
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
2390
+ border: "1px solid rgba(255, 255, 255, 0.2)",
2391
+ borderRadius: "8px",
2392
+ padding: "12px 16px",
2393
+ color: "white",
2394
+ fontFamily: "Rubik, system-ui, sans-serif",
2395
+ fontSize: "14px",
2396
+ cursor: disabled ? "not-allowed" : "pointer",
2397
+ opacity: disabled ? 0.5 : 1
2398
+ },
2399
+ children: COUNTRY_CODES.map((country) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("option", { value: country.code, style: { backgroundColor: "black" }, children: [
2400
+ country.flag,
2401
+ " +",
2402
+ country.code
2403
+ ] }, country.code))
2404
+ }
2405
+ ),
2406
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2407
+ "input",
2408
+ {
2409
+ type: "tel",
2410
+ value,
2411
+ onChange: handlePhoneChange,
2412
+ placeholder,
2413
+ disabled,
2414
+ style: {
2415
+ flex: 2,
2416
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
2417
+ border: error ? "1px solid #ef4444" : "1px solid rgba(255, 255, 255, 0.2)",
2418
+ borderRadius: "8px",
2419
+ padding: "12px 16px",
2420
+ color: "white",
2421
+ fontFamily: "Rubik, system-ui, sans-serif",
2422
+ fontSize: "14px",
2423
+ cursor: disabled ? "not-allowed" : "text",
2424
+ opacity: disabled ? 0.5 : 1
2425
+ }
2426
+ }
2427
+ )
2428
+ ] }),
2429
+ error && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2430
+ "p",
2431
+ {
2432
+ style: {
2433
+ color: "#ef4444",
2434
+ fontSize: "14px",
2435
+ marginTop: "8px",
2436
+ fontFamily: "Rubik, system-ui, sans-serif"
2437
+ },
2438
+ children: error
2439
+ }
2440
+ )
2441
+ ] });
2442
+ };
2443
+
2444
+ // src/components/OTPInput.tsx
2445
+ var import_react5 = require("react");
2446
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2447
+ var OTPInput = ({
2448
+ value,
2449
+ onChange,
2450
+ onComplete,
2451
+ length = 6,
2452
+ disabled = false,
2453
+ error,
2454
+ autoFocus = true,
2455
+ className = ""
2456
+ }) => {
2457
+ const inputRefs = (0, import_react5.useRef)([]);
2458
+ (0, import_react5.useEffect)(() => {
2459
+ inputRefs.current = inputRefs.current.slice(0, length);
2460
+ }, [length]);
2461
+ (0, import_react5.useEffect)(() => {
2462
+ if (autoFocus && inputRefs.current[0]) {
2463
+ setTimeout(() => inputRefs.current[0]?.focus(), 100);
2464
+ }
2465
+ }, [autoFocus]);
2466
+ const handleChange = (index, inputValue) => {
2467
+ if (inputValue && !/^\d$/.test(inputValue)) return;
2468
+ const newOtp = [...value];
2469
+ newOtp[index] = inputValue;
2470
+ onChange(newOtp);
2471
+ if (inputValue && index < length - 1) {
2472
+ inputRefs.current[index + 1]?.focus();
2473
+ }
2474
+ if (newOtp.every((digit) => digit !== "") && index === length - 1) {
2475
+ onComplete?.(newOtp.join(""));
2476
+ }
2477
+ };
2478
+ const handleKeyDown = (index, e) => {
2479
+ if (e.key === "Backspace" && !value[index] && index > 0) {
2480
+ inputRefs.current[index - 1]?.focus();
2481
+ }
2482
+ };
2483
+ const handlePaste = (e) => {
2484
+ e.preventDefault();
2485
+ const pastedData = e.clipboardData.getData("text").slice(0, length);
2486
+ const digits = pastedData.replace(/\D/g, "").split("");
2487
+ if (digits.length > 0) {
2488
+ const newOtp = [...value];
2489
+ digits.forEach((digit, i) => {
2490
+ if (i < length) newOtp[i] = digit;
2491
+ });
2492
+ onChange(newOtp);
2493
+ const focusIndex = Math.min(digits.length, length - 1);
2494
+ inputRefs.current[focusIndex]?.focus();
2495
+ if (newOtp.every((digit) => digit !== "")) {
2496
+ onComplete?.(newOtp.join(""));
2497
+ }
2498
+ }
2499
+ };
2500
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `zo-otp-input ${className}`, children: [
2501
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { display: "flex", gap: "8px", justifyContent: "center" }, children: Array.from({ length }).map((_, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2502
+ "input",
2503
+ {
2504
+ ref: (el) => {
2505
+ inputRefs.current[index] = el;
2506
+ },
2507
+ type: "text",
2508
+ inputMode: "numeric",
2509
+ maxLength: 1,
2510
+ value: value[index] || "",
2511
+ onChange: (e) => handleChange(index, e.target.value),
2512
+ onKeyDown: (e) => handleKeyDown(index, e),
2513
+ onPaste: handlePaste,
2514
+ disabled,
2515
+ style: {
2516
+ width: "48px",
2517
+ height: "56px",
2518
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
2519
+ border: error ? "1px solid #ef4444" : "1px solid rgba(255, 255, 255, 0.2)",
2520
+ borderRadius: "8px",
2521
+ textAlign: "center",
2522
+ color: "white",
2523
+ fontFamily: "Rubik, system-ui, sans-serif",
2524
+ fontSize: "20px",
2525
+ fontWeight: 600,
2526
+ cursor: disabled ? "not-allowed" : "text",
2527
+ opacity: disabled ? 0.5 : 1
2528
+ }
2529
+ },
2530
+ index
2531
+ )) }),
2532
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2533
+ "p",
2534
+ {
2535
+ style: {
2536
+ color: "#ef4444",
2537
+ fontSize: "14px",
2538
+ marginTop: "8px",
2539
+ textAlign: "center",
2540
+ fontFamily: "Rubik, system-ui, sans-serif"
2541
+ },
2542
+ children: error
2543
+ }
2544
+ )
2545
+ ] });
2546
+ };
2547
+
2548
+ // src/components/ZoAuth.tsx
2549
+ var import_react6 = require("react");
2550
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2551
+ var ZoAuth2 = ({
2552
+ onSuccess,
2553
+ onClose,
2554
+ sendOTP,
2555
+ verifyOTP,
2556
+ defaultCountryCode = "91",
2557
+ showCloseButton = true,
2558
+ className = ""
2559
+ }) => {
2560
+ const [step, setStep] = (0, import_react6.useState)("phone");
2561
+ const [countryCode, setCountryCode] = (0, import_react6.useState)(defaultCountryCode);
2562
+ const [phoneNumber, setPhoneNumber] = (0, import_react6.useState)("");
2563
+ const [otp, setOtp] = (0, import_react6.useState)(["", "", "", "", "", ""]);
2564
+ const [isLoading, setIsLoading] = (0, import_react6.useState)(false);
2565
+ const [error, setError] = (0, import_react6.useState)(null);
2566
+ const [resendCooldown, setResendCooldown] = (0, import_react6.useState)(0);
2567
+ (0, import_react6.useEffect)(() => {
2568
+ if (resendCooldown > 0) {
2569
+ const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1e3);
2570
+ return () => clearTimeout(timer);
2571
+ }
2572
+ }, [resendCooldown]);
2573
+ const handleSendOTP = async () => {
2574
+ if (!phoneNumber || phoneNumber.length < 10) {
2575
+ setError("Please enter a valid phone number");
2576
+ return;
2577
+ }
2578
+ setIsLoading(true);
2579
+ setError(null);
2580
+ try {
2581
+ const result = await sendOTP(countryCode, phoneNumber);
2582
+ if (result.success) {
2583
+ setStep("otp");
2584
+ setResendCooldown(60);
2585
+ } else {
2586
+ setError(result.message || "Failed to send OTP");
2587
+ }
2588
+ } catch (err) {
2589
+ setError(err.message || "Failed to send verification code");
2590
+ } finally {
2591
+ setIsLoading(false);
2592
+ }
2593
+ };
2594
+ const handleVerifyOTP = async (otpString) => {
2595
+ setIsLoading(true);
2596
+ setError(null);
2597
+ try {
2598
+ const result = await verifyOTP(countryCode, phoneNumber, otpString);
2599
+ if (result.success && result.user) {
2600
+ onSuccess(result.user.id || "", result.user);
2601
+ } else {
2602
+ setError(result.error || "Invalid verification code");
2603
+ setOtp(["", "", "", "", "", ""]);
2604
+ }
2605
+ } catch (err) {
2606
+ setError(err.message || "Invalid verification code");
2607
+ setOtp(["", "", "", "", "", ""]);
2608
+ } finally {
2609
+ setIsLoading(false);
2610
+ }
2611
+ };
2612
+ const handleResendOTP = async () => {
2613
+ if (resendCooldown > 0) return;
2614
+ await handleSendOTP();
2615
+ };
2616
+ const containerStyle = {
2617
+ position: "relative",
2618
+ width: "100%",
2619
+ maxWidth: "400px",
2620
+ backgroundColor: "rgba(0, 0, 0, 0.95)",
2621
+ border: "1px solid rgba(255, 255, 255, 0.2)",
2622
+ borderRadius: "24px",
2623
+ padding: "24px 32px",
2624
+ fontFamily: "Rubik, system-ui, sans-serif"
2625
+ };
2626
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `zo-auth ${className}`, style: containerStyle, children: [
2627
+ showCloseButton && onClose && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2628
+ "button",
2629
+ {
2630
+ onClick: onClose,
2631
+ style: {
2632
+ position: "absolute",
2633
+ top: "16px",
2634
+ right: "16px",
2635
+ width: "32px",
2636
+ height: "32px",
2637
+ display: "flex",
2638
+ alignItems: "center",
2639
+ justifyContent: "center",
2640
+ backgroundColor: "transparent",
2641
+ border: "none",
2642
+ cursor: "pointer",
2643
+ color: "rgba(255, 255, 255, 0.6)"
2644
+ },
2645
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
2646
+ }
2647
+ ),
2648
+ step === "phone" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
2649
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2650
+ "div",
2651
+ {
2652
+ style: {
2653
+ width: "64px",
2654
+ height: "64px",
2655
+ margin: "0 auto 16px",
2656
+ display: "flex",
2657
+ alignItems: "center",
2658
+ justifyContent: "center",
2659
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
2660
+ borderRadius: "50%"
2661
+ },
2662
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" }) })
2663
+ }
2664
+ ),
2665
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2666
+ "h2",
2667
+ {
2668
+ style: {
2669
+ textAlign: "center",
2670
+ color: "white",
2671
+ fontSize: "24px",
2672
+ fontWeight: 800,
2673
+ marginBottom: "8px",
2674
+ fontFamily: "Syne, system-ui, sans-serif"
2675
+ },
2676
+ children: "Enter Your Phone Number"
2677
+ }
2678
+ ),
2679
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2680
+ "p",
2681
+ {
2682
+ style: {
2683
+ textAlign: "center",
2684
+ color: "rgba(255, 255, 255, 0.6)",
2685
+ fontSize: "14px",
2686
+ marginBottom: "24px"
2687
+ },
2688
+ children: "We'll send you a verification code"
2689
+ }
2690
+ ),
2691
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2692
+ PhoneInput,
2693
+ {
2694
+ value: phoneNumber,
2695
+ countryCode,
2696
+ onChange: setPhoneNumber,
2697
+ onCountryChange: setCountryCode,
2698
+ disabled: isLoading,
2699
+ error: error || void 0
2700
+ }
2701
+ ),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2703
+ "button",
2704
+ {
2705
+ onClick: handleSendOTP,
2706
+ disabled: isLoading || phoneNumber.length < 10,
2707
+ style: {
2708
+ width: "100%",
2709
+ marginTop: "16px",
2710
+ padding: "16px",
2711
+ backgroundColor: "black",
2712
+ border: "2px solid rgba(255, 255, 255, 0.2)",
2713
+ borderRadius: "12px",
2714
+ color: "white",
2715
+ fontWeight: 500,
2716
+ fontSize: "16px",
2717
+ cursor: isLoading || phoneNumber.length < 10 ? "not-allowed" : "pointer",
2718
+ opacity: isLoading || phoneNumber.length < 10 ? 0.5 : 1
2719
+ },
2720
+ children: isLoading ? "Sending Code..." : "Send Code"
2721
+ }
2722
+ )
2723
+ ] }),
2724
+ step === "otp" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
2725
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2726
+ "div",
2727
+ {
2728
+ style: {
2729
+ width: "64px",
2730
+ height: "64px",
2731
+ margin: "0 auto 16px",
2732
+ display: "flex",
2733
+ alignItems: "center",
2734
+ justifyContent: "center",
2735
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
2736
+ borderRadius: "50%"
2737
+ },
2738
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" }) })
2739
+ }
2740
+ ),
2741
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2742
+ "h2",
2743
+ {
2744
+ style: {
2745
+ textAlign: "center",
2746
+ color: "white",
2747
+ fontSize: "24px",
2748
+ fontWeight: 800,
2749
+ marginBottom: "8px",
2750
+ fontFamily: "Syne, system-ui, sans-serif"
2751
+ },
2752
+ children: "Enter Verification Code"
2753
+ }
2754
+ ),
2755
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2756
+ "p",
2757
+ {
2758
+ style: {
2759
+ textAlign: "center",
2760
+ color: "rgba(255, 255, 255, 0.6)",
2761
+ fontSize: "14px",
2762
+ marginBottom: "24px"
2763
+ },
2764
+ children: [
2765
+ "We sent a code to +",
2766
+ countryCode,
2767
+ " ",
2768
+ phoneNumber
2769
+ ]
2770
+ }
2771
+ ),
2772
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2773
+ OTPInput,
2774
+ {
2775
+ value: otp,
2776
+ onChange: setOtp,
2777
+ onComplete: handleVerifyOTP,
2778
+ disabled: isLoading,
2779
+ error: error || void 0
2780
+ }
2781
+ ),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2783
+ "button",
2784
+ {
2785
+ onClick: () => handleVerifyOTP(otp.join("")),
2786
+ disabled: isLoading || otp.some((d) => !d),
2787
+ style: {
2788
+ width: "100%",
2789
+ marginTop: "16px",
2790
+ padding: "16px",
2791
+ backgroundColor: "black",
2792
+ border: "2px solid rgba(255, 255, 255, 0.2)",
2793
+ borderRadius: "12px",
2794
+ color: "white",
2795
+ fontWeight: 500,
2796
+ fontSize: "16px",
2797
+ cursor: isLoading || otp.some((d) => !d) ? "not-allowed" : "pointer",
2798
+ opacity: isLoading || otp.some((d) => !d) ? 0.5 : 1
2799
+ },
2800
+ children: isLoading ? "Verifying..." : "Verify"
2801
+ }
2802
+ ),
2803
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2804
+ "button",
2805
+ {
2806
+ onClick: handleResendOTP,
2807
+ disabled: resendCooldown > 0,
2808
+ style: {
2809
+ backgroundColor: "transparent",
2810
+ border: "none",
2811
+ color: "rgba(255, 255, 255, 0.6)",
2812
+ fontSize: "14px",
2813
+ cursor: resendCooldown > 0 ? "not-allowed" : "pointer",
2814
+ opacity: resendCooldown > 0 ? 0.5 : 1
2815
+ },
2816
+ children: resendCooldown > 0 ? `Resend code in ${resendCooldown}s` : "Didn't receive? Resend code"
2817
+ }
2818
+ ) })
2819
+ ] })
2820
+ ] });
2821
+ };
2822
+
2823
+ // src/components/ZoLanding.tsx
2824
+ var import_react7 = require("react");
2825
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2826
+ var ZoLanding = ({
2827
+ onAuthSuccess,
2828
+ sendOTP,
2829
+ verifyOTP,
2830
+ videoUrl = "/videos/loading-screen-background.mp4",
2831
+ logoUrl = "/figma-assets/landing-zo-logo.png",
2832
+ title = "ZOHMMM!",
2833
+ subtitles = [
2834
+ "Welcome to Zo World",
2835
+ "A parallel reality where you live your best life, by following your heart.",
2836
+ "Are you ready to tune in, Anon?"
2837
+ ],
2838
+ buttonText = "Tune into Zo World",
2839
+ className = ""
2840
+ }) => {
2841
+ const [showAuth, setShowAuth] = (0, import_react7.useState)(false);
2842
+ const containerStyle = {
2843
+ position: "fixed",
2844
+ inset: 0,
2845
+ display: "flex",
2846
+ flexDirection: "column",
2847
+ backgroundColor: "black",
2848
+ width: "100vw",
2849
+ height: "100vh",
2850
+ overflow: "hidden",
2851
+ fontFamily: "Rubik, system-ui, sans-serif"
2852
+ };
2853
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `zo-landing ${className}`, style: containerStyle, children: [
2854
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2855
+ "video",
2856
+ {
2857
+ autoPlay: true,
2858
+ loop: true,
2859
+ muted: true,
2860
+ playsInline: true,
2861
+ style: {
2862
+ position: "fixed",
2863
+ inset: 0,
2864
+ width: "100%",
2865
+ height: "100%",
2866
+ objectFit: "cover",
2867
+ zIndex: 1,
2868
+ pointerEvents: "none"
2869
+ },
2870
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("source", { src: videoUrl, type: "video/mp4" })
2871
+ }
2872
+ ),
2873
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2874
+ "div",
2875
+ {
2876
+ style: {
2877
+ position: "fixed",
2878
+ inset: 0,
2879
+ background: "linear-gradient(to bottom, transparent 50%, black)",
2880
+ zIndex: 2,
2881
+ pointerEvents: "none"
2882
+ }
2883
+ }
2884
+ ),
2885
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2886
+ "img",
2887
+ {
2888
+ src: logoUrl,
2889
+ alt: "Zo",
2890
+ style: {
2891
+ position: "absolute",
2892
+ left: "24px",
2893
+ top: "40px",
2894
+ width: "40px",
2895
+ height: "40px",
2896
+ objectFit: "cover",
2897
+ zIndex: 10
2898
+ }
2899
+ }
2900
+ ),
2901
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2902
+ "div",
2903
+ {
2904
+ style: {
2905
+ position: "relative",
2906
+ zIndex: 10,
2907
+ width: "100%",
2908
+ height: "100%",
2909
+ display: "flex",
2910
+ flexDirection: "column",
2911
+ alignItems: "center",
2912
+ justifyContent: "center",
2913
+ padding: "24px"
2914
+ },
2915
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2916
+ "div",
2917
+ {
2918
+ style: {
2919
+ display: "flex",
2920
+ flexDirection: "column",
2921
+ alignItems: "center",
2922
+ justifyContent: "center",
2923
+ width: "100%",
2924
+ maxWidth: "800px",
2925
+ gap: "32px"
2926
+ },
2927
+ children: [
2928
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { textAlign: "center" }, children: [
2929
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2930
+ "h1",
2931
+ {
2932
+ style: {
2933
+ fontFamily: "Syne, system-ui, sans-serif",
2934
+ fontSize: "clamp(32px, 8vw, 72px)",
2935
+ fontWeight: 800,
2936
+ color: "white",
2937
+ lineHeight: 1.1,
2938
+ letterSpacing: "0.32px",
2939
+ textTransform: "uppercase",
2940
+ margin: 0
2941
+ },
2942
+ children: title
2943
+ }
2944
+ ),
2945
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2946
+ "div",
2947
+ {
2948
+ style: {
2949
+ marginTop: "16px",
2950
+ fontSize: "clamp(16px, 3vw, 24px)",
2951
+ color: "white",
2952
+ lineHeight: 1.5
2953
+ },
2954
+ children: subtitles.map((text, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: { margin: "8px 0" }, children: text }, i))
2955
+ }
2956
+ )
2957
+ ] }),
2958
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2959
+ "button",
2960
+ {
2961
+ onClick: () => setShowAuth(true),
2962
+ style: {
2963
+ backgroundColor: "black",
2964
+ border: "2px solid rgba(255, 255, 255, 0.2)",
2965
+ borderRadius: "12px",
2966
+ padding: "16px 20px",
2967
+ width: "100%",
2968
+ maxWidth: "400px",
2969
+ height: "64px",
2970
+ cursor: "pointer",
2971
+ transition: "all 0.3s"
2972
+ },
2973
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2974
+ "span",
2975
+ {
2976
+ style: {
2977
+ fontFamily: "Rubik, system-ui, sans-serif",
2978
+ fontSize: "18px",
2979
+ fontWeight: 500,
2980
+ color: "white"
2981
+ },
2982
+ children: buttonText
2983
+ }
2984
+ )
2985
+ }
2986
+ )
2987
+ ]
2988
+ }
2989
+ )
2990
+ }
2991
+ ),
2992
+ showAuth && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2993
+ "div",
2994
+ {
2995
+ style: {
2996
+ position: "fixed",
2997
+ inset: 0,
2998
+ zIndex: 1e4,
2999
+ display: "flex",
3000
+ alignItems: "center",
3001
+ justifyContent: "center",
3002
+ padding: "16px"
3003
+ },
3004
+ children: [
3005
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3006
+ "div",
3007
+ {
3008
+ onClick: () => setShowAuth(false),
3009
+ style: {
3010
+ position: "absolute",
3011
+ inset: 0,
3012
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
3013
+ backdropFilter: "blur(4px)"
3014
+ }
3015
+ }
3016
+ ),
3017
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { position: "relative", zIndex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3018
+ ZoAuth2,
3019
+ {
3020
+ onSuccess: (userId, user) => {
3021
+ setShowAuth(false);
3022
+ onAuthSuccess(userId, user);
3023
+ },
3024
+ onClose: () => setShowAuth(false),
3025
+ sendOTP,
3026
+ verifyOTP
3027
+ }
3028
+ ) })
3029
+ ]
3030
+ }
3031
+ )
3032
+ ] });
3033
+ };
3034
+
3035
+ // src/components/ZoOnboarding.tsx
3036
+ var import_react8 = require("react");
3037
+ var import_jsx_runtime12 = require("react/jsx-runtime");
3038
+ var ZoOnboarding = ({
3039
+ onComplete,
3040
+ updateProfile,
3041
+ getProfile,
3042
+ videoUrl = "/videos/loading-screen-background.mp4",
3043
+ logoUrl = "/figma-assets/landing-zo-logo.png",
3044
+ broAvatarUrl = "/bro.png",
3045
+ baeAvatarUrl = "/bae.png",
3046
+ className = ""
3047
+ }) => {
3048
+ const [step, setStep] = (0, import_react8.useState)("input");
3049
+ const [nickname, setNickname] = (0, import_react8.useState)("");
3050
+ const [bodyType, setBodyType] = (0, import_react8.useState)("bro");
3051
+ const [city, setCity] = (0, import_react8.useState)("");
3052
+ const [locationEnabled, setLocationEnabled] = (0, import_react8.useState)(false);
3053
+ const [isLoadingLocation, setIsLoadingLocation] = (0, import_react8.useState)(false);
3054
+ const [isSaving, setIsSaving] = (0, import_react8.useState)(false);
3055
+ const [error, setError] = (0, import_react8.useState)("");
3056
+ const [avatarUrl, setAvatarUrl] = (0, import_react8.useState)(null);
3057
+ const pollingRef = (0, import_react8.useRef)(null);
3058
+ const attemptsRef = (0, import_react8.useRef)(0);
3059
+ const isNicknameValid = nickname.length >= 4 && nickname.length <= 16 && /^[a-z0-9]*$/.test(nickname);
3060
+ const canSubmit = isNicknameValid && locationEnabled && bodyType && !isSaving;
3061
+ (0, import_react8.useEffect)(() => {
3062
+ return () => {
3063
+ if (pollingRef.current) clearTimeout(pollingRef.current);
3064
+ };
3065
+ }, []);
3066
+ const handleNicknameChange = (e) => {
3067
+ setNickname(e.target.value.toLowerCase());
3068
+ setError("");
3069
+ };
3070
+ const handleLocationEnable = () => {
3071
+ if ("geolocation" in navigator) {
3072
+ setIsLoadingLocation(true);
3073
+ navigator.geolocation.getCurrentPosition(
3074
+ async (position) => {
3075
+ try {
3076
+ const response = await fetch(
3077
+ `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${position.coords.latitude}&longitude=${position.coords.longitude}&localityLanguage=en`
3078
+ );
3079
+ const data = await response.json();
3080
+ const detectedCity = data.city || data.locality || data.principalSubdivision || "Unknown City";
3081
+ setCity(detectedCity);
3082
+ setLocationEnabled(true);
3083
+ setIsLoadingLocation(false);
3084
+ setError("");
3085
+ } catch (err) {
3086
+ console.error("Failed to get city:", err);
3087
+ setError("Failed to detect location. Please try again.");
3088
+ setLocationEnabled(false);
3089
+ setIsLoadingLocation(false);
3090
+ }
3091
+ },
3092
+ (err) => {
3093
+ console.error("Location error:", err);
3094
+ setError("Location access denied. Please enable permissions.");
3095
+ setLocationEnabled(false);
3096
+ setIsLoadingLocation(false);
3097
+ }
3098
+ );
3099
+ } else {
3100
+ setError("Geolocation is not supported by your browser.");
3101
+ }
3102
+ };
3103
+ const handleSubmit = async () => {
3104
+ if (!canSubmit) return;
3105
+ setIsSaving(true);
3106
+ setError("");
3107
+ try {
3108
+ const result = await updateProfile({
3109
+ first_name: nickname,
3110
+ body_type: bodyType,
3111
+ place_name: city
3112
+ });
3113
+ if (!result.success) {
3114
+ throw new Error(result.error || "Profile update failed");
3115
+ }
3116
+ setStep("generating");
3117
+ pollForAvatar();
3118
+ } catch (err) {
3119
+ console.error("Error saving user:", err);
3120
+ setError("Failed to save. Please try again.");
3121
+ setIsSaving(false);
3122
+ }
3123
+ };
3124
+ const pollForAvatar = async () => {
3125
+ attemptsRef.current += 1;
3126
+ const maxAttempts = 30;
3127
+ if (attemptsRef.current > maxAttempts) {
3128
+ setAvatarUrl(bodyType === "bro" ? broAvatarUrl : baeAvatarUrl);
3129
+ setStep("success");
3130
+ return;
3131
+ }
3132
+ try {
3133
+ const profile = await getProfile();
3134
+ if (profile?.avatar?.image) {
3135
+ setAvatarUrl(profile.avatar.image);
3136
+ setTimeout(() => setStep("success"), 1e3);
3137
+ return;
3138
+ }
3139
+ pollingRef.current = setTimeout(pollForAvatar, 1e3);
3140
+ } catch (err) {
3141
+ console.error("Polling error:", err);
3142
+ pollingRef.current = setTimeout(pollForAvatar, 1e3);
3143
+ }
3144
+ };
3145
+ const handleComplete = () => {
3146
+ onComplete({
3147
+ nickname,
3148
+ bodyType,
3149
+ city,
3150
+ avatarUrl
3151
+ });
3152
+ };
3153
+ const containerStyle = {
3154
+ position: "fixed",
3155
+ inset: 0,
3156
+ display: "flex",
3157
+ flexDirection: "column",
3158
+ backgroundColor: "black",
3159
+ width: "100vw",
3160
+ height: "100vh",
3161
+ overflow: "hidden",
3162
+ fontFamily: "Rubik, system-ui, sans-serif"
3163
+ };
3164
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `zo-onboarding ${className}`, style: containerStyle, children: [
3165
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3166
+ "video",
3167
+ {
3168
+ autoPlay: true,
3169
+ loop: true,
3170
+ muted: true,
3171
+ playsInline: true,
3172
+ style: {
3173
+ position: "absolute",
3174
+ inset: 0,
3175
+ width: "100%",
3176
+ height: "100%",
3177
+ objectFit: "cover",
3178
+ opacity: 0.4,
3179
+ zIndex: 0,
3180
+ pointerEvents: "none"
3181
+ },
3182
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("source", { src: videoUrl, type: "video/mp4" })
3183
+ }
3184
+ ),
3185
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3186
+ "div",
3187
+ {
3188
+ style: {
3189
+ position: "absolute",
3190
+ inset: 0,
3191
+ background: "linear-gradient(to bottom, transparent, transparent, black)",
3192
+ zIndex: 0,
3193
+ pointerEvents: "none"
3194
+ }
3195
+ }
3196
+ ),
3197
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { position: "absolute", left: "24px", top: "40px", width: "40px", height: "40px", zIndex: 50 }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: logoUrl, alt: "Zo", style: { width: "100%", height: "100%", objectFit: "cover" } }) }),
3198
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3199
+ "div",
3200
+ {
3201
+ style: {
3202
+ position: "relative",
3203
+ zIndex: 10,
3204
+ width: "100%",
3205
+ height: "100%",
3206
+ display: "flex",
3207
+ flexDirection: "column",
3208
+ alignItems: "center",
3209
+ overflowY: "auto",
3210
+ paddingTop: "120px",
3211
+ paddingBottom: "40px"
3212
+ },
3213
+ children: [
3214
+ step === "input" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", width: "100%", maxWidth: "360px", padding: "0 24px" }, children: [
3215
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3216
+ "h1",
3217
+ {
3218
+ style: {
3219
+ fontFamily: "Syne, system-ui, sans-serif",
3220
+ fontWeight: 800,
3221
+ color: "white",
3222
+ textAlign: "center",
3223
+ textTransform: "uppercase",
3224
+ letterSpacing: "0.32px",
3225
+ lineHeight: 1.2,
3226
+ marginBottom: "24px",
3227
+ fontSize: "clamp(24px, 6vw, 48px)"
3228
+ },
3229
+ children: "WHO ARE YOU?"
3230
+ }
3231
+ ),
3232
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3233
+ "p",
3234
+ {
3235
+ style: {
3236
+ color: "rgba(255, 255, 255, 0.6)",
3237
+ textAlign: "center",
3238
+ lineHeight: 1.5,
3239
+ marginBottom: "40px",
3240
+ fontSize: "clamp(14px, 2vw, 16px)"
3241
+ },
3242
+ children: [
3243
+ "A difficult question, I know. We'll get to it.",
3244
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("br", {}),
3245
+ "But let's start with choosing a nick."
3246
+ ]
3247
+ }
3248
+ ),
3249
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3250
+ "input",
3251
+ {
3252
+ type: "text",
3253
+ value: nickname,
3254
+ onChange: handleNicknameChange,
3255
+ placeholder: "samurai",
3256
+ maxLength: 16,
3257
+ style: {
3258
+ width: "100%",
3259
+ height: "56px",
3260
+ padding: "0 20px",
3261
+ backgroundColor: "black",
3262
+ border: "1px solid #49494A",
3263
+ borderRadius: "12px",
3264
+ color: "white",
3265
+ fontSize: "16px",
3266
+ marginBottom: "40px"
3267
+ },
3268
+ autoFocus: true
3269
+ }
3270
+ ),
3271
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { width: "100%", marginBottom: "32px" }, children: [
3272
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { color: "rgba(255, 255, 255, 0.8)", fontSize: "14px", textAlign: "center", marginBottom: "16px" }, children: "Choose your avatar style" }),
3273
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", gap: "16px", justifyContent: "center" }, children: [
3274
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3275
+ "button",
3276
+ {
3277
+ onClick: () => setBodyType("bae"),
3278
+ style: {
3279
+ display: "flex",
3280
+ flexDirection: "column",
3281
+ alignItems: "center",
3282
+ gap: "8px",
3283
+ padding: "16px",
3284
+ borderRadius: "16px",
3285
+ border: bodyType === "bae" ? "2px solid #CFFF50" : "2px solid rgba(255, 255, 255, 0.3)",
3286
+ backgroundColor: bodyType === "bae" ? "rgba(255, 255, 255, 0.3)" : "rgba(255, 255, 255, 0.2)",
3287
+ cursor: "pointer",
3288
+ minWidth: "120px",
3289
+ transform: bodyType === "bae" ? "scale(1.05)" : "scale(1)",
3290
+ transition: "all 0.3s"
3291
+ },
3292
+ children: [
3293
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { width: "64px", height: "64px", borderRadius: "50%", overflow: "hidden", backgroundColor: "rgba(255, 255, 255, 0.1)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: baeAvatarUrl, alt: "Bae", style: { width: "100%", height: "100%", objectFit: "cover" } }) }),
3294
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: "white", fontSize: "14px", fontWeight: 500 }, children: "Bae" })
3295
+ ]
3296
+ }
3297
+ ),
3298
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3299
+ "button",
3300
+ {
3301
+ onClick: () => setBodyType("bro"),
3302
+ style: {
3303
+ display: "flex",
3304
+ flexDirection: "column",
3305
+ alignItems: "center",
3306
+ gap: "8px",
3307
+ padding: "16px",
3308
+ borderRadius: "16px",
3309
+ border: bodyType === "bro" ? "2px solid #CFFF50" : "2px solid rgba(255, 255, 255, 0.3)",
3310
+ backgroundColor: bodyType === "bro" ? "rgba(255, 255, 255, 0.3)" : "rgba(255, 255, 255, 0.2)",
3311
+ cursor: "pointer",
3312
+ minWidth: "120px",
3313
+ transform: bodyType === "bro" ? "scale(1.05)" : "scale(1)",
3314
+ transition: "all 0.3s"
3315
+ },
3316
+ children: [
3317
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { width: "64px", height: "64px", borderRadius: "50%", overflow: "hidden", backgroundColor: "rgba(255, 255, 255, 0.1)" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("img", { src: broAvatarUrl, alt: "Bro", style: { width: "100%", height: "100%", objectFit: "cover" } }) }),
3318
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: "white", fontSize: "14px", fontWeight: 500 }, children: "Bro" })
3319
+ ]
3320
+ }
3321
+ )
3322
+ ] })
3323
+ ] }),
3324
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { width: "100%", marginBottom: "24px" }, children: !locationEnabled ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3325
+ "button",
3326
+ {
3327
+ onClick: handleLocationEnable,
3328
+ disabled: isLoadingLocation,
3329
+ style: {
3330
+ width: "100%",
3331
+ height: "48px",
3332
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
3333
+ border: "1px solid rgba(255, 255, 255, 0.2)",
3334
+ borderRadius: "12px",
3335
+ color: "rgba(255, 255, 255, 0.8)",
3336
+ fontSize: "14px",
3337
+ cursor: isLoadingLocation ? "wait" : "pointer",
3338
+ display: "flex",
3339
+ alignItems: "center",
3340
+ justifyContent: "center",
3341
+ gap: "8px"
3342
+ },
3343
+ children: [
3344
+ "\u{1F4CD} ",
3345
+ isLoadingLocation ? "Detecting..." : "Enable Location"
3346
+ ]
3347
+ }
3348
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3349
+ "div",
3350
+ {
3351
+ style: {
3352
+ width: "100%",
3353
+ height: "48px",
3354
+ backgroundColor: "rgba(207, 255, 80, 0.1)",
3355
+ border: "1px solid rgba(207, 255, 80, 0.5)",
3356
+ borderRadius: "12px",
3357
+ color: "#CFFF50",
3358
+ fontSize: "14px",
3359
+ display: "flex",
3360
+ alignItems: "center",
3361
+ justifyContent: "center",
3362
+ gap: "8px"
3363
+ },
3364
+ children: [
3365
+ "\u{1F4CD} ",
3366
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { color: "white" }, children: city })
3367
+ ]
3368
+ }
3369
+ ) }),
3370
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3371
+ "button",
3372
+ {
3373
+ onClick: handleSubmit,
3374
+ disabled: !canSubmit,
3375
+ style: {
3376
+ width: "100%",
3377
+ height: "56px",
3378
+ backgroundColor: "white",
3379
+ border: "none",
3380
+ borderRadius: "12px",
3381
+ color: "black",
3382
+ fontSize: "16px",
3383
+ fontWeight: 500,
3384
+ cursor: canSubmit ? "pointer" : "not-allowed",
3385
+ opacity: canSubmit ? 1 : 0.5
3386
+ },
3387
+ children: isSaving ? "Processing..." : "Get Citizenship"
3388
+ }
3389
+ ),
3390
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { color: "#ef4444", fontSize: "14px", marginTop: "16px", textAlign: "center" }, children: error })
3391
+ ] }),
3392
+ step === "generating" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3393
+ "div",
3394
+ {
3395
+ style: {
3396
+ width: "200px",
3397
+ height: "200px",
3398
+ borderRadius: "50%",
3399
+ border: "2px solid rgba(255, 255, 255, 0.2)",
3400
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
3401
+ display: "flex",
3402
+ alignItems: "center",
3403
+ justifyContent: "center",
3404
+ overflow: "hidden"
3405
+ },
3406
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3407
+ "img",
3408
+ {
3409
+ src: bodyType === "bro" ? broAvatarUrl : baeAvatarUrl,
3410
+ alt: "Generating...",
3411
+ style: {
3412
+ width: "100%",
3413
+ height: "100%",
3414
+ objectFit: "cover",
3415
+ opacity: 0.8,
3416
+ animation: "pulse 2s ease-in-out infinite"
3417
+ }
3418
+ }
3419
+ )
3420
+ }
3421
+ ) }),
3422
+ step === "success" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: [
3423
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3424
+ "div",
3425
+ {
3426
+ style: {
3427
+ width: "200px",
3428
+ height: "200px",
3429
+ borderRadius: "50%",
3430
+ border: "4px solid white",
3431
+ boxShadow: "0 0 40px rgba(255, 255, 255, 0.6)",
3432
+ overflow: "hidden"
3433
+ },
3434
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3435
+ "img",
3436
+ {
3437
+ src: avatarUrl || (bodyType === "bro" ? broAvatarUrl : baeAvatarUrl),
3438
+ alt: "Avatar",
3439
+ style: { width: "100%", height: "100%", objectFit: "cover" }
3440
+ }
3441
+ )
3442
+ }
3443
+ ),
3444
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3445
+ "button",
3446
+ {
3447
+ onClick: handleComplete,
3448
+ style: {
3449
+ position: "fixed",
3450
+ bottom: "40px",
3451
+ left: "50%",
3452
+ transform: "translateX(-50%)",
3453
+ width: "90%",
3454
+ maxWidth: "360px",
3455
+ height: "56px",
3456
+ backgroundColor: "white",
3457
+ border: "none",
3458
+ borderRadius: "12px",
3459
+ color: "black",
3460
+ fontSize: "16px",
3461
+ fontWeight: 700,
3462
+ textTransform: "uppercase",
3463
+ letterSpacing: "0.05em",
3464
+ cursor: "pointer",
3465
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)"
3466
+ },
3467
+ children: "Zo Zo Zo! Let's Go"
3468
+ }
3469
+ )
3470
+ ] })
3471
+ ]
3472
+ }
3473
+ ),
3474
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("style", { children: `
3475
+ @keyframes pulse {
3476
+ 0%, 100% { transform: scale(1); opacity: 0.8; }
3477
+ 50% { transform: scale(1.05); opacity: 1; }
3478
+ }
3479
+ ` })
3480
+ ] });
3481
+ };
3482
+
3483
+ // src/hooks/useAuth.ts
3484
+ var import_react9 = require("react");
3485
+ function useAuth() {
3486
+ const { sdk, user, isAuthenticated, isLoading, sendOTP, verifyOTP, logout } = useZoPassport();
3487
+ const [otpSent, setOtpSent] = (0, import_react9.useState)(false);
3488
+ const [phoneNumber, setPhoneNumber] = (0, import_react9.useState)("");
3489
+ const [countryCode, setCountryCode] = (0, import_react9.useState)("91");
3490
+ const handleSendOTP = (0, import_react9.useCallback)(async (code, phone) => {
3491
+ setCountryCode(code);
3492
+ setPhoneNumber(phone);
3493
+ const result = await sendOTP(code, phone);
3494
+ if (result.success) {
3495
+ setOtpSent(true);
3496
+ }
3497
+ return result;
3498
+ }, [sendOTP]);
3499
+ const handleVerifyOTP = (0, import_react9.useCallback)(async (otp) => {
3500
+ const result = await verifyOTP(countryCode, phoneNumber, otp);
3501
+ if (result.success) {
3502
+ setOtpSent(false);
3503
+ }
3504
+ return result;
3505
+ }, [verifyOTP, countryCode, phoneNumber]);
3506
+ const handleLogout = (0, import_react9.useCallback)(async () => {
3507
+ await logout();
3508
+ setOtpSent(false);
3509
+ setPhoneNumber("");
3510
+ }, [logout]);
3511
+ return {
3512
+ user,
3513
+ isAuthenticated,
3514
+ isLoading,
3515
+ otpSent,
3516
+ phoneNumber,
3517
+ countryCode,
3518
+ sendOTP: handleSendOTP,
3519
+ verifyOTP: handleVerifyOTP,
3520
+ logout: handleLogout
3521
+ };
3522
+ }
3523
+
3524
+ // src/hooks/useProfile.ts
3525
+ var import_react11 = require("react");
3526
+ function useProfile() {
3527
+ const { sdk, user, isAuthenticated, refreshProfile } = useZoPassport();
3528
+ const [isLoading, setIsLoading] = (0, import_react11.useState)(false);
3529
+ const [error, setError] = (0, import_react11.useState)(null);
3530
+ const completion = user ? calculateCompletion(user) : { done: 0, total: 10, percentage: 0 };
3531
+ const isFounder = user?.membership === "founder";
3532
+ const updateProfile = (0, import_react11.useCallback)(async (updates) => {
3533
+ if (!sdk) return { success: false, error: "SDK not initialized" };
3534
+ setIsLoading(true);
3535
+ setError(null);
3536
+ try {
3537
+ const result = await sdk.updateProfile(updates);
3538
+ if (!result.success) {
3539
+ setError(result.error || "Failed to update profile");
3540
+ }
3541
+ return result;
3542
+ } finally {
3543
+ setIsLoading(false);
3544
+ }
3545
+ }, [sdk]);
3546
+ const reload = (0, import_react11.useCallback)(async () => {
3547
+ setIsLoading(true);
3548
+ try {
3549
+ await refreshProfile();
3550
+ } finally {
3551
+ setIsLoading(false);
3552
+ }
3553
+ }, [refreshProfile]);
3554
+ return {
3555
+ user,
3556
+ isAuthenticated,
3557
+ isLoading,
3558
+ error,
3559
+ completion,
3560
+ isFounder,
3561
+ updateProfile,
3562
+ reload
3563
+ };
3564
+ }
3565
+ function calculateCompletion(user) {
3566
+ const fields = [
3567
+ user.first_name,
3568
+ user.last_name,
3569
+ user.bio,
3570
+ user.date_of_birth,
3571
+ user.place_name,
3572
+ user.body_type,
3573
+ user.pfp_image,
3574
+ user.email_address,
3575
+ user.wallet_address,
3576
+ (user.cultures?.length ?? 0) > 0
3577
+ ];
3578
+ const done = fields.filter((f) => !!f).length;
3579
+ const total = fields.length;
3580
+ const percentage = Math.round(done / total * 100);
3581
+ return { done, total, percentage };
3582
+ }
3583
+
3584
+ // src/hooks/useAvatar.ts
3585
+ var import_react13 = require("react");
3586
+ function useAvatar() {
3587
+ const { sdk, user, refreshProfile } = useZoPassport();
3588
+ const [isGenerating, setIsGenerating] = (0, import_react13.useState)(false);
3589
+ const [progress, setProgress] = (0, import_react13.useState)(null);
3590
+ const [error, setError] = (0, import_react13.useState)(null);
3591
+ const avatarUrl = user?.avatar?.image || user?.pfp_image;
3592
+ const avatarStatus = user?.avatar?.status || "completed";
3593
+ const generateAvatar = (0, import_react13.useCallback)(async (bodyType) => {
3594
+ if (!sdk) return { success: false, error: "SDK not initialized" };
3595
+ setIsGenerating(true);
3596
+ setProgress("pending");
3597
+ setError(null);
3598
+ try {
3599
+ const result = await sdk.generateAvatar(bodyType);
3600
+ if (result.success) {
3601
+ await refreshProfile();
3602
+ } else {
3603
+ setError(result.error || "Failed to generate avatar");
3604
+ }
3605
+ return result;
3606
+ } finally {
3607
+ setIsGenerating(false);
3608
+ setProgress(null);
3609
+ }
3610
+ }, [sdk, refreshProfile]);
3611
+ return {
3612
+ avatarUrl,
3613
+ avatarStatus,
3614
+ isGenerating,
3615
+ progress,
3616
+ error,
3617
+ generateAvatar
3618
+ };
3619
+ }
3620
+
3621
+ // src/hooks/useWallet.ts
3622
+ var import_react17 = require("react");
3623
+
3624
+ // src/hooks/useWalletBalance.ts
3625
+ var import_react15 = require("react");
3626
+ var useWalletBalance = (apiClient, options) => {
3627
+ const [balance, setBalance] = (0, import_react15.useState)(0);
3628
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
3629
+ const [error, setError] = (0, import_react15.useState)(null);
3630
+ const fetchBalance = (0, import_react15.useCallback)(async () => {
3631
+ if (!apiClient) {
3632
+ setError(new Error("API client not provided"));
3633
+ setIsLoading(false);
3634
+ return;
3635
+ }
3636
+ try {
3637
+ setIsLoading(true);
3638
+ const response = await apiClient.get("/api/v1/web3/token/airdrops/summary");
3639
+ const amount = response.data?.data?.total_amount ?? 0;
3640
+ setBalance(amount);
3641
+ setError(null);
3642
+ } catch (err) {
3643
+ console.error("Failed to fetch balance:", err);
3644
+ setError(err);
3645
+ } finally {
3646
+ setIsLoading(false);
3647
+ }
3648
+ }, [apiClient]);
3649
+ (0, import_react15.useEffect)(() => {
3650
+ fetchBalance();
3651
+ if (options?.autoRefetch) {
3652
+ const interval = setInterval(fetchBalance, options.refetchInterval || 3e4);
3653
+ return () => clearInterval(interval);
3654
+ }
3655
+ }, [fetchBalance, options?.autoRefetch, options?.refetchInterval]);
3656
+ return {
3657
+ balance,
3658
+ isLoading,
3659
+ error,
3660
+ refetch: fetchBalance
3661
+ };
3662
+ };
3663
+
3664
+ // src/hooks/useTransactions.ts
3665
+ var import_react16 = require("react");
3666
+ var useTransactions = (apiClient, options) => {
3667
+ const [transactions, setTransactions] = (0, import_react16.useState)([]);
3668
+ const [isLoading, setIsLoading] = (0, import_react16.useState)(true);
3669
+ const [error, setError] = (0, import_react16.useState)(null);
3670
+ const [hasMore, setHasMore] = (0, import_react16.useState)(false);
3671
+ const fetchTransactions = (0, import_react16.useCallback)(async () => {
3672
+ if (!apiClient) {
3673
+ setError(new Error("API client not provided"));
3674
+ setIsLoading(false);
3675
+ return;
3676
+ }
3677
+ try {
3678
+ setIsLoading(true);
3679
+ const response = await apiClient.get("/api/v1/profile/completion-grants/claims", {
3680
+ params: {
3681
+ limit: options?.limit || 20
3682
+ }
3683
+ });
3684
+ const results = response.data?.data?.results ?? [];
3685
+ setTransactions(results);
3686
+ setHasMore(!!response.data?.data?.next);
3687
+ setError(null);
3688
+ } catch (err) {
3689
+ console.error("Failed to fetch transactions:", err);
3690
+ setError(err);
3691
+ } finally {
3692
+ setIsLoading(false);
3693
+ }
3694
+ }, [apiClient, options?.limit]);
3695
+ const loadMore = (0, import_react16.useCallback)(async () => {
3696
+ if (!hasMore || isLoading) return;
3697
+ }, [hasMore, isLoading]);
3698
+ (0, import_react16.useEffect)(() => {
3699
+ fetchTransactions();
3700
+ if (options?.autoRefetch) {
3701
+ const interval = setInterval(fetchTransactions, 6e4);
3702
+ return () => clearInterval(interval);
3703
+ }
3704
+ }, [fetchTransactions, options?.autoRefetch]);
3705
+ return {
3706
+ transactions,
3707
+ isLoading,
3708
+ error,
3709
+ hasMore,
3710
+ refetch: fetchTransactions,
3711
+ loadMore
3712
+ };
3713
+ };
3714
+
3715
+ // src/hooks/useWallet.ts
3716
+ var useWallet = (apiClient, options) => {
3717
+ const {
3718
+ balance,
3719
+ isLoading: isLoadingBalance,
3720
+ error: balanceError,
3721
+ refetch: refetchBalance
3722
+ } = useWalletBalance(apiClient, options);
3723
+ const {
3724
+ transactions,
3725
+ isLoading: isLoadingTransactions,
3726
+ error: transactionsError,
3727
+ refetch: refetchTransactions,
3728
+ loadMore
3729
+ } = useTransactions(apiClient, options);
3730
+ const isLoading = isLoadingBalance || isLoadingTransactions;
3731
+ const error = balanceError || transactionsError;
3732
+ const refetchAll = (0, import_react17.useCallback)(async () => {
3733
+ await Promise.all([refetchBalance(), refetchTransactions()]);
3734
+ }, [refetchBalance, refetchTransactions]);
3735
+ return {
3736
+ balance,
3737
+ transactions,
3738
+ isLoading,
3739
+ error,
3740
+ refetch: refetchAll,
3741
+ loadMore
3742
+ };
3743
+ };
3744
+
3745
+ // src/react.tsx
3746
+ var import_jsx_runtime13 = require("react/jsx-runtime");
3747
+ var ZoPassportContext = (0, import_react18.createContext)(null);
3748
+ function ZoPassportProvider({
3749
+ children,
3750
+ clientKey,
3751
+ baseUrl,
3752
+ autoRefresh = true
3753
+ }) {
3754
+ const [sdk, setSdk] = (0, import_react18.useState)(null);
3755
+ const [user, setUser] = (0, import_react18.useState)(null);
3756
+ const [isAuthenticated, setIsAuthenticated] = (0, import_react18.useState)(false);
3757
+ const [isLoading, setIsLoading] = (0, import_react18.useState)(true);
3758
+ (0, import_react18.useEffect)(() => {
3759
+ const passportSdk = new ZoPassportSDK({
3760
+ clientKey,
3761
+ baseUrl,
3762
+ autoRefresh
3763
+ });
3764
+ setSdk(passportSdk);
3765
+ const loadSession = async () => {
3766
+ setIsLoading(true);
3767
+ try {
3768
+ await passportSdk.ready();
3769
+ if (passportSdk.isAuthenticated) {
3770
+ const profile = await passportSdk.getProfile();
3771
+ if (profile) {
3772
+ setUser(profile);
3773
+ setIsAuthenticated(true);
3774
+ }
3775
+ }
3776
+ } catch (error) {
3777
+ console.error("[ZoPassport] Failed to load session:", error);
3778
+ } finally {
3779
+ setIsLoading(false);
3780
+ }
3781
+ };
3782
+ loadSession();
3783
+ return () => {
3784
+ passportSdk.destroy();
3785
+ };
3786
+ }, [clientKey, baseUrl, autoRefresh]);
3787
+ const sendOTP = async (countryCode, phoneNumber) => {
3788
+ if (!sdk) return { success: false, message: "SDK not initialized" };
3789
+ return sdk.auth.sendOTP(countryCode, phoneNumber);
3790
+ };
3791
+ const verifyOTP = async (countryCode, phoneNumber, otp) => {
3792
+ if (!sdk) return { success: false, error: "SDK not initialized" };
3793
+ const result = await sdk.loginWithPhone(countryCode, phoneNumber, otp);
3794
+ if (result.success && result.user) {
3795
+ setUser(result.user);
3796
+ setIsAuthenticated(true);
3797
+ }
3798
+ return result;
3799
+ };
3800
+ const logout = async () => {
3801
+ if (!sdk) return;
3802
+ await sdk.logout();
3803
+ setUser(null);
3804
+ setIsAuthenticated(false);
3805
+ };
3806
+ const refreshProfile = async () => {
3807
+ if (!sdk) return;
3808
+ const profile = await sdk.getProfile();
3809
+ if (profile) {
3810
+ setUser(profile);
3811
+ }
3812
+ };
3813
+ const value = {
3814
+ sdk,
3815
+ user,
3816
+ isAuthenticated,
3817
+ isLoading,
3818
+ sendOTP,
3819
+ verifyOTP,
3820
+ logout,
3821
+ refreshProfile
3822
+ };
3823
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ZoPassportContext.Provider, { value, children });
3824
+ }
3825
+ function useZoPassport() {
3826
+ const context = (0, import_react18.useContext)(ZoPassportContext);
3827
+ if (!context) {
3828
+ throw new Error("useZoPassport must be used within a ZoPassportProvider");
3829
+ }
3830
+ return context;
3831
+ }
3832
+ // Annotate the CommonJS export names for ESM import in node:
3833
+ 0 && (module.exports = {
3834
+ FounderBadge,
3835
+ OTPInput,
3836
+ PhoneInput,
3837
+ WalletCard,
3838
+ WalletFullPage,
3839
+ ZoAuth,
3840
+ ZoAvatar,
3841
+ ZoLanding,
3842
+ ZoOnboarding,
3843
+ ZoPassportCard,
3844
+ ZoPassportPage,
3845
+ ZoPassportProvider,
3846
+ ZoPassportSDK,
3847
+ ZoProgressRing,
3848
+ useAuth,
3849
+ useAvatar,
3850
+ useProfile,
3851
+ useTransactions,
3852
+ useWallet,
3853
+ useWalletBalance,
3854
+ useZoPassport
3855
+ });
3856
+ //# sourceMappingURL=react.js.map