@oxyhq/services 5.4.1 → 5.4.3

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 (154) hide show
  1. package/lib/commonjs/assets/icons/OxyServices.js +1 -1
  2. package/lib/commonjs/core/index.js +84 -2
  3. package/lib/commonjs/core/index.js.map +1 -1
  4. package/lib/commonjs/index.js +22 -22
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/node/index.js +6 -6
  7. package/lib/commonjs/node/index.js.map +1 -1
  8. package/lib/commonjs/ui/components/Avatar.js +3 -3
  9. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  10. package/lib/commonjs/ui/components/FollowButton.js +3 -3
  11. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  12. package/lib/commonjs/ui/components/OxyLogo.js +1 -1
  13. package/lib/commonjs/ui/components/OxyProvider.js +13 -13
  14. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  15. package/lib/commonjs/ui/components/OxySignInButton.js +2 -2
  16. package/lib/commonjs/ui/components/ProfileCard.js +2 -2
  17. package/lib/commonjs/ui/components/Section.js +1 -1
  18. package/lib/commonjs/ui/components/SectionTitle.js +1 -1
  19. package/lib/commonjs/ui/components/icon/index.js +1 -1
  20. package/lib/commonjs/ui/components/index.js +12 -12
  21. package/lib/commonjs/ui/context/OxyContext.js +20 -4
  22. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  23. package/lib/commonjs/ui/index.js +11 -11
  24. package/lib/commonjs/ui/index.js.map +1 -1
  25. package/lib/commonjs/ui/navigation/OxyRouter.js +18 -18
  26. package/lib/commonjs/ui/screens/AccountCenterScreen.js +18 -18
  27. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  28. package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
  29. package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
  30. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +45 -27
  31. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  32. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +29 -22
  33. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  34. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +3 -3
  35. package/lib/commonjs/ui/screens/AppInfoScreen.js +6 -6
  36. package/lib/commonjs/ui/screens/BillingManagementScreen.js +3 -3
  37. package/lib/commonjs/ui/screens/FileManagementScreen.js +324 -306
  38. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +3 -3
  40. package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
  41. package/lib/commonjs/ui/screens/SessionManagementScreen.js +2 -2
  42. package/lib/commonjs/ui/screens/SignInScreen.js +358 -310
  43. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  44. package/lib/commonjs/ui/screens/SignUpScreen.js +483 -308
  45. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  46. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +3 -3
  47. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +51 -26
  48. package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
  50. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
  51. package/lib/commonjs/ui/styles/index.js +2 -2
  52. package/lib/commonjs/ui/styles/theme.js +1 -1
  53. package/lib/commonjs/utils/index.js +1 -1
  54. package/lib/module/assets/icons/OxyServices.js +1 -1
  55. package/lib/module/assets/icons/OxyServices.js.map +1 -1
  56. package/lib/module/core/index.js +84 -2
  57. package/lib/module/core/index.js.map +1 -1
  58. package/lib/module/index.js +10 -10
  59. package/lib/module/index.js.map +1 -1
  60. package/lib/module/node/index.js +4 -4
  61. package/lib/module/node/index.js.map +1 -1
  62. package/lib/module/ui/components/Avatar.js +2 -2
  63. package/lib/module/ui/components/Avatar.js.map +1 -1
  64. package/lib/module/ui/components/FollowButton.js +3 -3
  65. package/lib/module/ui/components/FollowButton.js.map +1 -1
  66. package/lib/module/ui/components/GroupedSection.js +1 -1
  67. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  68. package/lib/module/ui/components/OxyLogo.js +1 -1
  69. package/lib/module/ui/components/OxyLogo.js.map +1 -1
  70. package/lib/module/ui/components/OxyProvider.js +10 -10
  71. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  72. package/lib/module/ui/components/OxySignInButton.js +2 -2
  73. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  74. package/lib/module/ui/components/ProfileCard.js +2 -2
  75. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  76. package/lib/module/ui/components/Section.js +1 -1
  77. package/lib/module/ui/components/Section.js.map +1 -1
  78. package/lib/module/ui/components/SectionTitle.js +1 -1
  79. package/lib/module/ui/components/SectionTitle.js.map +1 -1
  80. package/lib/module/ui/components/icon/index.js +1 -1
  81. package/lib/module/ui/components/icon/index.js.map +1 -1
  82. package/lib/module/ui/components/index.js +12 -12
  83. package/lib/module/ui/components/index.js.map +1 -1
  84. package/lib/module/ui/context/OxyContext.js +20 -4
  85. package/lib/module/ui/context/OxyContext.js.map +1 -1
  86. package/lib/module/ui/index.js +10 -10
  87. package/lib/module/ui/index.js.map +1 -1
  88. package/lib/module/ui/navigation/OxyRouter.js +18 -18
  89. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  90. package/lib/module/ui/screens/AccountCenterScreen.js +5 -5
  91. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  92. package/lib/module/ui/screens/AccountManagementDemo.js +2 -2
  93. package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
  94. package/lib/module/ui/screens/AccountOverviewScreen.js +46 -28
  95. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  96. package/lib/module/ui/screens/AccountSettingsScreen.js +30 -23
  97. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  98. package/lib/module/ui/screens/AccountSwitcherScreen.js +3 -3
  99. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  100. package/lib/module/ui/screens/AppInfoScreen.js +6 -6
  101. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  102. package/lib/module/ui/screens/BillingManagementScreen.js +3 -3
  103. package/lib/module/ui/screens/BillingManagementScreen.js.map +1 -1
  104. package/lib/module/ui/screens/FileManagementScreen.js +325 -307
  105. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  106. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +3 -3
  107. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  108. package/lib/module/ui/screens/ProfileScreen.js +2 -2
  109. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  110. package/lib/module/ui/screens/SessionManagementScreen.js +2 -2
  111. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  112. package/lib/module/ui/screens/SignInScreen.js +358 -310
  113. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  114. package/lib/module/ui/screens/SignUpScreen.js +486 -309
  115. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  116. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +3 -3
  117. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  118. package/lib/module/ui/screens/karma/KarmaFAQScreen.js +52 -27
  119. package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
  120. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
  121. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  122. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
  123. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  124. package/lib/module/ui/styles/index.js +2 -2
  125. package/lib/module/ui/styles/index.js.map +1 -1
  126. package/lib/module/ui/styles/theme.js +1 -1
  127. package/lib/module/ui/styles/theme.js.map +1 -1
  128. package/lib/module/utils/index.js +1 -1
  129. package/lib/module/utils/index.js.map +1 -1
  130. package/lib/typescript/core/index.d.ts +24 -0
  131. package/lib/typescript/core/index.d.ts.map +1 -1
  132. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  133. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  134. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts +2 -2
  135. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  136. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts +2 -2
  137. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  138. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  139. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  140. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  141. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts +2 -2
  142. package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
  143. package/package.json +21 -5
  144. package/src/core/index.ts +68 -0
  145. package/src/ui/components/OxyProvider.tsx +5 -5
  146. package/src/ui/context/OxyContext.tsx +61 -41
  147. package/src/ui/screens/AccountOverviewScreen.tsx +44 -26
  148. package/src/ui/screens/AccountSettingsScreen.tsx +24 -18
  149. package/src/ui/screens/FileManagementScreen.tsx +246 -211
  150. package/src/ui/screens/SignInScreen.tsx +382 -326
  151. package/src/ui/screens/SignUpScreen.tsx +443 -273
  152. package/src/ui/screens/karma/KarmaFAQScreen.tsx +50 -29
  153. package/lib/commonjs/package.json +0 -1
  154. package/lib/module/package.json +0 -1
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect } from 'react';
1
+ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -34,10 +34,17 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
34
34
  const [password, setPassword] = useState('');
35
35
  const [errorMessage, setErrorMessage] = useState('');
36
36
  const [userProfile, setUserProfile] = useState<any>(null);
37
+ const [showPassword, setShowPassword] = useState(false);
37
38
 
38
39
  // Multi-step form states
39
40
  const [currentStep, setCurrentStep] = useState(0);
40
41
  const [isInputFocused, setIsInputFocused] = useState(false);
42
+ const [isValidating, setIsValidating] = useState(false);
43
+ const [validationStatus, setValidationStatus] = useState<'idle' | 'validating' | 'valid' | 'invalid'>('idle');
44
+
45
+ // Cache for validation results to prevent repeated API calls
46
+ const validationCache = useRef<Map<string, { profile: any; timestamp: number }>>(new Map());
47
+
41
48
  const fadeAnim = useRef(new Animated.Value(1)).current;
42
49
  const slideAnim = useRef(new Animated.Value(0)).current;
43
50
  const scaleAnim = useRef(new Animated.Value(1)).current;
@@ -45,13 +52,19 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
45
52
  const logoAnim = useRef(new Animated.Value(0)).current;
46
53
  const progressAnim = useRef(new Animated.Value(0.5)).current;
47
54
 
48
- const { login, isLoading, user, isAuthenticated, sessions } = useOxy();
55
+ const { login, isLoading, user, isAuthenticated, sessions, oxyServices } = useOxy();
49
56
 
50
57
  const colors = useThemeColors(theme);
51
58
  const commonStyles = createCommonStyles(theme);
52
59
 
53
60
  // Check if this should be treated as "Add Account" mode
54
- const isAddAccountMode = user && isAuthenticated && sessions && sessions.length > 0;
61
+ const isAddAccountMode = useMemo(() =>
62
+ user && isAuthenticated && sessions && sessions.length > 0,
63
+ [user, isAuthenticated, sessions]
64
+ );
65
+
66
+ // Memoized styles to prevent rerenders
67
+ const styles = useMemo(() => createStyles(colors, theme), [colors, theme]);
55
68
 
56
69
  // Initialize logo animation
57
70
  useEffect(() => {
@@ -61,27 +74,161 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
61
74
  friction: 8,
62
75
  useNativeDriver: true,
63
76
  }).start();
64
- }, []);
77
+ }, [logoAnim]);
65
78
 
66
79
  // Input focus animations
67
- const handleInputFocus = () => {
80
+ const handleInputFocus = useCallback(() => {
68
81
  setIsInputFocused(true);
69
82
  Animated.spring(inputScaleAnim, {
70
83
  toValue: 1.02,
71
84
  useNativeDriver: true,
72
85
  }).start();
73
- };
86
+ }, [inputScaleAnim]);
74
87
 
75
- const handleInputBlur = () => {
88
+ const handleInputBlur = useCallback(() => {
76
89
  setIsInputFocused(false);
77
90
  Animated.spring(inputScaleAnim, {
78
91
  toValue: 1,
79
92
  useNativeDriver: true,
80
93
  }).start();
81
- };
94
+ }, [inputScaleAnim]);
95
+
96
+ // Memoized input change handlers to prevent re-renders
97
+ const handleUsernameChange = useCallback((text: string) => {
98
+ setUsername(text);
99
+ // Only clear error if we're changing from an invalid state
100
+ if (validationStatus === 'invalid') {
101
+ setErrorMessage('');
102
+ setValidationStatus('idle');
103
+ }
104
+ }, [validationStatus]);
105
+
106
+ const handlePasswordChange = useCallback((text: string) => {
107
+ setPassword(text);
108
+ setErrorMessage(''); // Clear error when user types
109
+ }, []);
110
+
111
+ // Username validation using core services with caching
112
+ const validateUsername = useCallback(async (usernameToValidate: string) => {
113
+ if (!usernameToValidate || usernameToValidate.length < 3) {
114
+ setValidationStatus('invalid');
115
+ return false;
116
+ }
117
+
118
+ // Check cache first (cache valid for 5 minutes)
119
+ const cached = validationCache.current.get(usernameToValidate);
120
+ const now = Date.now();
121
+ if (cached && (now - cached.timestamp) < 5 * 60 * 1000) {
122
+ setUserProfile(cached.profile);
123
+ setValidationStatus('valid');
124
+ setErrorMessage('');
125
+ return true;
126
+ }
127
+
128
+ setIsValidating(true);
129
+ setValidationStatus('validating');
130
+
131
+ try {
132
+ // First check if username exists by trying to get profile
133
+ const profile = await oxyServices.getUserProfileByUsername(usernameToValidate);
134
+
135
+ if (profile) {
136
+ const profileData = {
137
+ displayName: profile.name?.full || profile.name?.first || profile.username,
138
+ name: profile.username,
139
+ avatar: profile.avatar,
140
+ id: profile.id
141
+ };
142
+
143
+ setUserProfile(profileData);
144
+ setValidationStatus('valid');
145
+ setErrorMessage(''); // Clear any previous errors
146
+
147
+ // Cache the result
148
+ validationCache.current.set(usernameToValidate, {
149
+ profile: profileData,
150
+ timestamp: now
151
+ });
152
+
153
+ return true;
154
+ } else {
155
+ setValidationStatus('invalid');
156
+ setErrorMessage('Username not found. Please check your username or sign up.');
157
+ return false;
158
+ }
159
+ } catch (error: any) {
160
+ // If user not found (404), username doesn't exist
161
+ if (error.status === 404 || error.code === 'USER_NOT_FOUND') {
162
+ setValidationStatus('invalid');
163
+ setErrorMessage('Username not found. Please check your username or sign up.');
164
+ return false;
165
+ }
166
+
167
+ // For other errors, show generic message
168
+ console.error('Username validation error:', error);
169
+ setValidationStatus('invalid');
170
+ setErrorMessage('Unable to validate username. Please try again.');
171
+ return false;
172
+ } finally {
173
+ setIsValidating(false);
174
+ }
175
+ }, [oxyServices]);
176
+
177
+ // Debounced username validation - increased debounce time and added better conditions
178
+ useEffect(() => {
179
+ if (!username || username.length < 3) {
180
+ setValidationStatus('idle');
181
+ setUserProfile(null);
182
+ setErrorMessage(''); // Clear error when input is too short
183
+ return;
184
+ }
185
+
186
+ // Only validate if we haven't already validated this exact username
187
+ if (validationStatus === 'valid' && userProfile?.name === username) {
188
+ return;
189
+ }
190
+
191
+ const timeoutId = setTimeout(() => {
192
+ validateUsername(username);
193
+ }, 800); // Increased debounce to 800ms
194
+
195
+ return () => clearTimeout(timeoutId);
196
+ }, [username, validateUsername, validationStatus, userProfile?.name]);
197
+
198
+ // Cleanup cache on unmount and limit cache size
199
+ useEffect(() => {
200
+ return () => {
201
+ // Clear cache on unmount
202
+ validationCache.current.clear();
203
+ };
204
+ }, []);
205
+
206
+ // Clean up old cache entries periodically (older than 10 minutes)
207
+ useEffect(() => {
208
+ const cleanupInterval = setInterval(() => {
209
+ const now = Date.now();
210
+ const maxAge = 10 * 60 * 1000; // 10 minutes
211
+
212
+ for (const [key, value] of validationCache.current.entries()) {
213
+ if (now - value.timestamp > maxAge) {
214
+ validationCache.current.delete(key);
215
+ }
216
+ }
217
+
218
+ // Limit cache size to 50 entries
219
+ if (validationCache.current.size > 50) {
220
+ const entries = Array.from(validationCache.current.entries());
221
+ entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
222
+ const toDelete = entries.slice(0, entries.length - 50);
223
+ toDelete.forEach(([key]) => validationCache.current.delete(key));
224
+ }
225
+ }, 5 * 60 * 1000); // Clean up every 5 minutes
226
+
227
+ return () => clearInterval(cleanupInterval);
228
+ }, []);
82
229
 
83
230
  // Animation functions
84
- const animateTransition = (nextStep: number) => {
231
+ const animateTransition = useCallback((nextStep: number) => {
85
232
  // Scale down current content
86
233
  Animated.timing(scaleAnim, {
87
234
  toValue: 0.95,
@@ -122,9 +269,9 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
122
269
  })
123
270
  ]).start();
124
271
  });
125
- };
272
+ }, [fadeAnim, slideAnim, scaleAnim]);
126
273
 
127
- const nextStep = () => {
274
+ const nextStep = useCallback(() => {
128
275
  if (currentStep < 1) {
129
276
  // Animate progress bar
130
277
  Animated.timing(progressAnim, {
@@ -132,12 +279,12 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
132
279
  duration: 300,
133
280
  useNativeDriver: false,
134
281
  }).start();
135
-
282
+
136
283
  animateTransition(currentStep + 1);
137
284
  }
138
- };
285
+ }, [currentStep, progressAnim, animateTransition]);
139
286
 
140
- const prevStep = () => {
287
+ const prevStep = useCallback(() => {
141
288
  if (currentStep > 0) {
142
289
  // Animate progress bar
143
290
  Animated.timing(progressAnim, {
@@ -145,43 +292,39 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
145
292
  duration: 300,
146
293
  useNativeDriver: false,
147
294
  }).start();
148
-
295
+
149
296
  animateTransition(currentStep - 1);
150
297
  }
151
- };
152
-
153
- // Fetch user profile when username is entered
154
- useEffect(() => {
155
- const fetchUserProfile = async () => {
156
- if (username.length >= 3 && currentStep === 1) {
157
- try {
158
- // For now, we'll create a mock profile based on username
159
- // In a real app, you'd fetch this from your API
160
- setUserProfile({
161
- displayName: username,
162
- name: username,
163
- avatar: null, // Could be fetched from API
164
- });
165
- } catch (error) {
166
- // If user not found, we'll show a generic avatar
167
- setUserProfile(null);
168
- }
169
- }
170
- };
298
+ }, [currentStep, progressAnim, animateTransition]);
171
299
 
172
- fetchUserProfile();
173
- }, [username, currentStep]);
174
-
175
- const handleUsernameNext = () => {
300
+ const handleUsernameNext = useCallback(() => {
176
301
  if (!username) {
177
302
  toast.error('Please enter your username');
178
303
  return;
179
304
  }
180
- setErrorMessage('');
181
- nextStep();
182
- };
183
305
 
184
- const handleLogin = async () => {
306
+ if (validationStatus === 'invalid') {
307
+ // Don't show toast if we already have an error message displayed
308
+ if (!errorMessage) {
309
+ toast.error('Please enter a valid username');
310
+ }
311
+ return;
312
+ }
313
+
314
+ if (validationStatus === 'validating') {
315
+ toast.error('Please wait while we validate your username');
316
+ return;
317
+ }
318
+
319
+ if (validationStatus === 'valid' && userProfile) {
320
+ setErrorMessage('');
321
+ nextStep();
322
+ } else {
323
+ toast.error('Please enter a valid username');
324
+ }
325
+ }, [username, validationStatus, userProfile, errorMessage, nextStep]);
326
+
327
+ const handleLogin = useCallback(async () => {
185
328
  if (!username || !password) {
186
329
  toast.error('Please enter both username and password');
187
330
  return;
@@ -194,18 +337,18 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
194
337
  } catch (error: any) {
195
338
  toast.error(error.message || 'Login failed');
196
339
  }
197
- };
340
+ }, [username, password, login]);
198
341
 
199
- // Step components
200
- const renderUsernameStep = () => (
342
+ // Memoized step components
343
+ const renderUsernameStep = useMemo(() => (
201
344
  <Animated.View style={[
202
345
  styles.stepContainer,
203
- {
204
- opacity: fadeAnim,
346
+ {
347
+ opacity: fadeAnim,
205
348
  transform: [
206
349
  { translateX: slideAnim },
207
350
  { scale: scaleAnim }
208
- ]
351
+ ]
209
352
  }
210
353
  ]}>
211
354
  <View style={styles.modernImageContainer}>
@@ -220,7 +363,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
220
363
  <Stop offset="100%" stopColor={colors.primary} stopOpacity="0.3" />
221
364
  </LinearGradient>
222
365
  </Defs>
223
-
366
+
224
367
  {/* Modern abstract shapes */}
225
368
  <Circle cx="80" cy="80" r="45" fill="url(#primaryGradient)" />
226
369
  <Circle cx="200" cy="80" r="35" fill="url(#secondaryGradient)" />
@@ -231,12 +374,12 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
231
374
  fill="none"
232
375
  strokeLinecap="round"
233
376
  />
234
-
377
+
235
378
  {/* Floating elements */}
236
379
  <Circle cx="60" cy="50" r="8" fill={colors.primary} opacity="0.6" />
237
380
  <Circle cx="220" cy="120" r="6" fill={colors.primary} opacity="0.4" />
238
381
  <Circle cx="250" cy="40" r="4" fill={colors.primary} opacity="0.8" />
239
-
382
+
240
383
  {/* Central focus element */}
241
384
  <Circle cx="140" cy="80" r="25" fill={colors.background} opacity="0.9" />
242
385
  <Circle cx="135" cy="75" r="3" fill={colors.primary} />
@@ -256,7 +399,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
256
399
  {isAddAccountMode ? 'Add Account' : 'Welcome Back'}
257
400
  </Text>
258
401
  <Text style={[styles.modernSubtitle, { color: colors.secondaryText }]}>
259
- {isAddAccountMode
402
+ {isAddAccountMode
260
403
  ? 'Sign in with another account'
261
404
  : 'Sign in to continue your journey'
262
405
  }
@@ -283,10 +426,17 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
283
426
  styles.modernInputContainer,
284
427
  { transform: [{ scale: inputScaleAnim }] }
285
428
  ]}>
286
- <View style={[styles.inputWrapper, { borderColor: isInputFocused ? colors.primary : colors.border }]}>
287
- <Ionicons
288
- name="person-outline"
289
- size={20}
429
+ <View style={[
430
+ styles.inputWrapper,
431
+ {
432
+ borderColor: validationStatus === 'valid' ? colors.success :
433
+ validationStatus === 'invalid' ? colors.error :
434
+ isInputFocused ? colors.primary : colors.border
435
+ }
436
+ ]}>
437
+ <Ionicons
438
+ name="person-outline"
439
+ size={20}
290
440
  color={isInputFocused ? colors.primary : colors.secondaryText}
291
441
  style={styles.inputIcon}
292
442
  />
@@ -295,30 +445,64 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
295
445
  placeholder="Enter your username"
296
446
  placeholderTextColor={colors.placeholder}
297
447
  value={username}
298
- onChangeText={setUsername}
448
+ onChangeText={handleUsernameChange}
299
449
  onFocus={handleInputFocus}
300
450
  onBlur={handleInputBlur}
301
451
  autoCapitalize="none"
302
452
  testID="username-input"
303
453
  />
454
+ {validationStatus === 'validating' && (
455
+ <ActivityIndicator size="small" color={colors.primary} style={styles.validationIndicator} />
456
+ )}
457
+ {validationStatus === 'valid' && (
458
+ <Ionicons name="checkmark-circle" size={20} color={colors.success} style={styles.validationIndicator} />
459
+ )}
460
+ {validationStatus === 'invalid' && username.length >= 3 && (
461
+ <Ionicons name="close-circle" size={20} color={colors.error} style={styles.validationIndicator} />
462
+ )}
304
463
  </View>
464
+
465
+ {/* Validation feedback */}
466
+ {validationStatus === 'valid' && userProfile && (
467
+ <View style={[styles.validationSuccessCard, { backgroundColor: colors.success + '15' }]}>
468
+ <Ionicons name="checkmark-circle" size={16} color={colors.success} />
469
+ <Text style={[styles.validationText, { color: colors.success }]}>
470
+ Found user: {userProfile.displayName}
471
+ </Text>
472
+ </View>
473
+ )}
474
+
475
+ {validationStatus === 'invalid' && username.length >= 3 && !errorMessage && (
476
+ <View style={[styles.validationErrorCard, { backgroundColor: colors.error + '15' }]}>
477
+ <Ionicons name="alert-circle" size={16} color={colors.error} />
478
+ <Text style={[styles.validationText, { color: colors.error }]}>
479
+ Username not found
480
+ </Text>
481
+ </View>
482
+ )}
305
483
  </Animated.View>
306
484
 
307
485
  <TouchableOpacity
308
486
  style={[
309
- styles.modernButton,
310
- {
487
+ styles.modernButton,
488
+ {
311
489
  backgroundColor: colors.primary,
312
- opacity: !username ? 0.5 : 1,
490
+ opacity: (!username || validationStatus !== 'valid') ? 0.5 : 1,
313
491
  shadowColor: colors.primary,
314
492
  }
315
493
  ]}
316
494
  onPress={handleUsernameNext}
317
- disabled={!username}
495
+ disabled={!username || validationStatus !== 'valid' || isValidating}
318
496
  testID="username-next-button"
319
497
  >
320
- <Text style={styles.modernButtonText}>Continue</Text>
321
- <Ionicons name="arrow-forward" size={20} color="#FFFFFF" style={styles.buttonIcon} />
498
+ {isValidating ? (
499
+ <ActivityIndicator color="#FFFFFF" size="small" />
500
+ ) : (
501
+ <>
502
+ <Text style={styles.modernButtonText}>Continue</Text>
503
+ <Ionicons name="arrow-forward" size={20} color="#FFFFFF" style={styles.buttonIcon} />
504
+ </>
505
+ )}
322
506
  </TouchableOpacity>
323
507
 
324
508
  <View style={styles.footerTextContainer}>
@@ -330,17 +514,22 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
330
514
  </TouchableOpacity>
331
515
  </View>
332
516
  </Animated.View>
333
- );
334
-
335
- const renderPasswordStep = () => (
517
+ ), [
518
+ fadeAnim, slideAnim, scaleAnim, colors, isAddAccountMode, user?.username,
519
+ errorMessage, inputScaleAnim, isInputFocused, username, validationStatus,
520
+ userProfile, isValidating, handleInputFocus, handleInputBlur, handleUsernameChange,
521
+ handleUsernameNext, navigate, styles
522
+ ]);
523
+
524
+ const renderPasswordStep = useMemo(() => (
336
525
  <Animated.View style={[
337
526
  styles.stepContainer,
338
- {
339
- opacity: fadeAnim,
527
+ {
528
+ opacity: fadeAnim,
340
529
  transform: [
341
530
  { translateX: slideAnim },
342
531
  { scale: scaleAnim }
343
- ]
532
+ ]
344
533
  }
345
534
  ]}>
346
535
  <View style={styles.modernUserProfileContainer}>
@@ -357,14 +546,14 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
357
546
  />
358
547
  <View style={[styles.statusIndicator, { backgroundColor: colors.primary }]} />
359
548
  </Animated.View>
360
-
549
+
361
550
  <Text style={[styles.modernUserDisplayName, { color: colors.text }]}>
362
551
  {userProfile?.displayName || userProfile?.name || username}
363
552
  </Text>
364
553
  <Text style={[styles.modernUsernameSubtext, { color: colors.secondaryText }]}>
365
554
  @{username}
366
555
  </Text>
367
-
556
+
368
557
  <View style={[styles.welcomeBackBadge, { backgroundColor: colors.primary + '15' }]}>
369
558
  <Ionicons name="checkmark-circle" size={16} color={colors.primary} />
370
559
  <Text style={[styles.welcomeBackText, { color: colors.primary }]}>
@@ -385,9 +574,9 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
385
574
  { transform: [{ scale: inputScaleAnim }] }
386
575
  ]}>
387
576
  <View style={[styles.inputWrapper, { borderColor: isInputFocused ? colors.primary : colors.border }]}>
388
- <Ionicons
389
- name="lock-closed-outline"
390
- size={20}
577
+ <Ionicons
578
+ name="lock-closed-outline"
579
+ size={20}
391
580
  color={isInputFocused ? colors.primary : colors.secondaryText}
392
581
  style={styles.inputIcon}
393
582
  />
@@ -396,36 +585,45 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
396
585
  placeholder="Enter your password"
397
586
  placeholderTextColor={colors.placeholder}
398
587
  value={password}
399
- onChangeText={setPassword}
588
+ onChangeText={handlePasswordChange}
400
589
  onFocus={handleInputFocus}
401
590
  onBlur={handleInputBlur}
402
- secureTextEntry
591
+ secureTextEntry={!showPassword}
592
+ autoCapitalize="none"
403
593
  testID="password-input"
404
594
  />
595
+ <TouchableOpacity
596
+ style={styles.passwordToggle}
597
+ onPress={() => setShowPassword(!showPassword)}
598
+ >
599
+ <Ionicons
600
+ name={showPassword ? "eye-off" : "eye"}
601
+ size={20}
602
+ color={colors.secondaryText}
603
+ />
604
+ </TouchableOpacity>
405
605
  </View>
406
606
  </Animated.View>
407
607
 
408
608
  <TouchableOpacity
409
609
  style={[
410
- styles.modernButton,
411
- {
610
+ styles.modernButton,
611
+ {
412
612
  backgroundColor: colors.primary,
413
- opacity: isLoading ? 0.8 : 1,
613
+ opacity: !password ? 0.5 : 1,
414
614
  shadowColor: colors.primary,
415
615
  }
416
616
  ]}
417
617
  onPress={handleLogin}
418
- disabled={isLoading}
618
+ disabled={!password || isLoading}
419
619
  testID="login-button"
420
620
  >
421
621
  {isLoading ? (
422
622
  <ActivityIndicator color="#FFFFFF" size="small" />
423
623
  ) : (
424
624
  <>
425
- <Text style={styles.modernButtonText}>
426
- {isAddAccountMode ? 'Add Account' : 'Sign In'}
427
- </Text>
428
- <Ionicons name="arrow-forward" size={20} color="#FFFFFF" style={styles.buttonIcon} />
625
+ <Text style={styles.modernButtonText}>Sign In</Text>
626
+ <Ionicons name="log-in" size={20} color="#FFFFFF" style={styles.buttonIcon} />
429
627
  </>
430
628
  )}
431
629
  </TouchableOpacity>
@@ -435,232 +633,136 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
435
633
  style={[styles.modernBackButton, { borderColor: colors.border }]}
436
634
  onPress={prevStep}
437
635
  >
438
- <Ionicons name="chevron-back" size={20} color={colors.text} />
636
+ <Ionicons name="arrow-back" size={18} color={colors.text} />
439
637
  <Text style={[styles.modernBackButtonText, { color: colors.text }]}>Back</Text>
440
638
  </TouchableOpacity>
441
639
  </View>
442
640
 
443
- {/* Security notice */}
444
641
  <View style={styles.securityNotice}>
445
- <Ionicons name="shield-checkmark" size={16} color={colors.secondaryText} />
642
+ <Ionicons name="shield-checkmark" size={14} color={colors.secondaryText} />
446
643
  <Text style={[styles.securityText, { color: colors.secondaryText }]}>
447
- Your connection is secure and encrypted
644
+ Your data is encrypted and secure
448
645
  </Text>
449
646
  </View>
450
647
  </Animated.View>
451
- );
648
+ ), [
649
+ fadeAnim, slideAnim, scaleAnim, colors, userProfile, username, theme, logoAnim,
650
+ errorMessage, inputScaleAnim, isInputFocused, password, showPassword,
651
+ handleInputFocus, handleInputBlur, handlePasswordChange, handleLogin, isLoading, prevStep, styles
652
+ ]);
452
653
 
453
- const renderCurrentStep = () => {
654
+ const renderCurrentStep = useCallback(() => {
454
655
  switch (currentStep) {
455
656
  case 0:
456
- return renderUsernameStep();
657
+ return renderUsernameStep;
457
658
  case 1:
458
- return renderPasswordStep();
659
+ return renderPasswordStep;
459
660
  default:
460
- return renderUsernameStep();
661
+ return renderUsernameStep;
461
662
  }
462
- };
663
+ }, [currentStep, renderUsernameStep, renderPasswordStep]);
463
664
 
464
665
  return (
465
- <BottomSheetScrollView
466
- contentContainerStyle={commonStyles.scrollContainer}
467
- keyboardShouldPersistTaps="handled"
666
+ <KeyboardAvoidingView
667
+ style={[styles.container, { backgroundColor: colors.background }]}
668
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
468
669
  >
469
- <Animated.View style={[
470
- styles.logoContainer,
471
- { transform: [{ scale: logoAnim }] }
472
- ]}>
473
- <OxyLogo
474
- style={{ marginBottom: 24 }}
475
- width={50}
476
- height={50}
477
- />
478
- </Animated.View>
479
-
480
- {/* Modern Progress indicator */}
481
- <View style={styles.modernProgressContainer}>
482
- <View style={styles.progressTrack}>
483
- <Animated.View
484
- style={[
485
- styles.progressFill,
486
- {
487
- backgroundColor: colors.primary,
488
- width: progressAnim.interpolate({
489
- inputRange: [0, 1],
490
- outputRange: ['50%', '100%']
491
- })
492
- }
493
- ]}
494
- />
495
- </View>
496
- <Text style={[styles.progressText, { color: colors.secondaryText }]}>
497
- Step {currentStep + 1} of 2
498
- </Text>
499
- </View>
500
-
501
- {renderCurrentStep()}
502
- </BottomSheetScrollView>
670
+ <StatusBar
671
+ barStyle={theme === 'dark' ? 'light-content' : 'dark-content'}
672
+ backgroundColor={colors.background}
673
+ />
674
+
675
+ <ScrollView
676
+ contentContainerStyle={styles.scrollContent}
677
+ showsVerticalScrollIndicator={false}
678
+ keyboardShouldPersistTaps="handled"
679
+ >
680
+ {renderCurrentStep()}
681
+ </ScrollView>
682
+ </KeyboardAvoidingView>
503
683
  );
504
684
  };
505
685
 
506
- const styles = StyleSheet.create({
507
- // Legacy styles (keeping for compatibility)
508
- title: {
509
- fontFamily: Platform.OS === 'web'
510
- ? 'Phudu'
511
- : 'Phudu-Bold',
512
- fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
513
- fontSize: 54,
514
- marginBottom: 24,
515
- },
516
- formContainer: {
517
- width: '100%',
518
- },
519
- inputContainer: {
520
- marginBottom: 16,
686
+ // Memoized styles creation
687
+ const createStyles = (colors: any, theme: string) => StyleSheet.create({
688
+ container: {
689
+ flex: 1,
521
690
  },
522
- label: {
523
- fontSize: 14,
524
- fontWeight: '500' as TextStyle['fontWeight'],
525
- marginBottom: 8,
691
+ scrollContent: {
692
+ flexGrow: 1,
693
+ paddingHorizontal: 24,
694
+ paddingTop: 40,
695
+ paddingBottom: 40,
526
696
  },
527
- footerTextContainer: {
528
- flexDirection: 'row',
697
+ stepContainer: {
698
+ flex: 1,
529
699
  justifyContent: 'center',
530
- marginTop: 24,
531
- },
532
- footerText: {
533
- fontSize: 14,
534
- lineHeight: 20,
535
- },
536
- linkText: {
537
- fontSize: 14,
538
- lineHeight: 20,
539
- fontWeight: '600',
540
- },
541
- userInfoContainer: {
542
- padding: 20,
543
- marginVertical: 20,
544
- borderRadius: 35,
545
700
  alignItems: 'center',
701
+ minHeight: 600,
546
702
  },
547
- userInfoText: {
548
- fontSize: 16,
549
- lineHeight: 24,
550
- textAlign: 'center',
551
- },
552
- actionButtonsContainer: {
553
- marginTop: 20,
554
- },
555
- infoContainer: {
556
- padding: 16,
557
- marginVertical: 16,
558
- borderRadius: 8,
559
- alignItems: 'center',
560
- },
561
- infoText: {
562
- fontSize: 14,
563
- lineHeight: 20,
564
- textAlign: 'center',
565
- },
566
-
567
- // Modern UI Styles
568
- logoContainer: {
569
- alignItems: 'center',
570
- marginBottom: 16,
571
- },
572
- modernProgressContainer: {
573
- alignItems: 'center',
574
- marginBottom: 40,
575
- paddingHorizontal: 20,
576
- },
577
- progressTrack: {
578
- width: '100%',
579
- height: 4,
580
- backgroundColor: '#E5E5E5',
581
- borderRadius: 2,
582
- marginBottom: 8,
583
- overflow: 'hidden',
584
- },
585
- progressFill: {
586
- height: '100%',
587
- borderRadius: 2,
588
- },
589
- progressText: {
590
- fontSize: 12,
591
- fontWeight: '500',
592
- },
593
- stepContainer: {
594
- width: '100%',
595
- minHeight: 450,
596
- paddingHorizontal: 20,
597
- },
598
-
599
- // Modern Image Container
600
703
  modernImageContainer: {
601
704
  alignItems: 'center',
602
705
  marginBottom: 40,
603
- paddingVertical: 20,
604
706
  },
605
707
  modernHeader: {
606
- alignItems: 'center',
607
- marginBottom: 24,
708
+ alignItems: 'flex-start',
709
+ width: '100%',
710
+ marginBottom: 32,
608
711
  },
609
712
  modernTitle: {
610
713
  fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
611
714
  fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
612
- fontSize: 54,
613
- textAlign: 'center',
614
- marginBottom: 8,
615
- letterSpacing: -0.5,
715
+ fontSize: 42,
716
+ lineHeight: 48,
717
+ marginBottom: 12,
718
+ textAlign: 'left',
719
+ letterSpacing: -1,
616
720
  },
617
721
  modernSubtitle: {
618
- fontSize: 16,
619
- lineHeight: 22,
620
- textAlign: 'center',
722
+ fontSize: 18,
723
+ lineHeight: 24,
724
+ textAlign: 'left',
621
725
  opacity: 0.8,
622
726
  },
623
-
624
- // Modern Cards
625
727
  modernInfoCard: {
626
728
  flexDirection: 'row',
627
729
  alignItems: 'center',
628
730
  padding: 16,
629
- marginVertical: 16,
630
- borderRadius: 12,
731
+ borderRadius: 16,
732
+ marginBottom: 24,
631
733
  gap: 12,
734
+ width: '100%',
632
735
  },
633
736
  modernInfoText: {
634
737
  fontSize: 14,
635
- lineHeight: 20,
636
738
  flex: 1,
637
739
  },
638
740
  modernErrorCard: {
639
741
  flexDirection: 'row',
640
742
  alignItems: 'center',
641
743
  padding: 16,
642
- marginVertical: 16,
643
- borderRadius: 12,
744
+ borderRadius: 16,
745
+ marginBottom: 24,
644
746
  gap: 12,
747
+ width: '100%',
645
748
  },
646
749
  errorText: {
647
750
  fontSize: 14,
648
- lineHeight: 20,
751
+ fontWeight: '500',
649
752
  flex: 1,
650
753
  },
651
-
652
- // Modern Input Styles
653
754
  modernInputContainer: {
755
+ width: '100%',
654
756
  marginBottom: 24,
655
757
  },
656
758
  inputWrapper: {
657
759
  flexDirection: 'row',
658
760
  alignItems: 'center',
659
- borderWidth: 2,
761
+ height: 56,
660
762
  borderRadius: 16,
661
- paddingHorizontal: 16,
662
- paddingVertical: 4,
663
- backgroundColor: 'rgba(0,0,0,0.02)',
763
+ paddingHorizontal: 20,
764
+ borderWidth: 2,
765
+ backgroundColor: colors.inputBackground,
664
766
  },
665
767
  inputIcon: {
666
768
  marginRight: 12,
@@ -668,11 +770,34 @@ const styles = StyleSheet.create({
668
770
  modernInput: {
669
771
  flex: 1,
670
772
  fontSize: 16,
671
- paddingVertical: 16,
773
+ height: '100%',
774
+ },
775
+ passwordToggle: {
776
+ padding: 4,
777
+ },
778
+ validationIndicator: {
779
+ marginLeft: 8,
780
+ },
781
+ validationSuccessCard: {
782
+ flexDirection: 'row',
783
+ alignItems: 'center',
784
+ padding: 12,
785
+ borderRadius: 12,
786
+ marginTop: 8,
787
+ gap: 8,
788
+ },
789
+ validationErrorCard: {
790
+ flexDirection: 'row',
791
+ alignItems: 'center',
792
+ padding: 12,
793
+ borderRadius: 12,
794
+ marginTop: 8,
795
+ gap: 8,
796
+ },
797
+ validationText: {
798
+ fontSize: 12,
672
799
  fontWeight: '500',
673
800
  },
674
-
675
- // Modern Button Styles
676
801
  modernButton: {
677
802
  flexDirection: 'row',
678
803
  alignItems: 'center',
@@ -689,6 +814,7 @@ const styles = StyleSheet.create({
689
814
  shadowRadius: 8,
690
815
  elevation: 6,
691
816
  gap: 8,
817
+ width: '100%',
692
818
  },
693
819
  modernButtonText: {
694
820
  color: '#FFFFFF',
@@ -705,6 +831,14 @@ const styles = StyleSheet.create({
705
831
  fontWeight: '600',
706
832
  textDecorationLine: 'underline',
707
833
  },
834
+ footerTextContainer: {
835
+ flexDirection: 'row',
836
+ justifyContent: 'center',
837
+ marginTop: 28,
838
+ },
839
+ footerText: {
840
+ fontSize: 15,
841
+ },
708
842
 
709
843
  // Modern User Profile Styles
710
844
  modernUserProfileContainer: {
@@ -791,84 +925,6 @@ const styles = StyleSheet.create({
791
925
  fontSize: 12,
792
926
  fontWeight: '500',
793
927
  },
794
-
795
- // Legacy compatibility styles
796
- progressContainer: {
797
- flexDirection: 'row',
798
- alignItems: 'center',
799
- justifyContent: 'center',
800
- marginBottom: 32,
801
- paddingHorizontal: 40,
802
- },
803
- progressDot: {
804
- width: 12,
805
- height: 12,
806
- borderRadius: 6,
807
- marginHorizontal: 4,
808
- },
809
- progressLine: {
810
- flex: 1,
811
- height: 2,
812
- marginHorizontal: 8,
813
- },
814
- welcomeImageContainer: {
815
- alignItems: 'center',
816
- marginBottom: 32,
817
- },
818
- header: {
819
- alignItems: 'center',
820
- marginBottom: 16,
821
- },
822
- welcomeTitle: {
823
- fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
824
- fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
825
- fontSize: 32,
826
- textAlign: 'center',
827
- marginBottom: 8,
828
- },
829
- welcomeText: {
830
- fontSize: 16,
831
- lineHeight: 24,
832
- textAlign: 'center',
833
- marginBottom: 32,
834
- paddingHorizontal: 20,
835
- },
836
- userProfileContainer: {
837
- alignItems: 'center',
838
- marginBottom: 32,
839
- paddingVertical: 20,
840
- },
841
- userAvatar: {
842
- marginBottom: 16,
843
- },
844
- userDisplayName: {
845
- fontSize: 24,
846
- fontWeight: 'bold',
847
- marginBottom: 4,
848
- textAlign: 'center',
849
- },
850
- usernameSubtext: {
851
- fontSize: 16,
852
- textAlign: 'center',
853
- },
854
- navigationButtons: {
855
- flexDirection: 'row',
856
- justifyContent: 'center',
857
- marginTop: 24,
858
- },
859
- backButton: {
860
- flexDirection: 'row',
861
- alignItems: 'center',
862
- paddingVertical: 12,
863
- paddingHorizontal: 20,
864
- borderRadius: 12,
865
- borderWidth: 1,
866
- },
867
- backButtonText: {
868
- fontSize: 16,
869
- fontWeight: '500',
870
- marginLeft: 8,
871
- },
872
928
  });
873
929
 
874
930
  export default SignInScreen;