@oxyhq/services 5.1.11 → 5.1.13

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 (102) hide show
  1. package/lib/commonjs/ui/components/OxyProvider.js +50 -30
  2. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  3. package/lib/commonjs/ui/components/OxySignInButton.js +4 -4
  4. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  5. package/lib/commonjs/ui/context/OxyContext.js +15 -7
  6. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  7. package/lib/commonjs/ui/navigation/OxyRouter.js +47 -7
  8. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  9. package/lib/commonjs/ui/screens/ProfileScreen.js +346 -0
  10. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -0
  11. package/lib/commonjs/ui/screens/SignInScreen.js +82 -86
  12. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  13. package/lib/commonjs/ui/screens/SignUpScreen.js +190 -108
  14. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +88 -0
  16. package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -0
  17. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +364 -0
  18. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -0
  19. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +202 -0
  20. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -0
  21. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +148 -0
  22. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -0
  23. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +127 -0
  24. package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -0
  25. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +105 -0
  26. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -0
  27. package/lib/commonjs/ui/styles/theme.js +1 -2
  28. package/lib/commonjs/ui/styles/theme.js.map +1 -1
  29. package/lib/module/ui/components/OxyProvider.js +51 -31
  30. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  31. package/lib/module/ui/components/OxySignInButton.js +4 -4
  32. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  33. package/lib/module/ui/context/OxyContext.js +15 -7
  34. package/lib/module/ui/context/OxyContext.js.map +1 -1
  35. package/lib/module/ui/navigation/OxyRouter.js +47 -7
  36. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  37. package/lib/module/ui/screens/ProfileScreen.js +340 -0
  38. package/lib/module/ui/screens/ProfileScreen.js.map +1 -0
  39. package/lib/module/ui/screens/SignInScreen.js +83 -87
  40. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  41. package/lib/module/ui/screens/SignUpScreen.js +189 -109
  42. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  43. package/lib/module/ui/screens/karma/KarmaAboutScreen.js +83 -0
  44. package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -0
  45. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +358 -0
  46. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -0
  47. package/lib/module/ui/screens/karma/KarmaFAQScreen.js +197 -0
  48. package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -0
  49. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +142 -0
  50. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -0
  51. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +122 -0
  52. package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -0
  53. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +100 -0
  54. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -0
  55. package/lib/module/ui/styles/theme.js +1 -2
  56. package/lib/module/ui/styles/theme.js.map +1 -1
  57. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  58. package/lib/typescript/ui/components/OxySignInButton.d.ts +5 -0
  59. package/lib/typescript/ui/components/OxySignInButton.d.ts.map +1 -1
  60. package/lib/typescript/ui/context/OxyContext.d.ts +4 -1
  61. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  62. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  63. package/lib/typescript/ui/screens/ProfileScreen.d.ts +9 -0
  64. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -0
  65. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  66. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  67. package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts +5 -0
  68. package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -0
  69. package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts +5 -0
  70. package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -0
  71. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts +5 -0
  72. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -0
  73. package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts +5 -0
  74. package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -0
  75. package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts +5 -0
  76. package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -0
  77. package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts +5 -0
  78. package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -0
  79. package/lib/typescript/ui/styles/theme.d.ts +0 -1
  80. package/lib/typescript/ui/styles/theme.d.ts.map +1 -1
  81. package/package.json +4 -3
  82. package/src/ui/components/OxyProvider.tsx +42 -29
  83. package/src/ui/components/OxySignInButton.tsx +9 -3
  84. package/src/ui/context/OxyContext.tsx +16 -8
  85. package/src/ui/navigation/OxyRouter.tsx +44 -7
  86. package/src/ui/screens/ProfileScreen.tsx +155 -0
  87. package/src/ui/screens/SignInScreen.tsx +68 -73
  88. package/src/ui/screens/SignUpScreen.tsx +136 -87
  89. package/src/ui/screens/karma/KarmaAboutScreen.tsx +45 -0
  90. package/src/ui/screens/karma/KarmaCenterScreen.tsx +271 -0
  91. package/src/ui/screens/karma/KarmaFAQScreen.tsx +164 -0
  92. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +79 -0
  93. package/src/ui/screens/karma/KarmaRewardsScreen.tsx +55 -0
  94. package/src/ui/screens/karma/KarmaRulesScreen.tsx +65 -0
  95. package/src/ui/styles/theme.ts +1 -2
  96. package/lib/commonjs/ui/screens/AboutKarmaScreen.js +0 -50
  97. package/lib/commonjs/ui/screens/AboutKarmaScreen.js.map +0 -1
  98. package/lib/module/ui/screens/AboutKarmaScreen.js +0 -45
  99. package/lib/module/ui/screens/AboutKarmaScreen.js.map +0 -1
  100. package/lib/typescript/ui/screens/AboutKarmaScreen.d.ts +0 -5
  101. package/lib/typescript/ui/screens/AboutKarmaScreen.d.ts.map +0 -1
  102. package/src/ui/screens/AboutKarmaScreen.tsx +0 -58
@@ -0,0 +1,271 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ ActivityIndicator,
8
+ ScrollView,
9
+ Alert,
10
+ Platform,
11
+ } from 'react-native';
12
+ import { BaseScreenProps } from '../../navigation/types';
13
+ import { useOxy } from '../../context/OxyContext';
14
+ import { fontFamilies } from '../../styles/fonts';
15
+ import Avatar from '../../components/Avatar';
16
+ import { Ionicons } from '@expo/vector-icons';
17
+
18
+ const KarmaCenterScreen: React.FC<BaseScreenProps> = ({
19
+ onClose,
20
+ theme,
21
+ navigate,
22
+ goBack,
23
+ }) => {
24
+ const { user, oxyServices } = useOxy();
25
+ const [karmaTotal, setKarmaTotal] = useState<number | null>(null);
26
+ const [karmaHistory, setKarmaHistory] = useState<any[]>([]);
27
+ const [isLoading, setIsLoading] = useState(true);
28
+ const [error, setError] = useState<string | null>(null);
29
+
30
+ const isDarkTheme = theme === 'dark';
31
+ const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
32
+ const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
33
+ const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#F5F5F5';
34
+ const borderColor = isDarkTheme ? '#444444' : '#E0E0E0';
35
+ const primaryColor = '#d169e5';
36
+
37
+ useEffect(() => {
38
+ if (!user) return;
39
+ setIsLoading(true);
40
+ setError(null);
41
+ Promise.all([
42
+ oxyServices.getUserKarmaTotal(user.id),
43
+ oxyServices.getUserKarmaHistory(user.id, 20, 0),
44
+ ])
45
+ .then(([totalRes, historyRes]) => {
46
+ setKarmaTotal(totalRes.total);
47
+ setKarmaHistory(Array.isArray(historyRes.history) ? historyRes.history : []);
48
+ })
49
+ .catch((err) => {
50
+ setError(err.message || 'Failed to load karma data');
51
+ })
52
+ .finally(() => setIsLoading(false));
53
+ }, [user]);
54
+
55
+ if (!user) {
56
+ return (
57
+ <View style={[styles.container, { backgroundColor }]}>
58
+ <Text style={[styles.message, { color: textColor }]}>Not signed in</Text>
59
+ </View>
60
+ );
61
+ }
62
+
63
+ if (isLoading) {
64
+ return (
65
+ <View style={[styles.container, { backgroundColor, justifyContent: 'center' }]}>
66
+ <ActivityIndicator size="large" color={primaryColor} />
67
+ </View>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <View style={[styles.container, { backgroundColor }]}>
73
+ <ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContainer}>
74
+ <View style={styles.walletHeader}>
75
+ <Avatar
76
+ imageUrl={user.avatarUrl}
77
+ name={user.username}
78
+ size={60}
79
+ theme={theme}
80
+ style={styles.avatar}
81
+ />
82
+ <Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Karma Balance</Text>
83
+ <Text style={[styles.karmaAmount, { color: primaryColor }]}>{karmaTotal ?? 0}</Text>
84
+ <View style={styles.actionRow}>
85
+ <TouchableOpacity style={styles.actionIconWrapper} onPress={() => navigate && navigate('KarmaLeaderboard')}>
86
+ <View style={[styles.actionIcon, { backgroundColor: '#E0E0E0' }]}>
87
+ <Ionicons name="trophy-outline" size={28} color="#888" />
88
+ </View>
89
+ <Text style={styles.actionLabel}>Leaderboard</Text>
90
+ </TouchableOpacity>
91
+ <TouchableOpacity style={styles.actionIconWrapper} onPress={() => navigate && navigate('KarmaRules')}>
92
+ <View style={[styles.actionIcon, { backgroundColor: '#E0E0E0' }]}>
93
+ <Ionicons name="document-text-outline" size={28} color="#888" />
94
+ </View>
95
+ <Text style={styles.actionLabel}>Rules</Text>
96
+ </TouchableOpacity>
97
+ <TouchableOpacity style={styles.actionIconWrapper} onPress={() => navigate && navigate('AboutKarma')}>
98
+ <View style={[styles.actionIcon, { backgroundColor: '#E0E0E0' }]}>
99
+ <Ionicons name="star-outline" size={28} color="#888" />
100
+ </View>
101
+ <Text style={styles.actionLabel}>About</Text>
102
+ </TouchableOpacity>
103
+ <TouchableOpacity style={styles.actionIconWrapper} onPress={() => navigate && navigate('KarmaRewards')}>
104
+ <View style={[styles.actionIcon, { backgroundColor: '#E0E0E0' }]}>
105
+ <Ionicons name="gift-outline" size={28} color="#888" />
106
+ </View>
107
+ <Text style={styles.actionLabel}>Rewards</Text>
108
+ </TouchableOpacity>
109
+ <TouchableOpacity style={styles.actionIconWrapper} onPress={() => navigate && navigate('KarmaFAQ')}>
110
+ <View style={[styles.actionIcon, { backgroundColor: '#E0E0E0' }]}>
111
+ <Ionicons name="help-circle-outline" size={28} color="#888" />
112
+ </View>
113
+ <Text style={styles.actionLabel}>FAQ</Text>
114
+ </TouchableOpacity>
115
+ </View>
116
+ <Text style={styles.infoText}>Karma can only be earned by positive actions in the Oxy Ecosystem. It cannot be sent or received directly.</Text>
117
+ </View>
118
+ <Text style={[styles.sectionTitle, { color: textColor }]}>Karma History</Text>
119
+ <View style={styles.historyContainer}>
120
+ {karmaHistory.length === 0 ? (
121
+ <Text style={{ color: textColor, textAlign: 'center', marginTop: 16 }}>No karma history yet.</Text>
122
+ ) : (
123
+ karmaHistory.map((entry: any) => (
124
+ <View key={entry.id} style={[styles.historyItem, { borderColor }]}>
125
+ <Text style={[styles.historyPoints, { color: entry.points > 0 ? primaryColor : '#D32F2F' }]}>
126
+ {entry.points > 0 ? '+' : ''}{entry.points}
127
+ </Text>
128
+ <Text style={[styles.historyDesc, { color: textColor }]}>
129
+ {entry.reason || 'No description'}
130
+ </Text>
131
+ <Text style={[styles.historyDate, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>
132
+ {entry.createdAt ? new Date(entry.createdAt).toLocaleString() : ''}
133
+ </Text>
134
+ </View>
135
+ ))
136
+ )}
137
+ </View>
138
+ {error && <Text style={{ color: '#D32F2F', marginTop: 16, textAlign: 'center' }}>{error}</Text>}
139
+ </ScrollView>
140
+ <View style={styles.footer}>
141
+ <TouchableOpacity style={styles.closeButton} onPress={onClose}>
142
+ <Text style={[styles.closeButtonText, { color: primaryColor }]}>Close</Text>
143
+ </TouchableOpacity>
144
+ </View>
145
+ </View>
146
+ );
147
+ };
148
+
149
+ const styles = StyleSheet.create({
150
+ container: {
151
+ flex: 1,
152
+ },
153
+ scrollView: {
154
+ flex: 1,
155
+ },
156
+ scrollContainer: {
157
+ padding: 0,
158
+ alignItems: 'center',
159
+ },
160
+ walletHeader: {
161
+ alignItems: 'center',
162
+ paddingTop: 36,
163
+ paddingBottom: 24,
164
+ width: '100%',
165
+ backgroundColor: 'transparent',
166
+ },
167
+ avatar: {
168
+ marginBottom: 12,
169
+ },
170
+ karmaLabel: {
171
+ fontSize: 16,
172
+ marginBottom: 4,
173
+ fontFamily: fontFamilies.phudu,
174
+ },
175
+ karmaAmount: {
176
+ fontSize: 48,
177
+ fontWeight: 'bold',
178
+ marginBottom: 18,
179
+ fontFamily: fontFamilies.phuduBold,
180
+ },
181
+ actionRow: {
182
+ flexDirection: 'row',
183
+ justifyContent: 'center',
184
+ marginBottom: 18,
185
+ flexWrap: 'wrap',
186
+ rowGap: 0,
187
+ columnGap: 0,
188
+ },
189
+ actionIconWrapper: {
190
+ alignItems: 'center',
191
+ marginHorizontal: 8,
192
+ marginVertical: 4,
193
+ width: 72,
194
+ },
195
+ actionIcon: {
196
+ width: 56,
197
+ height: 56,
198
+ borderRadius: 28,
199
+ alignItems: 'center',
200
+ justifyContent: 'center',
201
+ marginBottom: 6,
202
+ opacity: 0.5,
203
+ },
204
+ actionIconText: {
205
+ fontSize: 28,
206
+ },
207
+ actionLabel: {
208
+ fontSize: 13,
209
+ color: '#888',
210
+ },
211
+ infoText: {
212
+ fontSize: 13,
213
+ color: '#888',
214
+ textAlign: 'center',
215
+ marginTop: 8,
216
+ marginBottom: 8,
217
+ maxWidth: 320,
218
+ },
219
+ sectionTitle: {
220
+ fontSize: 18,
221
+ fontWeight: '600',
222
+ marginBottom: 12,
223
+ marginTop: 8,
224
+ alignSelf: 'flex-start',
225
+ marginLeft: 24,
226
+ },
227
+ historyContainer: {
228
+ borderRadius: 15,
229
+ overflow: 'hidden',
230
+ marginBottom: 20,
231
+ width: '100%',
232
+ paddingHorizontal: 12,
233
+ },
234
+ historyItem: {
235
+ padding: 14,
236
+ borderBottomWidth: 1,
237
+ },
238
+ historyPoints: {
239
+ fontSize: 16,
240
+ fontWeight: '700',
241
+ },
242
+ historyDesc: {
243
+ fontSize: 15,
244
+ marginTop: 2,
245
+ },
246
+ historyDate: {
247
+ fontSize: 13,
248
+ marginTop: 2,
249
+ },
250
+ footer: {
251
+ padding: 16,
252
+ borderTopWidth: 1,
253
+ borderTopColor: '#E0E0E0',
254
+ alignItems: 'center',
255
+ },
256
+ closeButton: {
257
+ paddingVertical: 8,
258
+ paddingHorizontal: 16,
259
+ },
260
+ closeButtonText: {
261
+ fontSize: 16,
262
+ fontWeight: '600',
263
+ },
264
+ message: {
265
+ fontSize: 16,
266
+ textAlign: 'center',
267
+ marginTop: 24,
268
+ },
269
+ });
270
+
271
+ export default KarmaCenterScreen;
@@ -0,0 +1,164 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, Platform, TouchableOpacity, TextInput, LayoutAnimation, UIManager } from 'react-native';
3
+ import { BaseScreenProps } from '../../navigation/types';
4
+ import { Ionicons } from '@expo/vector-icons';
5
+
6
+ if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
7
+ UIManager.setLayoutAnimationEnabledExperimental(true);
8
+ }
9
+
10
+ const FAQS = [
11
+ {
12
+ q: 'What is karma?',
13
+ a: 'Karma is a recognition of your positive actions in the Oxy Ecosystem. It cannot be sent or received directly.'
14
+ },
15
+ {
16
+ q: 'How do I earn karma?',
17
+ a: 'By helping others, reporting bugs, contributing content, and participating in community events.'
18
+ },
19
+ {
20
+ q: 'Can I lose karma?',
21
+ a: 'Karma may be reduced for negative actions or breaking community rules.'
22
+ },
23
+ {
24
+ q: 'What can I do with karma?',
25
+ a: 'Unlock rewards, badges, and special features as you earn more karma.'
26
+ },
27
+ {
28
+ q: 'Can I transfer karma to others?',
29
+ a: 'No, karma cannot be sent or received. It is only earned by your actions.'
30
+ },
31
+ {
32
+ q: 'How do I get support?',
33
+ a: 'Contact Oxy support via the app or website for any karma-related questions.'
34
+ },
35
+ ];
36
+
37
+ const KarmaFAQScreen: React.FC<BaseScreenProps> = ({ goBack, theme }) => {
38
+ const isDarkTheme = theme === 'dark';
39
+ const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
40
+ const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
41
+ const cardColor = isDarkTheme ? '#23232b' : '#f7f7fa';
42
+ const primaryColor = '#d169e5';
43
+ const inputBg = isDarkTheme ? '#23232b' : '#f2f2f7';
44
+ const inputBorder = isDarkTheme ? '#444' : '#e0e0e0';
45
+
46
+ const [expanded, setExpanded] = useState<number | null>(0);
47
+ const [search, setSearch] = useState('');
48
+
49
+ const filteredFaqs = FAQS.filter(faq =>
50
+ faq.q.toLowerCase().includes(search.toLowerCase()) ||
51
+ faq.a.toLowerCase().includes(search.toLowerCase())
52
+ );
53
+
54
+ const handleToggle = (idx: number) => {
55
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
56
+ setExpanded(expanded === idx ? null : idx);
57
+ };
58
+
59
+ return (
60
+ <View style={[styles.container, { backgroundColor }]}>
61
+ <Text style={[styles.title, { color: textColor }]}>Karma FAQ</Text>
62
+ <View style={[styles.searchBar, { backgroundColor: inputBg, borderColor: inputBorder }]}>
63
+ <Ionicons name="search-outline" size={20} color={primaryColor} style={{ marginRight: 8 }} />
64
+ <TextInput
65
+ style={[styles.searchInput, { color: textColor }]}
66
+ placeholder="Search FAQ..."
67
+ placeholderTextColor={isDarkTheme ? '#aaa' : '#888'}
68
+ value={search}
69
+ onChangeText={setSearch}
70
+ returnKeyType="search"
71
+ />
72
+ </View>
73
+ <ScrollView contentContainerStyle={styles.contentContainer} keyboardShouldPersistTaps="handled">
74
+ {filteredFaqs.length === 0 ? (
75
+ <Text style={[styles.noResults, { color: textColor }]}>No results found.</Text>
76
+ ) : (
77
+ filteredFaqs.map((item, idx) => {
78
+ const isOpen = expanded === idx;
79
+ return (
80
+ <TouchableOpacity
81
+ key={idx}
82
+ style={[styles.card, { backgroundColor: cardColor, shadowColor: isDarkTheme ? '#000' : '#d169e5' }]}
83
+ activeOpacity={0.95}
84
+ onPress={() => handleToggle(idx)}
85
+ >
86
+ <View style={styles.questionRow}>
87
+ <Ionicons name={isOpen ? 'chevron-down' : 'chevron-forward'} size={22} color={primaryColor} style={{ marginRight: 8 }} />
88
+ <Text style={[styles.question, { color: primaryColor }]}>{item.q}</Text>
89
+ </View>
90
+ {isOpen && (
91
+ <Text style={[styles.answer, { color: textColor }]}>{item.a}</Text>
92
+ )}
93
+ </TouchableOpacity>
94
+ );
95
+ })
96
+ )}
97
+ <Text style={[styles.paragraph, { color: textColor, marginTop: 32, textAlign: 'center' }]}>Still have questions? Contact support!</Text>
98
+ </ScrollView>
99
+ </View>
100
+ );
101
+ };
102
+
103
+ const styles = StyleSheet.create({
104
+ container: { flex: 1 },
105
+ title: {
106
+ fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
107
+ fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
108
+ fontSize: 38,
109
+ margin: 24,
110
+ marginBottom: 12,
111
+ textAlign: 'center',
112
+ },
113
+ searchBar: {
114
+ flexDirection: 'row',
115
+ alignItems: 'center',
116
+ borderRadius: 16,
117
+ borderWidth: 1,
118
+ marginHorizontal: 24,
119
+ marginBottom: 12,
120
+ paddingHorizontal: 12,
121
+ height: 44,
122
+ },
123
+ searchInput: {
124
+ flex: 1,
125
+ fontSize: 16,
126
+ height: 44,
127
+ },
128
+ contentContainer: { padding: 24, paddingBottom: 40 },
129
+ card: {
130
+ borderRadius: 18,
131
+ padding: 20,
132
+ marginBottom: 18,
133
+ shadowOpacity: 0.08,
134
+ shadowOffset: { width: 0, height: 2 },
135
+ shadowRadius: 8,
136
+ elevation: 2,
137
+ },
138
+ questionRow: {
139
+ flexDirection: 'row',
140
+ alignItems: 'center',
141
+ marginBottom: 8,
142
+ },
143
+ question: {
144
+ fontSize: 17,
145
+ fontWeight: 'bold',
146
+ },
147
+ answer: {
148
+ fontSize: 16,
149
+ lineHeight: 22,
150
+ marginTop: 8,
151
+ },
152
+ paragraph: {
153
+ fontSize: 16,
154
+ marginBottom: 12,
155
+ },
156
+ noResults: {
157
+ fontSize: 16,
158
+ marginTop: 32,
159
+ textAlign: 'center',
160
+ opacity: 0.7,
161
+ },
162
+ });
163
+
164
+ export default KarmaFAQScreen;
@@ -0,0 +1,79 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, ActivityIndicator, TouchableOpacity } from 'react-native';
3
+ import { BaseScreenProps } from '../../navigation/types';
4
+ import { useOxy } from '../../context/OxyContext';
5
+ import Avatar from '../../components/Avatar';
6
+
7
+ const KarmaLeaderboardScreen: React.FC<BaseScreenProps> = ({ goBack, theme, navigate }) => {
8
+ const { oxyServices } = useOxy();
9
+ const [leaderboard, setLeaderboard] = useState<any[]>([]);
10
+ const [isLoading, setIsLoading] = useState(true);
11
+ const [error, setError] = useState<string | null>(null);
12
+
13
+ const isDarkTheme = theme === 'dark';
14
+ const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
15
+ const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
16
+ const primaryColor = '#d169e5';
17
+
18
+ useEffect(() => {
19
+ setIsLoading(true);
20
+ setError(null);
21
+ oxyServices.getKarmaLeaderboard()
22
+ .then((data) => setLeaderboard(Array.isArray(data) ? data : []))
23
+ .catch((err) => setError(err.message || 'Failed to load leaderboard'))
24
+ .finally(() => setIsLoading(false));
25
+ }, [oxyServices]);
26
+
27
+ return (
28
+ <View style={[styles.container, { backgroundColor }]}>
29
+ <Text style={[styles.title, { color: textColor }]}>Karma Leaderboard</Text>
30
+ {isLoading ? (
31
+ <ActivityIndicator size="large" color={primaryColor} style={{ marginTop: 40 }} />
32
+ ) : error ? (
33
+ <Text style={[styles.error, { color: '#D32F2F' }]}>{error}</Text>
34
+ ) : (
35
+ <ScrollView contentContainerStyle={styles.listContainer}>
36
+ {leaderboard.length === 0 ? (
37
+ <Text style={[styles.placeholder, { color: textColor }]}>No leaderboard data.</Text>
38
+ ) : (
39
+ leaderboard.map((entry, idx) => (
40
+ <TouchableOpacity
41
+ key={entry.userId}
42
+ style={[styles.row, idx < 3 && { backgroundColor: '#f7eaff' }]}
43
+ onPress={() => navigate && navigate('KarmaProfile', { userId: entry.userId, username: entry.username })}
44
+ activeOpacity={0.7}
45
+ >
46
+ <Text style={[styles.rank, { color: primaryColor }]}>{idx + 1}</Text>
47
+ <Avatar name={entry.username || 'User'} size={40} theme={theme} style={styles.avatar} />
48
+ <Text style={[styles.username, { color: textColor }]}>{entry.username || entry.userId}</Text>
49
+ <Text style={[styles.karma, { color: primaryColor }]}>{entry.total}</Text>
50
+ </TouchableOpacity>
51
+ ))
52
+ )}
53
+ </ScrollView>
54
+ )}
55
+ </View>
56
+ );
57
+ };
58
+
59
+ const styles = StyleSheet.create({
60
+ container: { flex: 1 },
61
+ title: { fontSize: 24, fontWeight: 'bold', margin: 24, textAlign: 'center' },
62
+ listContainer: { paddingBottom: 40 },
63
+ row: {
64
+ flexDirection: 'row',
65
+ alignItems: 'center',
66
+ paddingVertical: 12,
67
+ paddingHorizontal: 20,
68
+ borderBottomWidth: 1,
69
+ borderColor: '#eee',
70
+ },
71
+ rank: { fontSize: 20, width: 32, textAlign: 'center', fontWeight: 'bold' },
72
+ avatar: { marginHorizontal: 8 },
73
+ username: { flex: 1, fontSize: 16, marginLeft: 8 },
74
+ karma: { fontSize: 18, fontWeight: 'bold', marginLeft: 12 },
75
+ placeholder: { fontSize: 16, color: '#888', textAlign: 'center', marginTop: 40 },
76
+ error: { fontSize: 16, textAlign: 'center', marginTop: 40 },
77
+ });
78
+
79
+ export default KarmaLeaderboardScreen;
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, ScrollView } from 'react-native';
3
+ import { BaseScreenProps } from '../../navigation/types';
4
+
5
+ const KarmaRewardsScreen: React.FC<BaseScreenProps> = ({ goBack, theme }) => {
6
+ const isDarkTheme = theme === 'dark';
7
+ const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
8
+ const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
9
+ const primaryColor = '#d169e5';
10
+
11
+ // Placeholder: In a real app, fetch rewards from API
12
+ return (
13
+ <View style={[styles.container, { backgroundColor }]}>
14
+ <Text style={[styles.title, { color: textColor }]}>Karma Rewards</Text>
15
+ <ScrollView contentContainerStyle={styles.contentContainer}>
16
+ <Text style={[styles.paragraph, { color: textColor }]}>Unlock special features and recognition by earning karma!</Text>
17
+ <View style={styles.rewardBox}>
18
+ <Text style={[styles.rewardTitle, { color: primaryColor }]}>🎉 Early Access</Text>
19
+ <Text style={[styles.rewardDesc, { color: textColor }]}>Get early access to new features with 100+ karma.</Text>
20
+ </View>
21
+ <View style={styles.rewardBox}>
22
+ <Text style={[styles.rewardTitle, { color: primaryColor }]}>🏅 Community Badge</Text>
23
+ <Text style={[styles.rewardDesc, { color: textColor }]}>Earn a special badge for 500+ karma.</Text>
24
+ </View>
25
+ <View style={styles.rewardBox}>
26
+ <Text style={[styles.rewardTitle, { color: primaryColor }]}>🌟 Featured Member</Text>
27
+ <Text style={[styles.rewardDesc, { color: textColor }]}>Be featured in the community for 1000+ karma.</Text>
28
+ </View>
29
+ <Text style={[styles.paragraph, { color: textColor, marginTop: 24 }]}>More rewards coming soon!</Text>
30
+ </ScrollView>
31
+ </View>
32
+ );
33
+ };
34
+
35
+ const styles = StyleSheet.create({
36
+ container: { flex: 1 },
37
+ title: { fontSize: 24, fontWeight: 'bold', margin: 24, textAlign: 'center' },
38
+ contentContainer: { padding: 24 },
39
+ rewardBox: {
40
+ backgroundColor: '#f7eaff',
41
+ borderRadius: 16,
42
+ padding: 18,
43
+ marginBottom: 18,
44
+ shadowColor: '#000',
45
+ shadowOpacity: 0.04,
46
+ shadowOffset: { width: 0, height: 1 },
47
+ shadowRadius: 4,
48
+ elevation: 1,
49
+ },
50
+ rewardTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 6 },
51
+ rewardDesc: { fontSize: 15 },
52
+ paragraph: { fontSize: 16, marginBottom: 12 },
53
+ });
54
+
55
+ export default KarmaRewardsScreen;
@@ -0,0 +1,65 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, ActivityIndicator } from 'react-native';
3
+ import { BaseScreenProps } from '../../navigation/types';
4
+ import { useOxy } from '../../context/OxyContext';
5
+
6
+ const KarmaRulesScreen: React.FC<BaseScreenProps> = ({ goBack, theme }) => {
7
+ const { oxyServices } = useOxy();
8
+ const [rules, setRules] = useState<any[]>([]);
9
+ const [isLoading, setIsLoading] = useState(true);
10
+ const [error, setError] = useState<string | null>(null);
11
+
12
+ const isDarkTheme = theme === 'dark';
13
+ const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
14
+ const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
15
+ const primaryColor = '#d169e5';
16
+
17
+ useEffect(() => {
18
+ setIsLoading(true);
19
+ setError(null);
20
+ oxyServices.getKarmaRules()
21
+ .then((data) => setRules(Array.isArray(data) ? data : []))
22
+ .catch((err) => setError(err.message || 'Failed to load rules'))
23
+ .finally(() => setIsLoading(false));
24
+ }, [oxyServices]);
25
+
26
+ return (
27
+ <View style={[styles.container, { backgroundColor }]}>
28
+ <Text style={[styles.title, { color: textColor }]}>Karma Rules</Text>
29
+ {isLoading ? (
30
+ <ActivityIndicator size="large" color={primaryColor} style={{ marginTop: 40 }} />
31
+ ) : error ? (
32
+ <Text style={[styles.error, { color: '#D32F2F' }]}>{error}</Text>
33
+ ) : (
34
+ <ScrollView contentContainerStyle={styles.listContainer}>
35
+ {rules.length === 0 ? (
36
+ <Text style={[styles.placeholder, { color: textColor }]}>No rules found.</Text>
37
+ ) : (
38
+ rules.map((rule, idx) => (
39
+ <View key={rule.id || idx} style={styles.ruleRow}>
40
+ <Text style={[styles.ruleDesc, { color: textColor }]}>{rule.description}</Text>
41
+ </View>
42
+ ))
43
+ )}
44
+ </ScrollView>
45
+ )}
46
+ </View>
47
+ );
48
+ };
49
+
50
+ const styles = StyleSheet.create({
51
+ container: { flex: 1 },
52
+ title: { fontSize: 24, fontWeight: 'bold', margin: 24, textAlign: 'center' },
53
+ listContainer: { paddingBottom: 40 },
54
+ ruleRow: {
55
+ paddingVertical: 14,
56
+ paddingHorizontal: 24,
57
+ borderBottomWidth: 1,
58
+ borderColor: '#eee',
59
+ },
60
+ ruleDesc: { fontSize: 16 },
61
+ placeholder: { fontSize: 16, color: '#888', textAlign: 'center', marginTop: 40 },
62
+ error: { fontSize: 16, textAlign: 'center', marginTop: 40 },
63
+ });
64
+
65
+ export default KarmaRulesScreen;
@@ -100,8 +100,7 @@ export const createCommonStyles = (theme: 'light' | 'dark') => {
100
100
  backgroundColor: colors.background,
101
101
  },
102
102
  scrollContainer: {
103
- flexGrow: 1,
104
- padding: 10,
103
+ padding: 26,
105
104
  },
106
105
  input: {
107
106
  height: 48,