zopassport 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +407 -0
  3. package/app/.env.example +15 -0
  4. package/app/README.md +28 -0
  5. package/app/package.json +24 -0
  6. package/app/reanimated-mock.js +102 -0
  7. package/app/reanimated-mock.jsx +97 -0
  8. package/app/src/App.tsx +331 -0
  9. package/app/src/components/FounderBadge.tsx +26 -0
  10. package/app/src/components/OTPInput.tsx +149 -0
  11. package/app/src/components/PhoneInput.tsx +109 -0
  12. package/app/src/components/ZoAuth.tsx +320 -0
  13. package/app/src/components/ZoAvatar.tsx +87 -0
  14. package/app/src/components/ZoLanding.tsx +231 -0
  15. package/app/src/components/ZoOnboarding.tsx +524 -0
  16. package/app/src/components/ZoPassportCard.tsx +183 -0
  17. package/app/src/components/ZoProgressRing.tsx +57 -0
  18. package/app/src/components/index.ts +16 -0
  19. package/app/src/components/wallet/MovingShine.tsx +43 -0
  20. package/app/src/components/wallet/TransactionItem.tsx +84 -0
  21. package/app/src/components/wallet/TransactionList.tsx +65 -0
  22. package/app/src/components/wallet/WalletCard.tsx +152 -0
  23. package/app/src/components/wallet/WalletScreen.tsx +190 -0
  24. package/app/src/components/wallet/ZoToken.tsx +69 -0
  25. package/app/src/components/wallet/index.ts +8 -0
  26. package/app/src/components/wallet/styles/index.ts +4 -0
  27. package/app/src/components/wallet/styles/walletStyles.ts +210 -0
  28. package/app/src/sdk/ZoPassportSDK.ts +277 -0
  29. package/app/src/sdk/lib/api/auth.ts +223 -0
  30. package/app/src/sdk/lib/api/avatar.ts +155 -0
  31. package/app/src/sdk/lib/api/client.ts +135 -0
  32. package/app/src/sdk/lib/api/index.ts +8 -0
  33. package/app/src/sdk/lib/api/profile.ts +80 -0
  34. package/app/src/sdk/lib/api/wallet.ts +59 -0
  35. package/app/src/sdk/lib/types/auth.ts +78 -0
  36. package/app/src/sdk/lib/types/avatar.ts +22 -0
  37. package/app/src/sdk/lib/types/index.ts +8 -0
  38. package/app/src/sdk/lib/types/profile.ts +18 -0
  39. package/app/src/sdk/lib/types/wallet.ts +103 -0
  40. package/app/src/sdk/lib/types.ts +205 -0
  41. package/app/src/sdk/lib/utils/index.ts +6 -0
  42. package/app/src/sdk/lib/utils/phone.ts +71 -0
  43. package/app/src/sdk/lib/utils/storage.ts +116 -0
  44. package/app/src/sdk/lib/utils/wallet.ts +73 -0
  45. package/app/src/sdk/types.ts +205 -0
  46. package/app/src/styles.css +154 -0
  47. package/app/svg-mock.js +125 -0
  48. package/app/svg-mock.jsx +120 -0
  49. package/app/vite.config.ts +70 -0
  50. package/assets/ASSETS_MANIFEST.md +124 -0
  51. package/assets/bae.png +0 -0
  52. package/assets/bro.png +0 -0
  53. package/assets/cultural-stickers/Business.png +0 -0
  54. package/assets/cultural-stickers/Default (2).jpg +0 -0
  55. package/assets/cultural-stickers/Design.png +0 -0
  56. package/assets/cultural-stickers/FollowYourHeart.png +0 -0
  57. package/assets/cultural-stickers/Food.png +0 -0
  58. package/assets/cultural-stickers/Game.png +0 -0
  59. package/assets/cultural-stickers/Health&Fitness.png +0 -0
  60. package/assets/cultural-stickers/Home&Lifestyle.png +0 -0
  61. package/assets/cultural-stickers/Law.png +0 -0
  62. package/assets/cultural-stickers/Literature&Stories.png +0 -0
  63. package/assets/cultural-stickers/Music&Entertainment.png +0 -0
  64. package/assets/cultural-stickers/Nature&Wildlife.png +0 -0
  65. package/assets/cultural-stickers/Photography.png +0 -0
  66. package/assets/cultural-stickers/Science&Technology.png +0 -0
  67. package/assets/cultural-stickers/Spiritual.png +0 -0
  68. package/assets/cultural-stickers/Sport.png +0 -0
  69. package/assets/cultural-stickers/Stories&Journal.png +0 -0
  70. package/assets/cultural-stickers/Television&Cinema.png +0 -0
  71. package/assets/cultural-stickers/Travel&Adventure.png +0 -0
  72. package/assets/cultural-stickers/z.jpg (1).jpg +0 -0
  73. package/assets/figma-assets/landing-zo-logo.png +0 -0
  74. package/assets/images/rank1.jpeg +0 -0
  75. package/assets/index.ts +76 -0
  76. package/assets/lotties/loader.json +1216 -0
  77. package/assets/lotties/spinner.json +1 -0
  78. package/assets/videos/loading-screen-background.mp4 +0 -0
  79. package/assets/videos/opening-disks.mp4 +0 -0
  80. package/assets/wallet/constants.ts +38 -0
  81. package/assets/zo-coin.gif +0 -0
  82. package/assets/zo-fallback.png +0 -0
  83. package/dist/assets/index.d.mts +136 -0
  84. package/dist/assets/index.d.ts +136 -0
  85. package/dist/assets/index.js +133 -0
  86. package/dist/assets/index.js.map +1 -0
  87. package/dist/assets/index.mjs +100 -0
  88. package/dist/assets/index.mjs.map +1 -0
  89. package/dist/index.d.mts +789 -0
  90. package/dist/index.d.ts +789 -0
  91. package/dist/index.js +1118 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/index.mjs +1060 -0
  94. package/dist/index.mjs.map +1 -0
  95. package/dist/react-native.d.mts +537 -0
  96. package/dist/react-native.d.ts +537 -0
  97. package/dist/react-native.js +1617 -0
  98. package/dist/react-native.js.map +1 -0
  99. package/dist/react-native.mjs +1588 -0
  100. package/dist/react-native.mjs.map +1 -0
  101. package/dist/react.d.mts +824 -0
  102. package/dist/react.d.ts +824 -0
  103. package/dist/react.js +3856 -0
  104. package/dist/react.js.map +1 -0
  105. package/dist/react.mjs +3801 -0
  106. package/dist/react.mjs.map +1 -0
  107. package/package.json +112 -0
  108. package/scripts/init.js +196 -0
  109. package/scripts/postinstall.js +174 -0
  110. package/scripts/verify-build.js +121 -0
@@ -0,0 +1,57 @@
1
+ // src/components/ZoProgressRing.tsx
2
+ // Circular progress ring component
3
+
4
+ import React from 'react';
5
+
6
+ export interface ZoProgressRingProps {
7
+ /** Progress percentage (0-100) */
8
+ progress: number;
9
+ /** Size in pixels (default: 140) */
10
+ size?: number;
11
+ /** Stroke width in pixels (default: 4) */
12
+ strokeWidth?: number;
13
+ /** Primary stroke color (default: #FFFFFF) */
14
+ primaryColor?: string;
15
+ /** Secondary/background stroke color (default: rgba(255,255,255,0.2)) */
16
+ secondaryColor?: string;
17
+ }
18
+
19
+ export const ZoProgressRing: React.FC<ZoProgressRingProps> = ({
20
+ progress,
21
+ size = 140,
22
+ strokeWidth = 4,
23
+ primaryColor = '#FFFFFF',
24
+ secondaryColor = 'rgba(255,255,255,0.2)',
25
+ }) => {
26
+ const radius = (size - strokeWidth) / 2;
27
+ const circumference = radius * 2 * Math.PI;
28
+ const strokeDashoffset = circumference - (progress / 100) * circumference;
29
+
30
+ return (
31
+ <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
32
+ {/* Background circle */}
33
+ <circle
34
+ cx={size / 2}
35
+ cy={size / 2}
36
+ r={radius}
37
+ strokeWidth={strokeWidth}
38
+ fill="none"
39
+ stroke={secondaryColor}
40
+ />
41
+ {/* Progress circle */}
42
+ <circle
43
+ cx={size / 2}
44
+ cy={size / 2}
45
+ r={radius}
46
+ stroke={primaryColor}
47
+ strokeWidth={strokeWidth}
48
+ fill="none"
49
+ strokeDasharray={circumference}
50
+ strokeDashoffset={strokeDashoffset}
51
+ strokeLinecap="round"
52
+ style={{ transition: 'stroke-dashoffset 0.5s ease-in-out' }}
53
+ />
54
+ </svg>
55
+ );
56
+ };
57
+
@@ -0,0 +1,16 @@
1
+ // src/components/index.ts
2
+ // Re-export all components
3
+
4
+ export { ZoPassportCard, type ZoPassportCardProps } from './ZoPassportCard';
5
+ export { ZoProgressRing, type ZoProgressRingProps } from './ZoProgressRing';
6
+ export { ZoAvatar, type ZoAvatarProps } from './ZoAvatar';
7
+ export { PhoneInput, type PhoneInputProps } from './PhoneInput';
8
+ export { OTPInput, type OTPInputProps } from './OTPInput';
9
+ export { ZoAuth, type ZoAuthProps } from './ZoAuth';
10
+ export { ZoLanding, type ZoLandingProps } from './ZoLanding';
11
+ export { ZoOnboarding, type ZoOnboardingProps } from './ZoOnboarding';
12
+ export { FounderBadge } from './FounderBadge';
13
+
14
+ // Wallet components
15
+ export * from './wallet';
16
+
@@ -0,0 +1,43 @@
1
+ // Moving Shine Effect - Extracted from Zostel app
2
+ import React, { memo, useEffect } from 'react';
3
+ import { View, Image, StyleSheet } from 'react-native';
4
+ import Animated, {
5
+ useSharedValue,
6
+ useAnimatedStyle,
7
+ withRepeat,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+ import { WALLET_ASSETS, ANIMATIONS } from '../../../assets/wallet/constants';
11
+ import { walletStyles } from './styles/walletStyles';
12
+ import type { MovingShineProps } from '../../lib/types/wallet';
13
+
14
+ export const MovingShine: React.FC<MovingShineProps> = memo(
15
+ ({ duration = ANIMATIONS.shineDuration }) => {
16
+ const tx = useSharedValue(-100);
17
+
18
+ const animatedStyle = useAnimatedStyle(() => ({
19
+ transform: [{ rotate: '30deg' }, { translateX: tx.value }],
20
+ }));
21
+
22
+ useEffect(() => {
23
+ tx.value = withRepeat(withTiming(420, { duration }), -1, false);
24
+ }, [duration]);
25
+
26
+ return (
27
+ <Animated.View style={[styles.shineEffect, animatedStyle]}>
28
+ <Image
29
+ source={{ uri: WALLET_ASSETS.shine }}
30
+ style={StyleSheet.absoluteFillObject}
31
+ resizeMode="cover"
32
+ />
33
+ </Animated.View>
34
+ );
35
+ }
36
+ );
37
+
38
+ MovingShine.displayName = 'MovingShine';
39
+
40
+ const styles = StyleSheet.create({
41
+ shineEffect: walletStyles.shineEffect,
42
+ });
43
+
@@ -0,0 +1,84 @@
1
+ // Transaction Item Component - Extracted from Zostel app
2
+ import React, { memo } from 'react';
3
+ import { View, Text, StyleSheet } from 'react-native';
4
+ import moment from 'moment';
5
+ import { ZoToken } from './ZoToken';
6
+ import { walletStyles } from './styles/walletStyles';
7
+ import { formatBalance, getTransactionColor } from '../../lib/utils/wallet';
8
+ import type { TransactionItemProps } from '../../lib/types/wallet';
9
+
10
+ const StatusIcon: React.FC = memo(() => {
11
+ return (
12
+ <View style={styles.iconBgTilted}>
13
+ <Text style={styles.iconText}>←</Text>
14
+ </View>
15
+ );
16
+ });
17
+
18
+ StatusIcon.displayName = 'StatusIcon';
19
+
20
+ export const TransactionItem: React.FC<TransactionItemProps> = memo(
21
+ ({ transaction, showDate = true }) => {
22
+ const color = getTransactionColor(transaction.action);
23
+ const formattedAmount = formatBalance(transaction.amount);
24
+
25
+ return (
26
+ <View style={styles.txnRow}>
27
+ <StatusIcon />
28
+
29
+ <View style={styles.flex}>
30
+ <Text style={styles.description}>{transaction.description}</Text>
31
+ {showDate && transaction.claimed_at && (
32
+ <Text style={styles.date}>
33
+ {moment(transaction.claimed_at).format('DD MMM hh:mm A')}
34
+ </Text>
35
+ )}
36
+ </View>
37
+
38
+ <View style={styles.tokenContainer}>
39
+ <Text
40
+ style={[
41
+ styles.textShadow,
42
+ {
43
+ shadowColor: color,
44
+ color: color,
45
+ fontWeight: '600',
46
+ fontSize: 16,
47
+ },
48
+ ]}
49
+ >
50
+ + {formattedAmount}
51
+ </Text>
52
+ <ZoToken size={16} />
53
+ </View>
54
+ </View>
55
+ );
56
+ }
57
+ );
58
+
59
+ TransactionItem.displayName = 'TransactionItem';
60
+
61
+ const styles = StyleSheet.create({
62
+ txnRow: walletStyles.txnRow,
63
+ iconBgTilted: walletStyles.iconBgTilted,
64
+ tokenContainer: walletStyles.tokenContainer,
65
+ textShadow: walletStyles.textShadow,
66
+ flex: {
67
+ flex: 1,
68
+ },
69
+ iconText: {
70
+ color: 'rgba(255, 255, 255, 0.6)',
71
+ fontSize: 16,
72
+ },
73
+ description: {
74
+ color: '#FFFFFF',
75
+ fontSize: 16,
76
+ fontWeight: '500',
77
+ },
78
+ date: {
79
+ color: 'rgba(255, 255, 255, 0.44)',
80
+ fontSize: 12,
81
+ marginTop: 2,
82
+ },
83
+ });
84
+
@@ -0,0 +1,65 @@
1
+ // Transaction List Component - Extracted from Zostel app
2
+ import React, { memo } from 'react';
3
+ import { View, ScrollView, Text, ActivityIndicator, StyleSheet } from 'react-native';
4
+ import { TransactionItem } from './TransactionItem';
5
+ import { walletStyles } from './styles/walletStyles';
6
+ import type { TransactionListProps } from '../../lib/types/wallet';
7
+
8
+ export const TransactionList: React.FC<TransactionListProps> = memo(
9
+ ({ transactions, isLoading, onEndReached }) => {
10
+ if (isLoading && !transactions?.length) {
11
+ return (
12
+ <View style={styles.loader}>
13
+ <ActivityIndicator size="large" color="#00C853" />
14
+ </View>
15
+ );
16
+ }
17
+
18
+ if (!transactions?.length) {
19
+ return (
20
+ <View style={styles.emptyState}>
21
+ <Text style={styles.emptyText}>No transactions yet</Text>
22
+ <Text style={styles.emptySubtext}>
23
+ Complete quests to earn $Zo tokens
24
+ </Text>
25
+ </View>
26
+ );
27
+ }
28
+
29
+ return (
30
+ <View style={styles.txnContent}>
31
+ {transactions.map((transaction, index) => (
32
+ <TransactionItem key={transaction.id || index} transaction={transaction} />
33
+ ))}
34
+ </View>
35
+ );
36
+ }
37
+ );
38
+
39
+ TransactionList.displayName = 'TransactionList';
40
+
41
+ const styles = StyleSheet.create({
42
+ txnContent: walletStyles.txnContent,
43
+ loader: {
44
+ padding: 40,
45
+ alignItems: 'center',
46
+ justifyContent: 'center',
47
+ },
48
+ emptyState: {
49
+ padding: 40,
50
+ alignItems: 'center',
51
+ justifyContent: 'center',
52
+ },
53
+ emptyText: {
54
+ color: '#FFFFFF',
55
+ fontSize: 18,
56
+ fontWeight: '600',
57
+ marginBottom: 8,
58
+ },
59
+ emptySubtext: {
60
+ color: 'rgba(255, 255, 255, 0.44)',
61
+ fontSize: 14,
62
+ textAlign: 'center',
63
+ },
64
+ });
65
+
@@ -0,0 +1,152 @@
1
+ // Wallet Card Component - Extracted from Zostel app
2
+ import React, { memo, useEffect } from 'react';
3
+ import { View, Text, Image, Pressable, StyleSheet } from 'react-native';
4
+ import Animated, {
5
+ useSharedValue,
6
+ useAnimatedStyle,
7
+ withTiming,
8
+ FadeIn,
9
+ } from 'react-native-reanimated';
10
+ import { MovingShine } from './MovingShine';
11
+ import { ZoTokenVideo } from './ZoToken';
12
+ import { WALLET_ASSETS, ANIMATIONS } from '../../../assets/wallet/constants';
13
+ import { walletStyles } from './styles/walletStyles';
14
+ import { formatBalance, formatWalletAddress, formatNickname } from '../../lib/utils/wallet';
15
+ import type { WalletCardProps } from '../../lib/types/wallet';
16
+
17
+ export const WalletCard: React.FC<WalletCardProps> = memo(
18
+ ({ balance, user, isOpen = false, onToggle, isLoading }) => {
19
+ const bgY = useSharedValue(0);
20
+ const cardY = useSharedValue(0);
21
+
22
+ const animatedBackgroundStyle = useAnimatedStyle(() => ({
23
+ transform: [{ translateY: bgY.value }],
24
+ }));
25
+
26
+ const animatedCardStyle = useAnimatedStyle(() => ({
27
+ transform: [{ translateY: cardY.value }],
28
+ }));
29
+
30
+ useEffect(() => {
31
+ bgY.value = withTiming(isOpen ? 200 : 0, { duration: ANIMATIONS.cardTransition });
32
+ cardY.value = withTiming(isOpen ? -150 : 0, { duration: ANIMATIONS.cardTransition });
33
+ }, [isOpen]);
34
+
35
+ const displayName = user.nickname
36
+ ? formatNickname(user.nickname)
37
+ : user.first_name || 'You';
38
+
39
+ const walletText = `${displayName}'s wallet`;
40
+
41
+ return (
42
+ <Pressable
43
+ style={styles.cardPressContainer}
44
+ onPress={onToggle}
45
+ >
46
+ <Animated.View style={[styles.card, animatedBackgroundStyle]}>
47
+ {/* Background Image */}
48
+ <Image
49
+ source={{ uri: WALLET_ASSETS.walletBackground }}
50
+ style={StyleSheet.absoluteFillObject}
51
+ resizeMode="contain"
52
+ />
53
+
54
+ {/* Shadow */}
55
+ <Animated.View
56
+ style={styles.cardShadow}
57
+ entering={FadeIn.duration(ANIMATIONS.fadeInDuration)}
58
+ />
59
+
60
+ {/* Card Content */}
61
+ <Animated.View style={[styles.cardContainer, animatedCardStyle]}>
62
+ <View style={styles.cardContent}>
63
+ {/* Balance Row */}
64
+ <View style={styles.balanceRow}>
65
+ <View style={styles.balanceWrapper}>
66
+ <Text style={[styles.whiteText, styles.balanceAmount]}>
67
+ {formatBalance(balance)}
68
+ </Text>
69
+ <Text style={[styles.grayText, styles.currency]}>
70
+ $Zo
71
+ </Text>
72
+ </View>
73
+ <ZoTokenVideo size={24} />
74
+ </View>
75
+
76
+ <View style={styles.flex} />
77
+
78
+ {/* User Info */}
79
+ {user && (
80
+ <View style={styles.avatarInfo}>
81
+ {user.avatar?.image && (
82
+ <Image
83
+ source={{ uri: user.avatar.image }}
84
+ style={styles.avatar}
85
+ />
86
+ )}
87
+ <View style={styles.flex}>
88
+ <Text style={[styles.whiteText, styles.userName]}>
89
+ {displayName}
90
+ </Text>
91
+ <Text style={[styles.grayText, styles.walletAddress]}>
92
+ {formatWalletAddress(user.wallet_address || '')}
93
+ </Text>
94
+ </View>
95
+ </View>
96
+ )}
97
+
98
+ {/* Shine Effect */}
99
+ <View style={styles.shineContainer}>
100
+ <MovingShine />
101
+ </View>
102
+ </View>
103
+ </Animated.View>
104
+
105
+ {/* Card Cover */}
106
+ <View style={styles.cardCover}>
107
+ <Image
108
+ source={{ uri: WALLET_ASSETS.walletCover }}
109
+ style={StyleSheet.absoluteFillObject}
110
+ resizeMode="cover"
111
+ />
112
+ <View style={styles.cardCoverTextContainer}>
113
+ <Text style={[styles.grayText, styles.cardCoverText]}>
114
+ {walletText}
115
+ </Text>
116
+ </View>
117
+ </View>
118
+ </Animated.View>
119
+ </Pressable>
120
+ );
121
+ }
122
+ );
123
+
124
+ WalletCard.displayName = 'WalletCard';
125
+
126
+ const styles = StyleSheet.create({
127
+ ...walletStyles,
128
+ balanceAmount: {
129
+ fontSize: 24,
130
+ fontWeight: '700',
131
+ },
132
+ currency: {
133
+ fontSize: 12,
134
+ },
135
+ userName: {
136
+ fontSize: 16,
137
+ fontWeight: '600',
138
+ },
139
+ walletAddress: {
140
+ fontSize: 12,
141
+ },
142
+ avatar: {
143
+ width: 32,
144
+ height: 32,
145
+ borderRadius: 16,
146
+ },
147
+ cardCoverText: {
148
+ fontSize: 16,
149
+ fontWeight: '600',
150
+ },
151
+ });
152
+
@@ -0,0 +1,190 @@
1
+ // Wallet Screen Component - Complete wallet screen from Zostel app
2
+ import React, { useState, useCallback, useMemo, memo } from 'react';
3
+ import {
4
+ View,
5
+ Text,
6
+ ScrollView,
7
+ Pressable,
8
+ ActivityIndicator,
9
+ StyleSheet,
10
+ SafeAreaView,
11
+ } from 'react-native';
12
+ import Animated, { FadeIn, FadeOut, FadeInUp, FadeOutUp, FadeInDown } from 'react-native-reanimated';
13
+ import { WalletCard } from './WalletCard';
14
+ import { TransactionList } from './TransactionList';
15
+ import { walletStyles } from './styles/walletStyles';
16
+ import type { WalletScreenProps } from '../../lib/types/wallet';
17
+
18
+ // Mock data for demo
19
+ const MOCK_USER = {
20
+ avatar: {
21
+ image: 'https://i.pravatar.cc/150?img=12',
22
+ },
23
+ first_name: 'John',
24
+ nickname: '@johndoe',
25
+ wallet_address: '0x1234567890abcdef1234567890abcdef12345678',
26
+ };
27
+
28
+ const MOCK_TRANSACTIONS = [
29
+ {
30
+ id: '1',
31
+ created_at: '2025-12-10T10:30:00Z',
32
+ updated_at: '2025-12-10T10:30:00Z',
33
+ amount: 100,
34
+ description: 'Completed profile',
35
+ claimed_at: '2025-12-10T10:30:00Z',
36
+ grant: {
37
+ id: 'g1',
38
+ name: 'Profile Completion',
39
+ description: 'Complete your profile',
40
+ },
41
+ action: 'deposit' as const,
42
+ },
43
+ {
44
+ id: '2',
45
+ created_at: '2025-12-09T15:20:00Z',
46
+ updated_at: '2025-12-09T15:20:00Z',
47
+ amount: 250,
48
+ description: 'Quest: Explore 3 locations',
49
+ claimed_at: '2025-12-09T15:20:00Z',
50
+ grant: {
51
+ id: 'g2',
52
+ name: 'Explorer Quest',
53
+ description: 'Visit locations',
54
+ },
55
+ action: 'deposit' as const,
56
+ },
57
+ {
58
+ id: '3',
59
+ created_at: '2025-12-08T09:15:00Z',
60
+ updated_at: '2025-12-08T09:15:00Z',
61
+ amount: 500,
62
+ description: 'Booking reward',
63
+ claimed_at: '2025-12-08T09:15:00Z',
64
+ grant: {
65
+ id: 'g3',
66
+ name: 'Booking Reward',
67
+ description: 'Book a stay',
68
+ },
69
+ action: 'deposit' as const,
70
+ },
71
+ ];
72
+
73
+ export const WalletScreen: React.FC<WalletScreenProps> = memo(({ onBack }) => {
74
+ const [isOpen, setIsOpen] = useState(false);
75
+ const [isTitleVisible, setIsTitleVisible] = useState(false);
76
+ const [isLoading, setIsLoading] = useState(false);
77
+
78
+ // In real implementation, fetch from API
79
+ const balance = 850;
80
+ const user = MOCK_USER;
81
+ const transactions = MOCK_TRANSACTIONS;
82
+
83
+ const toggleView = useCallback(() => {
84
+ setIsOpen((prev) => !prev);
85
+ }, []);
86
+
87
+ const handleScroll = useCallback((event: any) => {
88
+ const scrollY = event.nativeEvent.contentOffset.y;
89
+ setIsTitleVisible(scrollY > 200);
90
+ }, []);
91
+
92
+ const backdrop = useMemo(
93
+ () =>
94
+ isOpen ? (
95
+ <Pressable onPress={toggleView} style={styles.openBg}>
96
+ <View />
97
+ </Pressable>
98
+ ) : null,
99
+ [isOpen, toggleView]
100
+ );
101
+
102
+ const description = useMemo(
103
+ () =>
104
+ isOpen ? (
105
+ <Animated.View entering={FadeIn} exiting={FadeOut} style={styles.zoDescriptionContainer}>
106
+ <Text style={styles.description}>
107
+ Get Zo World coins as airdrop by completing quests, stay & trips.
108
+ </Text>
109
+ </Animated.View>
110
+ ) : null,
111
+ [isOpen]
112
+ );
113
+
114
+ const walletText = `${user.nickname || user.first_name}'s wallet`;
115
+
116
+ return (
117
+ <View style={styles.screen}>
118
+ {/* Header */}
119
+ <View style={styles.header}>
120
+ <Pressable onPress={onBack}>
121
+ <Text style={styles.backButton}>←</Text>
122
+ </Pressable>
123
+ <Animated.View pointerEvents="none" style={styles.titleContainer}>
124
+ {isTitleVisible && (
125
+ <Animated.View entering={FadeInUp} exiting={FadeOutUp}>
126
+ <Text style={[styles.whiteText, styles.headerTitle]}>
127
+ {walletText}
128
+ </Text>
129
+ </Animated.View>
130
+ )}
131
+ </Animated.View>
132
+ </View>
133
+
134
+ {/* Content */}
135
+ <ScrollView
136
+ contentContainerStyle={styles.container}
137
+ onScroll={handleScroll}
138
+ scrollEventThrottle={16}
139
+ showsVerticalScrollIndicator={false}
140
+ >
141
+ {isLoading ? (
142
+ <Animated.View key="loader" entering={FadeIn} exiting={FadeOut} style={styles.loader}>
143
+ <ActivityIndicator size="large" color="#00C853" />
144
+ </Animated.View>
145
+ ) : (
146
+ <Animated.View entering={FadeInDown} style={styles.container}>
147
+ <SafeAreaView />
148
+
149
+ {/* Transactions */}
150
+ <TransactionList transactions={transactions} isLoading={false} />
151
+
152
+ {/* Backdrop */}
153
+ {backdrop}
154
+
155
+ {/* Wallet Card */}
156
+ <WalletCard
157
+ balance={balance}
158
+ user={user}
159
+ isOpen={isOpen}
160
+ onToggle={toggleView}
161
+ isLoading={isLoading}
162
+ />
163
+
164
+ <View style={styles.bar} />
165
+ <SafeAreaView />
166
+ </Animated.View>
167
+ )}
168
+ </ScrollView>
169
+
170
+ {/* Description */}
171
+ {description}
172
+ </View>
173
+ );
174
+ });
175
+
176
+ WalletScreen.displayName = 'WalletScreen';
177
+
178
+ const styles = StyleSheet.create({
179
+ ...walletStyles,
180
+ backButton: {
181
+ color: '#FFFFFF',
182
+ fontSize: 24,
183
+ fontWeight: '600',
184
+ },
185
+ headerTitle: {
186
+ fontSize: 18,
187
+ fontWeight: '600',
188
+ },
189
+ });
190
+
@@ -0,0 +1,69 @@
1
+ // $Zo Token Components - Extracted from Zostel app
2
+ import React from 'react';
3
+ import { View, StyleSheet } from 'react-native';
4
+ import Svg, { Circle, Defs, LinearGradient, Stop, Path } from 'react-native-svg';
5
+
6
+ interface ZoTokenProps {
7
+ size?: number;
8
+ style?: any;
9
+ }
10
+
11
+ /**
12
+ * Static $Zo Token Icon
13
+ */
14
+ export const ZoToken: React.FC<ZoTokenProps> = ({ size = 16, style }) => {
15
+ return (
16
+ <View style={[{ width: size, height: size }, style]}>
17
+ <Svg width={size} height={size} viewBox="0 0 32 32" fill="none">
18
+ <Defs>
19
+ <LinearGradient id="zoGradient" x1="0%" y1="0%" x2="100%" y2="100%">
20
+ <Stop offset="0%" stopColor="#00E676" stopOpacity="1" />
21
+ <Stop offset="100%" stopColor="#00C853" stopOpacity="1" />
22
+ </LinearGradient>
23
+ </Defs>
24
+ <Circle cx="16" cy="16" r="16" fill="url(#zoGradient)" />
25
+ <Path
26
+ d="M9 11h14l-10 10h10"
27
+ stroke="white"
28
+ strokeWidth="2"
29
+ strokeLinecap="round"
30
+ strokeLinejoin="round"
31
+ />
32
+ </Svg>
33
+ </View>
34
+ );
35
+ };
36
+
37
+ /**
38
+ * Animated $Zo Token Video Component
39
+ * Note: In production this uses a video/lottie animation
40
+ * This is a simplified static version
41
+ */
42
+ export const ZoTokenVideo: React.FC<ZoTokenProps> = ({ size = 24, style }) => {
43
+ return (
44
+ <View
45
+ style={[
46
+ {
47
+ width: size,
48
+ height: size,
49
+ borderRadius: size,
50
+ overflow: 'hidden',
51
+ backgroundColor: '#00C853',
52
+ alignItems: 'center',
53
+ justifyContent: 'center',
54
+ },
55
+ style,
56
+ ]}
57
+ >
58
+ <ZoToken size={size * 0.6} />
59
+ </View>
60
+ );
61
+ };
62
+
63
+ const styles = StyleSheet.create({
64
+ container: {
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ },
68
+ });
69
+
@@ -0,0 +1,8 @@
1
+ // Export all wallet components
2
+ export { WalletScreen } from './WalletScreen';
3
+ export { WalletCard } from './WalletCard';
4
+ export { TransactionList } from './TransactionList';
5
+ export { TransactionItem } from './TransactionItem';
6
+ export { MovingShine } from './MovingShine';
7
+ export { ZoToken, ZoTokenVideo } from './ZoToken';
8
+
@@ -0,0 +1,4 @@
1
+ // Export all styles
2
+ export { walletStyles } from './walletStyles';
3
+ export { default as styles } from './walletStyles';
4
+