@oxyhq/services 5.3.10 → 5.3.11

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -571
  3. package/lib/commonjs/core/index.js +103 -34
  4. package/lib/commonjs/core/index.js.map +1 -1
  5. package/lib/commonjs/lib/sonner.js +17 -0
  6. package/lib/commonjs/lib/sonner.js.map +1 -0
  7. package/lib/commonjs/lib/sonner.web.js +17 -0
  8. package/lib/commonjs/lib/sonner.web.js.map +1 -0
  9. package/lib/commonjs/ui/components/FollowButton.js +54 -4
  10. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  11. package/lib/commonjs/ui/components/OxyProvider.js +22 -3
  12. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  13. package/lib/commonjs/ui/screens/AccountCenterScreen.js +4 -3
  14. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +15 -14
  16. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  17. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +10 -9
  18. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  19. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +17 -16
  20. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  21. package/lib/commonjs/ui/screens/AppInfoScreen.js +4 -5
  22. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  23. package/lib/commonjs/ui/screens/SessionManagementScreen.js +7 -6
  24. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  25. package/lib/commonjs/ui/screens/SignInScreen.js +821 -74
  26. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  27. package/lib/commonjs/ui/screens/SignUpScreen.js +7 -5
  28. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  29. package/lib/commonjs/ui/styles/theme.js +1 -1
  30. package/lib/module/core/index.js +103 -34
  31. package/lib/module/core/index.js.map +1 -1
  32. package/lib/module/lib/sonner.js +4 -0
  33. package/lib/module/lib/sonner.js.map +1 -0
  34. package/lib/module/lib/sonner.web.js +4 -0
  35. package/lib/module/lib/sonner.web.js.map +1 -0
  36. package/lib/module/ui/components/FollowButton.js +54 -4
  37. package/lib/module/ui/components/FollowButton.js.map +1 -1
  38. package/lib/module/ui/components/OxyProvider.js +22 -3
  39. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  40. package/lib/module/ui/screens/AccountCenterScreen.js +4 -3
  41. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  42. package/lib/module/ui/screens/AccountOverviewScreen.js +15 -14
  43. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  44. package/lib/module/ui/screens/AccountSettingsScreen.js +10 -9
  45. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  46. package/lib/module/ui/screens/AccountSwitcherScreen.js +17 -16
  47. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  48. package/lib/module/ui/screens/AppInfoScreen.js +5 -6
  49. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  50. package/lib/module/ui/screens/SessionManagementScreen.js +7 -6
  51. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  52. package/lib/module/ui/screens/SignInScreen.js +824 -77
  53. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  54. package/lib/module/ui/screens/SignUpScreen.js +7 -5
  55. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  56. package/lib/module/ui/styles/theme.js +1 -1
  57. package/lib/typescript/core/index.d.ts +44 -23
  58. package/lib/typescript/core/index.d.ts.map +1 -1
  59. package/lib/typescript/lib/sonner.d.ts +2 -0
  60. package/lib/typescript/lib/sonner.d.ts.map +1 -0
  61. package/lib/typescript/lib/sonner.web.d.ts +2 -0
  62. package/lib/typescript/lib/sonner.web.d.ts.map +1 -0
  63. package/lib/typescript/models/interfaces.d.ts +24 -0
  64. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  65. package/lib/typescript/ui/components/FollowButton.d.ts +32 -1
  66. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  67. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  68. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  69. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  70. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  71. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  72. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  73. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  74. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  75. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  76. package/package.json +5 -3
  77. package/src/core/index.ts +104 -36
  78. package/src/lib/sonner.ts +1 -0
  79. package/src/lib/sonner.web.ts +1 -0
  80. package/src/models/interfaces.ts +29 -0
  81. package/src/ui/components/FollowButton.tsx +67 -3
  82. package/src/ui/components/OxyProvider.tsx +15 -0
  83. package/src/ui/screens/AccountCenterScreen.tsx +4 -3
  84. package/src/ui/screens/AccountOverviewScreen.tsx +15 -14
  85. package/src/ui/screens/AccountSettingsScreen.tsx +10 -9
  86. package/src/ui/screens/AccountSwitcherScreen.tsx +17 -16
  87. package/src/ui/screens/AppInfoScreen.tsx +4 -7
  88. package/src/ui/screens/SessionManagementScreen.tsx +7 -15
  89. package/src/ui/screens/SignInScreen.tsx +729 -52
  90. package/src/ui/screens/SignUpScreen.tsx +7 -5
  91. package/src/ui/styles/theme.ts +1 -1
  92. package/CHANGELOG.md +0 -97
  93. package/UI_COMPONENTS.md +0 -462
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useRef, useEffect } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -10,20 +10,40 @@ import {
10
10
  KeyboardAvoidingView,
11
11
  ScrollView,
12
12
  TextStyle,
13
+ Animated,
14
+ Dimensions,
15
+ StatusBar,
13
16
  } from 'react-native';
14
17
  import { BaseScreenProps } from '../navigation/types';
15
18
  import { useOxy } from '../context/OxyContext';
16
19
  import { fontFamilies, useThemeColors, createCommonStyles } from '../styles';
17
20
  import OxyLogo from '../components/OxyLogo';
21
+ import Avatar from '../components/Avatar';
18
22
  import { BottomSheetScrollView } from '@gorhom/bottom-sheet';
23
+ import { Ionicons } from '@expo/vector-icons';
24
+ import Svg, { Path, Circle, Defs, LinearGradient, Stop } from 'react-native-svg';
25
+ import { toast } from '../../lib/sonner';
19
26
 
20
27
  const SignInScreen: React.FC<BaseScreenProps> = ({
21
28
  navigate,
29
+ goBack,
22
30
  theme,
23
31
  }) => {
32
+ // Form data states
24
33
  const [username, setUsername] = useState('');
25
34
  const [password, setPassword] = useState('');
26
35
  const [errorMessage, setErrorMessage] = useState('');
36
+ const [userProfile, setUserProfile] = useState<any>(null);
37
+
38
+ // Multi-step form states
39
+ const [currentStep, setCurrentStep] = useState(0);
40
+ const [isInputFocused, setIsInputFocused] = useState(false);
41
+ const fadeAnim = useRef(new Animated.Value(1)).current;
42
+ const slideAnim = useRef(new Animated.Value(0)).current;
43
+ const scaleAnim = useRef(new Animated.Value(1)).current;
44
+ const inputScaleAnim = useRef(new Animated.Value(1)).current;
45
+ const logoAnim = useRef(new Animated.Value(0)).current;
46
+ const progressAnim = useRef(new Animated.Value(0.5)).current;
27
47
 
28
48
  const { login, isLoading, user, isAuthenticated, sessions } = useOxy();
29
49
 
@@ -33,9 +53,137 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
33
53
  // Check if this should be treated as "Add Account" mode
34
54
  const isAddAccountMode = user && isAuthenticated && sessions && sessions.length > 0;
35
55
 
56
+ // Initialize logo animation
57
+ useEffect(() => {
58
+ Animated.spring(logoAnim, {
59
+ toValue: 1,
60
+ tension: 50,
61
+ friction: 8,
62
+ useNativeDriver: true,
63
+ }).start();
64
+ }, []);
65
+
66
+ // Input focus animations
67
+ const handleInputFocus = () => {
68
+ setIsInputFocused(true);
69
+ Animated.spring(inputScaleAnim, {
70
+ toValue: 1.02,
71
+ useNativeDriver: true,
72
+ }).start();
73
+ };
74
+
75
+ const handleInputBlur = () => {
76
+ setIsInputFocused(false);
77
+ Animated.spring(inputScaleAnim, {
78
+ toValue: 1,
79
+ useNativeDriver: true,
80
+ }).start();
81
+ };
82
+
83
+ // Animation functions
84
+ const animateTransition = (nextStep: number) => {
85
+ // Scale down current content
86
+ Animated.timing(scaleAnim, {
87
+ toValue: 0.95,
88
+ duration: 150,
89
+ useNativeDriver: true,
90
+ }).start();
91
+
92
+ // Fade out
93
+ Animated.timing(fadeAnim, {
94
+ toValue: 0,
95
+ duration: 200,
96
+ useNativeDriver: true,
97
+ }).start(() => {
98
+ setCurrentStep(nextStep);
99
+
100
+ // Reset animations
101
+ slideAnim.setValue(-50);
102
+ scaleAnim.setValue(0.95);
103
+
104
+ // Animate in new content
105
+ Animated.parallel([
106
+ Animated.timing(fadeAnim, {
107
+ toValue: 1,
108
+ duration: 300,
109
+ useNativeDriver: true,
110
+ }),
111
+ Animated.spring(slideAnim, {
112
+ toValue: 0,
113
+ tension: 80,
114
+ friction: 8,
115
+ useNativeDriver: true,
116
+ }),
117
+ Animated.spring(scaleAnim, {
118
+ toValue: 1,
119
+ tension: 80,
120
+ friction: 8,
121
+ useNativeDriver: true,
122
+ })
123
+ ]).start();
124
+ });
125
+ };
126
+
127
+ const nextStep = () => {
128
+ if (currentStep < 1) {
129
+ // Animate progress bar
130
+ Animated.timing(progressAnim, {
131
+ toValue: 1.0,
132
+ duration: 300,
133
+ useNativeDriver: false,
134
+ }).start();
135
+
136
+ animateTransition(currentStep + 1);
137
+ }
138
+ };
139
+
140
+ const prevStep = () => {
141
+ if (currentStep > 0) {
142
+ // Animate progress bar
143
+ Animated.timing(progressAnim, {
144
+ toValue: 0.5,
145
+ duration: 300,
146
+ useNativeDriver: false,
147
+ }).start();
148
+
149
+ animateTransition(currentStep - 1);
150
+ }
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
+ };
171
+
172
+ fetchUserProfile();
173
+ }, [username, currentStep]);
174
+
175
+ const handleUsernameNext = () => {
176
+ if (!username) {
177
+ toast.error('Please enter your username');
178
+ return;
179
+ }
180
+ setErrorMessage('');
181
+ nextStep();
182
+ };
183
+
36
184
  const handleLogin = async () => {
37
185
  if (!username || !password) {
38
- setErrorMessage('Please enter both username and password');
186
+ toast.error('Please enter both username and password');
39
187
  return;
40
188
  }
41
189
 
@@ -44,100 +192,324 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
44
192
  await login(username, password);
45
193
  // The authentication state change will be handled through context
46
194
  } catch (error: any) {
47
- setErrorMessage(error.message || 'Login failed');
195
+ toast.error(error.message || 'Login failed');
48
196
  }
49
197
  };
50
198
 
51
- return (
52
- <BottomSheetScrollView
53
- contentContainerStyle={commonStyles.scrollContainer}
54
- keyboardShouldPersistTaps="handled"
55
- >
56
- <OxyLogo
57
- style={{ marginBottom: 24 }}
58
- width={50}
59
- height={50}
60
- />
61
- <Text style={[
62
- styles.title,
63
- { color: colors.text }
64
- ]}>{isAddAccountMode ? 'Add Another Account' : 'Sign In'}</Text>
199
+ // Step components
200
+ const renderUsernameStep = () => (
201
+ <Animated.View style={[
202
+ styles.stepContainer,
203
+ {
204
+ opacity: fadeAnim,
205
+ transform: [
206
+ { translateX: slideAnim },
207
+ { scale: scaleAnim }
208
+ ]
209
+ }
210
+ ]}>
211
+ <View style={styles.modernImageContainer}>
212
+ <Svg width={280} height={160} viewBox="0 0 280 160">
213
+ <Defs>
214
+ <LinearGradient id="primaryGradient" x1="0%" y1="0%" x2="100%" y2="100%">
215
+ <Stop offset="0%" stopColor={colors.primary} stopOpacity="0.8" />
216
+ <Stop offset="100%" stopColor={colors.primary} stopOpacity="0.2" />
217
+ </LinearGradient>
218
+ <LinearGradient id="secondaryGradient" x1="0%" y1="0%" x2="100%" y2="100%">
219
+ <Stop offset="0%" stopColor={colors.primary} stopOpacity="0.1" />
220
+ <Stop offset="100%" stopColor={colors.primary} stopOpacity="0.3" />
221
+ </LinearGradient>
222
+ </Defs>
223
+
224
+ {/* Modern abstract shapes */}
225
+ <Circle cx="80" cy="80" r="45" fill="url(#primaryGradient)" />
226
+ <Circle cx="200" cy="80" r="35" fill="url(#secondaryGradient)" />
227
+ <Path
228
+ d="M40 120 Q80 40 140 80 Q200 120 240 60"
229
+ stroke={colors.primary}
230
+ strokeWidth="4"
231
+ fill="none"
232
+ strokeLinecap="round"
233
+ />
234
+
235
+ {/* Floating elements */}
236
+ <Circle cx="60" cy="50" r="8" fill={colors.primary} opacity="0.6" />
237
+ <Circle cx="220" cy="120" r="6" fill={colors.primary} opacity="0.4" />
238
+ <Circle cx="250" cy="40" r="4" fill={colors.primary} opacity="0.8" />
239
+
240
+ {/* Central focus element */}
241
+ <Circle cx="140" cy="80" r="25" fill={colors.background} opacity="0.9" />
242
+ <Circle cx="135" cy="75" r="3" fill={colors.primary} />
243
+ <Circle cx="145" cy="75" r="3" fill={colors.primary} />
244
+ <Path
245
+ d="M132 85 Q140 92 148 85"
246
+ stroke={colors.primary}
247
+ strokeWidth="2"
248
+ fill="none"
249
+ strokeLinecap="round"
250
+ />
251
+ </Svg>
252
+ </View>
253
+
254
+ <View style={styles.modernHeader}>
255
+ <Text style={[styles.modernTitle, { color: colors.text }]}>
256
+ {isAddAccountMode ? 'Add Account' : 'Welcome Back'}
257
+ </Text>
258
+ <Text style={[styles.modernSubtitle, { color: colors.secondaryText }]}>
259
+ {isAddAccountMode
260
+ ? 'Sign in with another account'
261
+ : 'Sign in to continue your journey'
262
+ }
263
+ </Text>
264
+ </View>
65
265
 
66
266
  {isAddAccountMode && (
67
- <View style={[styles.infoContainer, { backgroundColor: colors.inputBackground }]}>
68
- <Text style={[styles.infoText, { color: colors.secondaryText }]}>
69
- Currently signed in as {user?.username}. Sign in with another account to add it to this device.
267
+ <View style={[styles.modernInfoCard, { backgroundColor: colors.inputBackground }]}>
268
+ <Ionicons name="information-circle" size={20} color={colors.primary} />
269
+ <Text style={[styles.modernInfoText, { color: colors.text }]}>
270
+ Currently signed in as <Text style={{ fontWeight: 'bold' }}>{user?.username}</Text>
70
271
  </Text>
71
272
  </View>
72
273
  )}
73
274
 
74
275
  {errorMessage ? (
75
- <View style={commonStyles.errorContainer}>
76
- <Text style={commonStyles.errorText}>{errorMessage}</Text>
77
- </View>
276
+ <Animated.View style={[styles.modernErrorCard, { backgroundColor: '#FF6B6B20' }]}>
277
+ <Ionicons name="alert-circle" size={20} color="#FF6B6B" />
278
+ <Text style={[styles.errorText, { color: '#FF6B6B' }]}>{errorMessage}</Text>
279
+ </Animated.View>
78
280
  ) : null}
79
281
 
80
- <View style={styles.formContainer}>
81
- <View style={styles.inputContainer}>
82
- <Text style={[styles.label, { color: colors.text }]}>Username</Text>
282
+ <Animated.View style={[
283
+ styles.modernInputContainer,
284
+ { transform: [{ scale: inputScaleAnim }] }
285
+ ]}>
286
+ <View style={[styles.inputWrapper, { borderColor: isInputFocused ? colors.primary : colors.border }]}>
287
+ <Ionicons
288
+ name="person-outline"
289
+ size={20}
290
+ color={isInputFocused ? colors.primary : colors.secondaryText}
291
+ style={styles.inputIcon}
292
+ />
83
293
  <TextInput
84
- style={commonStyles.input}
294
+ style={[styles.modernInput, { color: colors.text }]}
85
295
  placeholder="Enter your username"
86
296
  placeholderTextColor={colors.placeholder}
87
297
  value={username}
88
298
  onChangeText={setUsername}
299
+ onFocus={handleInputFocus}
300
+ onBlur={handleInputBlur}
89
301
  autoCapitalize="none"
90
302
  testID="username-input"
91
303
  />
92
304
  </View>
305
+ </Animated.View>
306
+
307
+ <TouchableOpacity
308
+ style={[
309
+ styles.modernButton,
310
+ {
311
+ backgroundColor: colors.primary,
312
+ opacity: !username ? 0.5 : 1,
313
+ shadowColor: colors.primary,
314
+ }
315
+ ]}
316
+ onPress={handleUsernameNext}
317
+ disabled={!username}
318
+ testID="username-next-button"
319
+ >
320
+ <Text style={styles.modernButtonText}>Continue</Text>
321
+ <Ionicons name="arrow-forward" size={20} color="#FFFFFF" style={styles.buttonIcon} />
322
+ </TouchableOpacity>
323
+
324
+ <View style={styles.footerTextContainer}>
325
+ <Text style={[styles.footerText, { color: colors.secondaryText }]}>
326
+ Don't have an account?{' '}
327
+ </Text>
328
+ <TouchableOpacity onPress={() => navigate('SignUp')}>
329
+ <Text style={[styles.modernLinkText, { color: colors.primary }]}>Sign Up</Text>
330
+ </TouchableOpacity>
331
+ </View>
332
+ </Animated.View>
333
+ );
334
+
335
+ const renderPasswordStep = () => (
336
+ <Animated.View style={[
337
+ styles.stepContainer,
338
+ {
339
+ opacity: fadeAnim,
340
+ transform: [
341
+ { translateX: slideAnim },
342
+ { scale: scaleAnim }
343
+ ]
344
+ }
345
+ ]}>
346
+ <View style={styles.modernUserProfileContainer}>
347
+ <Animated.View style={[
348
+ styles.avatarContainer,
349
+ { transform: [{ scale: logoAnim }] }
350
+ ]}>
351
+ <Avatar
352
+ uri={userProfile?.avatar}
353
+ name={userProfile?.displayName || userProfile?.name || username}
354
+ size={100}
355
+ theme={theme}
356
+ style={styles.modernUserAvatar}
357
+ />
358
+ <View style={[styles.statusIndicator, { backgroundColor: colors.primary }]} />
359
+ </Animated.View>
360
+
361
+ <Text style={[styles.modernUserDisplayName, { color: colors.text }]}>
362
+ {userProfile?.displayName || userProfile?.name || username}
363
+ </Text>
364
+ <Text style={[styles.modernUsernameSubtext, { color: colors.secondaryText }]}>
365
+ @{username}
366
+ </Text>
367
+
368
+ <View style={[styles.welcomeBackBadge, { backgroundColor: colors.primary + '15' }]}>
369
+ <Ionicons name="checkmark-circle" size={16} color={colors.primary} />
370
+ <Text style={[styles.welcomeBackText, { color: colors.primary }]}>
371
+ Welcome back!
372
+ </Text>
373
+ </View>
374
+ </View>
93
375
 
94
- <View style={styles.inputContainer}>
95
- <Text style={[styles.label, { color: colors.text }]}>Password</Text>
376
+ {errorMessage ? (
377
+ <Animated.View style={[styles.modernErrorCard, { backgroundColor: '#FF6B6B20' }]}>
378
+ <Ionicons name="alert-circle" size={20} color="#FF6B6B" />
379
+ <Text style={[styles.errorText, { color: '#FF6B6B' }]}>{errorMessage}</Text>
380
+ </Animated.View>
381
+ ) : null}
382
+
383
+ <Animated.View style={[
384
+ styles.modernInputContainer,
385
+ { transform: [{ scale: inputScaleAnim }] }
386
+ ]}>
387
+ <View style={[styles.inputWrapper, { borderColor: isInputFocused ? colors.primary : colors.border }]}>
388
+ <Ionicons
389
+ name="lock-closed-outline"
390
+ size={20}
391
+ color={isInputFocused ? colors.primary : colors.secondaryText}
392
+ style={styles.inputIcon}
393
+ />
96
394
  <TextInput
97
- style={commonStyles.input}
395
+ style={[styles.modernInput, { color: colors.text }]}
98
396
  placeholder="Enter your password"
99
397
  placeholderTextColor={colors.placeholder}
100
398
  value={password}
101
399
  onChangeText={setPassword}
400
+ onFocus={handleInputFocus}
401
+ onBlur={handleInputBlur}
102
402
  secureTextEntry
103
403
  testID="password-input"
104
404
  />
105
405
  </View>
406
+ </Animated.View>
106
407
 
107
- <TouchableOpacity
108
- style={[commonStyles.button, { opacity: isLoading ? 0.8 : 1 }]}
109
- onPress={handleLogin}
110
- disabled={isLoading}
111
- testID="login-button"
112
- >
113
- {isLoading ? (
114
- <ActivityIndicator color="#FFFFFF" size="small" />
115
- ) : (
116
- <Text style={commonStyles.buttonText}>
408
+ <TouchableOpacity
409
+ style={[
410
+ styles.modernButton,
411
+ {
412
+ backgroundColor: colors.primary,
413
+ opacity: isLoading ? 0.8 : 1,
414
+ shadowColor: colors.primary,
415
+ }
416
+ ]}
417
+ onPress={handleLogin}
418
+ disabled={isLoading}
419
+ testID="login-button"
420
+ >
421
+ {isLoading ? (
422
+ <ActivityIndicator color="#FFFFFF" size="small" />
423
+ ) : (
424
+ <>
425
+ <Text style={styles.modernButtonText}>
117
426
  {isAddAccountMode ? 'Add Account' : 'Sign In'}
118
427
  </Text>
119
- )}
428
+ <Ionicons name="arrow-forward" size={20} color="#FFFFFF" style={styles.buttonIcon} />
429
+ </>
430
+ )}
431
+ </TouchableOpacity>
432
+
433
+ <View style={styles.modernNavigationButtons}>
434
+ <TouchableOpacity
435
+ style={[styles.modernBackButton, { borderColor: colors.border }]}
436
+ onPress={prevStep}
437
+ >
438
+ <Ionicons name="chevron-back" size={20} color={colors.text} />
439
+ <Text style={[styles.modernBackButtonText, { color: colors.text }]}>Back</Text>
120
440
  </TouchableOpacity>
441
+ </View>
121
442
 
122
- <View style={styles.footerTextContainer}>
123
- <Text style={[styles.footerText, { color: colors.text }]}>
124
- Don't have an account?{' '}
125
- </Text>
126
- <TouchableOpacity onPress={() => navigate('SignUp')}>
127
- <Text style={[styles.linkText, { color: colors.primary }]}>Sign Up</Text>
128
- </TouchableOpacity>
443
+ {/* Security notice */}
444
+ <View style={styles.securityNotice}>
445
+ <Ionicons name="shield-checkmark" size={16} color={colors.secondaryText} />
446
+ <Text style={[styles.securityText, { color: colors.secondaryText }]}>
447
+ Your connection is secure and encrypted
448
+ </Text>
449
+ </View>
450
+ </Animated.View>
451
+ );
452
+
453
+ const renderCurrentStep = () => {
454
+ switch (currentStep) {
455
+ case 0:
456
+ return renderUsernameStep();
457
+ case 1:
458
+ return renderPasswordStep();
459
+ default:
460
+ return renderUsernameStep();
461
+ }
462
+ };
463
+
464
+ return (
465
+ <BottomSheetScrollView
466
+ contentContainerStyle={commonStyles.scrollContainer}
467
+ keyboardShouldPersistTaps="handled"
468
+ >
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
+ />
129
495
  </View>
496
+ <Text style={[styles.progressText, { color: colors.secondaryText }]}>
497
+ Step {currentStep + 1} of 2
498
+ </Text>
130
499
  </View>
500
+
501
+ {renderCurrentStep()}
131
502
  </BottomSheetScrollView>
132
503
  );
133
504
  };
134
505
 
135
506
  const styles = StyleSheet.create({
507
+ // Legacy styles (keeping for compatibility)
136
508
  title: {
137
509
  fontFamily: Platform.OS === 'web'
138
- ? 'Phudu' // Use CSS font name directly for web
139
- : 'Phudu-Bold', // Use exact font name as registered with Font.loadAsync
140
- fontWeight: Platform.OS === 'web' ? 'bold' : undefined, // Only apply fontWeight on web
510
+ ? 'Phudu'
511
+ : 'Phudu-Bold',
512
+ fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
141
513
  fontSize: 54,
142
514
  marginBottom: 24,
143
515
  },
@@ -166,7 +538,6 @@ const styles = StyleSheet.create({
166
538
  lineHeight: 20,
167
539
  fontWeight: '600',
168
540
  },
169
- // New styles for authenticated user view
170
541
  userInfoContainer: {
171
542
  padding: 20,
172
543
  marginVertical: 20,
@@ -192,6 +563,312 @@ const styles = StyleSheet.create({
192
563
  lineHeight: 20,
193
564
  textAlign: 'center',
194
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
+ modernImageContainer: {
601
+ alignItems: 'center',
602
+ marginBottom: 40,
603
+ paddingVertical: 20,
604
+ },
605
+ modernHeader: {
606
+ alignItems: 'center',
607
+ marginBottom: 24,
608
+ },
609
+ modernTitle: {
610
+ fontFamily: Platform.OS === 'web' ? 'Phudu' : 'Phudu-Bold',
611
+ fontWeight: Platform.OS === 'web' ? 'bold' : undefined,
612
+ fontSize: 54,
613
+ textAlign: 'center',
614
+ marginBottom: 8,
615
+ letterSpacing: -0.5,
616
+ },
617
+ modernSubtitle: {
618
+ fontSize: 16,
619
+ lineHeight: 22,
620
+ textAlign: 'center',
621
+ opacity: 0.8,
622
+ },
623
+
624
+ // Modern Cards
625
+ modernInfoCard: {
626
+ flexDirection: 'row',
627
+ alignItems: 'center',
628
+ padding: 16,
629
+ marginVertical: 16,
630
+ borderRadius: 12,
631
+ gap: 12,
632
+ },
633
+ modernInfoText: {
634
+ fontSize: 14,
635
+ lineHeight: 20,
636
+ flex: 1,
637
+ },
638
+ modernErrorCard: {
639
+ flexDirection: 'row',
640
+ alignItems: 'center',
641
+ padding: 16,
642
+ marginVertical: 16,
643
+ borderRadius: 12,
644
+ gap: 12,
645
+ },
646
+ errorText: {
647
+ fontSize: 14,
648
+ lineHeight: 20,
649
+ flex: 1,
650
+ },
651
+
652
+ // Modern Input Styles
653
+ modernInputContainer: {
654
+ marginBottom: 24,
655
+ },
656
+ inputWrapper: {
657
+ flexDirection: 'row',
658
+ alignItems: 'center',
659
+ borderWidth: 2,
660
+ borderRadius: 16,
661
+ paddingHorizontal: 16,
662
+ paddingVertical: 4,
663
+ backgroundColor: 'rgba(0,0,0,0.02)',
664
+ },
665
+ inputIcon: {
666
+ marginRight: 12,
667
+ },
668
+ modernInput: {
669
+ flex: 1,
670
+ fontSize: 16,
671
+ paddingVertical: 16,
672
+ fontWeight: '500',
673
+ },
674
+
675
+ // Modern Button Styles
676
+ modernButton: {
677
+ flexDirection: 'row',
678
+ alignItems: 'center',
679
+ justifyContent: 'center',
680
+ paddingVertical: 18,
681
+ paddingHorizontal: 32,
682
+ borderRadius: 16,
683
+ marginVertical: 8,
684
+ shadowOffset: {
685
+ width: 0,
686
+ height: 4,
687
+ },
688
+ shadowOpacity: 0.3,
689
+ shadowRadius: 8,
690
+ elevation: 6,
691
+ gap: 8,
692
+ },
693
+ modernButtonText: {
694
+ color: '#FFFFFF',
695
+ fontSize: 16,
696
+ fontWeight: '600',
697
+ letterSpacing: 0.5,
698
+ },
699
+ buttonIcon: {
700
+ marginLeft: 4,
701
+ },
702
+ modernLinkText: {
703
+ fontSize: 14,
704
+ lineHeight: 20,
705
+ fontWeight: '600',
706
+ textDecorationLine: 'underline',
707
+ },
708
+
709
+ // Modern User Profile Styles
710
+ modernUserProfileContainer: {
711
+ alignItems: 'center',
712
+ marginBottom: 32,
713
+ paddingVertical: 24,
714
+ },
715
+ avatarContainer: {
716
+ position: 'relative',
717
+ marginBottom: 20,
718
+ },
719
+ modernUserAvatar: {
720
+ borderWidth: 4,
721
+ borderColor: 'rgba(209, 105, 229, 0.2)',
722
+ },
723
+ statusIndicator: {
724
+ position: 'absolute',
725
+ bottom: 4,
726
+ right: 4,
727
+ width: 20,
728
+ height: 20,
729
+ borderRadius: 10,
730
+ borderWidth: 3,
731
+ borderColor: '#FFFFFF',
732
+ },
733
+ modernUserDisplayName: {
734
+ fontSize: 26,
735
+ fontWeight: '700',
736
+ marginBottom: 4,
737
+ textAlign: 'center',
738
+ letterSpacing: -0.5,
739
+ },
740
+ modernUsernameSubtext: {
741
+ fontSize: 16,
742
+ textAlign: 'center',
743
+ marginBottom: 16,
744
+ opacity: 0.7,
745
+ },
746
+ welcomeBackBadge: {
747
+ flexDirection: 'row',
748
+ alignItems: 'center',
749
+ paddingHorizontal: 12,
750
+ paddingVertical: 6,
751
+ borderRadius: 20,
752
+ gap: 6,
753
+ },
754
+ welcomeBackText: {
755
+ fontSize: 12,
756
+ fontWeight: '600',
757
+ textTransform: 'uppercase',
758
+ letterSpacing: 0.5,
759
+ },
760
+
761
+ // Modern Navigation
762
+ modernNavigationButtons: {
763
+ flexDirection: 'row',
764
+ justifyContent: 'center',
765
+ marginTop: 24,
766
+ marginBottom: 16,
767
+ },
768
+ modernBackButton: {
769
+ flexDirection: 'row',
770
+ alignItems: 'center',
771
+ paddingVertical: 12,
772
+ paddingHorizontal: 20,
773
+ borderRadius: 12,
774
+ borderWidth: 1,
775
+ gap: 8,
776
+ },
777
+ modernBackButtonText: {
778
+ fontSize: 16,
779
+ fontWeight: '500',
780
+ },
781
+
782
+ // Security Notice
783
+ securityNotice: {
784
+ flexDirection: 'row',
785
+ alignItems: 'center',
786
+ justifyContent: 'center',
787
+ marginTop: 20,
788
+ gap: 6,
789
+ },
790
+ securityText: {
791
+ fontSize: 12,
792
+ fontWeight: '500',
793
+ },
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
+ },
195
872
  });
196
873
 
197
874
  export default SignInScreen;