@onairos/react-native 3.0.36 → 3.0.38

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.
@@ -40,7 +40,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
40
40
  test = false,
41
41
  preferredPlatform,
42
42
  }) => {
43
- const [step, setStep] = useState<'connect' | 'pin' | 'training' | 'oauth'>('connect');
43
+ const [step, setStep] = useState<'connect' | 'pin' | 'training' | 'oauth' | 'success'>('connect');
44
44
  const [connections, setConnections] = useState<ConnectionStatus>({});
45
45
  const [pin, setPin] = useState<string>('');
46
46
  const [selectedTier, setSelectedTier] = useState<'Small' | 'Medium' | 'Large'>('Medium');
@@ -54,6 +54,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
54
54
  const [currentPlatform, setCurrentPlatform] = useState<string>('');
55
55
  const [username, setUsername] = useState<string>('Avatar');
56
56
  const [isConnectingPlatform, setIsConnectingPlatform] = useState<boolean>(false);
57
+ const [showLoginWebView, setShowLoginWebView] = useState<boolean>(false);
57
58
 
58
59
  const platforms = [
59
60
  { id: 'instagram', name: 'Instagram', icon: require('../assets/images/instagram.png') },
@@ -220,33 +221,11 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
220
221
  * Handles OAuth callback URLs
221
222
  */
222
223
  const handleOAuthCallback = useCallback((url: string) => {
223
- try {
224
- // Extract the authorization code from the URL
225
- const parsedUrl = new URL(url);
226
- const code = parsedUrl.searchParams.get('code');
227
- const platform = parsedUrl.searchParams.get('platform') || currentPlatform;
228
-
229
- if (code && platform) {
230
- // Update connections state
231
- setConnections(prev => ({
232
- ...prev,
233
- [platform]: { userName: username, connected: true }
234
- }));
235
-
236
- // Update platform toggles
237
- setPlatformToggles(prev => ({
238
- ...prev,
239
- [platform]: true
240
- }));
241
-
242
- // Return to the connect step
243
- setStep('connect');
244
- }
245
- } catch (error) {
246
- console.error('Error handling OAuth callback:', error);
247
- }
248
- }, [currentPlatform, username]);
249
-
224
+ console.log('OAuth callback received:', url);
225
+ // Handle the OAuth callback here
226
+ // This would typically extract the authorization code and complete the OAuth flow
227
+ }, []);
228
+
250
229
  /**
251
230
  * Handles completion of the OAuth flow
252
231
  */
@@ -276,6 +255,28 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
276
255
  setStep('connect');
277
256
  }, [currentPlatform, username]);
278
257
 
258
+ // Function to handle "Already have an account" button
259
+ const handleAlreadyHaveAccount = useCallback(() => {
260
+ console.log('Already have an account clicked - opening login WebView');
261
+ setShowLoginWebView(true);
262
+ // TODO: Open WebView to check for existing Onairos login details
263
+ }, []);
264
+
265
+ // Function to check for existing account (spoofed for now)
266
+ const checkExistingAccount = useCallback(async () => {
267
+ console.log('Checking for existing account...');
268
+ // TODO: Implement actual logic to check cookies/storage for existing account
269
+ // For now, this is spoofed and doesn't do anything
270
+ return false;
271
+ }, []);
272
+
273
+ // Function to handle login WebView completion
274
+ const handleLoginWebViewComplete = useCallback(() => {
275
+ console.log('Login WebView completed');
276
+ setShowLoginWebView(false);
277
+ // TODO: If login successful, skip onboarding and call onComplete
278
+ }, []);
279
+
279
280
  const handlePinSubmit = useCallback(async (userPin: string) => {
280
281
  setPin(userPin);
281
282
  setStep('training');
@@ -307,9 +308,14 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
307
308
  }, [platformToggles]);
308
309
 
309
310
  const handleProceed = () => {
310
- if (canProceedToPin()) {
311
+ console.log('Proceeding to next step');
312
+ // Show success screen first
313
+ setStep('success');
314
+
315
+ // After a delay, proceed to PIN
316
+ setTimeout(() => {
311
317
  setStep('pin');
312
- }
318
+ }, 3000);
313
319
  };
314
320
 
315
321
  return (
@@ -351,6 +357,14 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
351
357
  <Text style={styles.onairosIconText}>O</Text>
352
358
  </View>
353
359
  </View>
360
+
361
+ {/* Already have an account button */}
362
+ <TouchableOpacity
363
+ style={styles.alreadyHaveAccountButton}
364
+ onPress={handleAlreadyHaveAccount}
365
+ >
366
+ <Text style={styles.alreadyHaveAccountText}>Already have an account?</Text>
367
+ </TouchableOpacity>
354
368
  </View>
355
369
 
356
370
  <ScrollView
@@ -375,7 +389,12 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
375
389
  {/* Platform connection options */}
376
390
  <View style={styles.platformsContainer}>
377
391
  {platforms.map((platform) => (
378
- <View key={platform.id} style={styles.platformItem}>
392
+ <TouchableOpacity
393
+ key={platform.id}
394
+ style={styles.platformItem}
395
+ onPress={() => togglePlatform(platform.id)}
396
+ disabled={isConnectingPlatform}
397
+ >
379
398
  <View style={styles.platformInfo}>
380
399
  <Image
381
400
  source={platform.icon}
@@ -386,13 +405,20 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
386
405
  {platform.name}
387
406
  </Text>
388
407
  </View>
389
- <Switch
390
- value={platformToggles[platform.id]}
391
- onValueChange={() => togglePlatform(platform.id)}
392
- trackColor={{ false: '#767577', true: '#81b0ff' }}
393
- thumbColor={platformToggles[platform.id] ? '#2196F3' : '#f4f3f4'}
394
- />
395
- </View>
408
+
409
+ {isConnectingPlatform && currentPlatform === platform.id ? (
410
+ <ActivityIndicator size="small" color={COLORS.primary} />
411
+ ) : (
412
+ <View style={[
413
+ styles.platformToggle,
414
+ platformToggles[platform.id] && styles.platformToggleActive
415
+ ]}>
416
+ {platformToggles[platform.id] && (
417
+ <Icon name="check" size={16} color="#fff" />
418
+ )}
419
+ </View>
420
+ )}
421
+ </TouchableOpacity>
396
422
  ))}
397
423
  </View>
398
424
  </ScrollView>
@@ -419,6 +445,35 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
419
445
  </>
420
446
  )}
421
447
 
448
+ {step === 'success' && (
449
+ <View style={styles.successContainer}>
450
+ <View style={styles.successContent}>
451
+ {/* Big green checkmark */}
452
+ <View style={styles.successIcon}>
453
+ <Icon name="check" size={48} color="#fff" />
454
+ </View>
455
+
456
+ <Text style={styles.successTitle}>Never Connect Again!</Text>
457
+ <Text style={styles.successSubtitle}>
458
+ Setup for future use complete
459
+ </Text>
460
+
461
+ <View style={styles.successMessage}>
462
+ <Text style={styles.successMessageText}>
463
+ Your connections are saved securely. Next time you use {AppName},
464
+ you'll be automatically connected.
465
+ </Text>
466
+ </View>
467
+
468
+ {/* Auto-progress indicator */}
469
+ <View style={styles.progressIndicator}>
470
+ <ActivityIndicator size="small" color="#4CAF50" />
471
+ <Text style={styles.progressText}>Continuing...</Text>
472
+ </View>
473
+ </View>
474
+ </View>
475
+ )}
476
+
422
477
  {step === 'pin' && (
423
478
  <PinInput
424
479
  onSubmit={handlePinSubmit}
@@ -461,6 +516,20 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
461
516
  onComplete={() => setStep('connect')}
462
517
  />
463
518
  )}
519
+
520
+ {/* Login WebView for existing account check */}
521
+ {showLoginWebView && (
522
+ <OAuthWebView
523
+ url="https://onairos.uk/login" // TODO: Replace with actual login URL
524
+ platform="onairos"
525
+ onClose={() => setShowLoginWebView(false)}
526
+ onSuccess={(result) => {
527
+ console.log('Login successful:', result);
528
+ handleLoginWebViewComplete();
529
+ }}
530
+ onComplete={handleLoginWebViewComplete}
531
+ />
532
+ )}
464
533
  </SafeAreaView>
465
534
  </Animated.View>
466
535
  </TouchableWithoutFeedback>
@@ -539,20 +608,20 @@ const styles = StyleSheet.create({
539
608
  color: '#000',
540
609
  },
541
610
  titleContainer: {
542
- marginBottom: 30,
611
+ marginBottom: 20,
543
612
  },
544
613
  mainTitle: {
545
- fontSize: 22,
614
+ fontSize: 20,
546
615
  fontWeight: '600',
547
616
  color: '#000',
548
617
  textAlign: 'center',
549
- marginBottom: 16,
618
+ marginBottom: 12,
550
619
  },
551
620
  privacyMessage: {
552
621
  fontSize: 14,
553
622
  color: '#666',
554
623
  textAlign: 'center',
555
- marginBottom: 16,
624
+ marginBottom: 12,
556
625
  },
557
626
  content: {
558
627
  flex: 1,
@@ -560,8 +629,7 @@ const styles = StyleSheet.create({
560
629
  },
561
630
  scrollContent: {
562
631
  flexGrow: 1,
563
- paddingBottom: 40,
564
- minHeight: '100%',
632
+ paddingBottom: 20,
565
633
  },
566
634
  platformsContainer: {
567
635
  marginTop: 16,
@@ -570,20 +638,21 @@ const styles = StyleSheet.create({
570
638
  flexDirection: 'row',
571
639
  justifyContent: 'space-between',
572
640
  alignItems: 'center',
573
- padding: 16,
641
+ padding: 12,
574
642
  backgroundColor: '#fff',
575
- borderRadius: 16,
576
- marginBottom: 16,
643
+ borderRadius: 12,
644
+ marginBottom: 8,
577
645
  borderWidth: 1,
578
646
  borderColor: '#eee',
579
647
  },
580
648
  platformInfo: {
581
649
  flexDirection: 'row',
582
650
  alignItems: 'center',
651
+ flex: 1,
583
652
  },
584
653
  platformIcon: {
585
- width: 32,
586
- height: 32,
654
+ width: 24,
655
+ height: 24,
587
656
  marginRight: 12,
588
657
  },
589
658
  platformName: {
@@ -624,4 +693,88 @@ const styles = StyleSheet.create({
624
693
  fontSize: 16,
625
694
  fontWeight: '600',
626
695
  },
696
+ alreadyHaveAccountButton: {
697
+ paddingVertical: 8,
698
+ paddingHorizontal: 16,
699
+ },
700
+ alreadyHaveAccountText: {
701
+ color: '#666',
702
+ fontSize: 16,
703
+ },
704
+ successContainer: {
705
+ flex: 1,
706
+ justifyContent: 'center',
707
+ alignItems: 'center',
708
+ },
709
+ successContent: {
710
+ backgroundColor: '#fff',
711
+ padding: 24,
712
+ borderRadius: 16,
713
+ alignItems: 'center',
714
+ },
715
+ successIcon: {
716
+ backgroundColor: '#4CAF50',
717
+ borderRadius: 24,
718
+ padding: 12,
719
+ marginBottom: 16,
720
+ },
721
+ successTitle: {
722
+ fontSize: 22,
723
+ fontWeight: '600',
724
+ color: '#000',
725
+ textAlign: 'center',
726
+ marginBottom: 16,
727
+ },
728
+ successSubtitle: {
729
+ fontSize: 14,
730
+ color: '#666',
731
+ textAlign: 'center',
732
+ marginBottom: 16,
733
+ },
734
+ successMessage: {
735
+ backgroundColor: '#f0f0f0',
736
+ padding: 16,
737
+ borderRadius: 8,
738
+ marginBottom: 16,
739
+ },
740
+ successMessageText: {
741
+ fontSize: 14,
742
+ color: '#666',
743
+ },
744
+ platformToggle: {
745
+ width: 24,
746
+ height: 24,
747
+ borderRadius: 12,
748
+ borderWidth: 1,
749
+ borderColor: '#ccc',
750
+ alignItems: 'center',
751
+ justifyContent: 'center',
752
+ backgroundColor: '#fff',
753
+ },
754
+ platformToggleActive: {
755
+ borderColor: '#000',
756
+ backgroundColor: '#000',
757
+ },
758
+ // Dark mode styles
759
+ darkPlatformItem: {
760
+ backgroundColor: '#333',
761
+ borderColor: '#555',
762
+ },
763
+ darkText: {
764
+ color: '#fff',
765
+ },
766
+ darkSubText: {
767
+ color: '#ccc',
768
+ },
769
+ progressIndicator: {
770
+ flexDirection: 'row',
771
+ alignItems: 'center',
772
+ marginTop: 16,
773
+ },
774
+ progressText: {
775
+ fontSize: 16,
776
+ fontWeight: '500',
777
+ color: '#000',
778
+ marginLeft: 8,
779
+ },
627
780
  });
@@ -1,12 +1,83 @@
1
1
  import { useCallback } from 'react';
2
- import * as Keychain from 'react-native-keychain';
3
2
  import { STORAGE_KEYS } from '../constants';
4
3
  import type { CredentialsResult } from '../types';
5
4
 
5
+ // Create a mock storage for environments without Keychain access
6
+ const mockCredentialStorage: Record<string, any> = {};
7
+
8
+ // Try to import Keychain, but provide fallbacks if not available
9
+ let Keychain: any = null;
10
+ try {
11
+ Keychain = require('react-native-keychain');
12
+ } catch (error) {
13
+ console.warn('react-native-keychain module not available in useCredentials, using mock storage');
14
+ }
15
+
16
+ // Check if Keychain is properly initialized and available
17
+ const isKeychainAvailable = () => {
18
+ try {
19
+ return Keychain && typeof Keychain.getGenericPassword === 'function';
20
+ } catch (e) {
21
+ return false;
22
+ }
23
+ };
24
+
25
+ // Safe wrapper for getGenericPassword
26
+ const safeGetGenericPassword = async (options: any) => {
27
+ try {
28
+ if (isKeychainAvailable()) {
29
+ return await Keychain.getGenericPassword(options);
30
+ } else {
31
+ const key = options?.service || 'default';
32
+ return mockCredentialStorage[key] || null;
33
+ }
34
+ } catch (error) {
35
+ console.warn('Keychain access failed, using mock storage', error);
36
+ const key = options?.service || 'default';
37
+ return mockCredentialStorage[key] || null;
38
+ }
39
+ };
40
+
41
+ // Safe wrapper for setGenericPassword
42
+ const safeSetGenericPassword = async (username: string, password: string, options?: any) => {
43
+ try {
44
+ if (isKeychainAvailable()) {
45
+ return await Keychain.setGenericPassword(username, password, options);
46
+ } else {
47
+ const key = options?.service || 'default';
48
+ mockCredentialStorage[key] = { username, password };
49
+ return true;
50
+ }
51
+ } catch (error) {
52
+ console.warn('Keychain access failed, using mock storage', error);
53
+ const key = options?.service || 'default';
54
+ mockCredentialStorage[key] = { username, password };
55
+ return true;
56
+ }
57
+ };
58
+
59
+ // Safe wrapper for resetGenericPassword
60
+ const safeResetGenericPassword = async (options?: any) => {
61
+ try {
62
+ if (isKeychainAvailable()) {
63
+ return await Keychain.resetGenericPassword(options);
64
+ } else {
65
+ const key = options?.service || 'default';
66
+ delete mockCredentialStorage[key];
67
+ return true;
68
+ }
69
+ } catch (error) {
70
+ console.warn('Keychain access failed, using mock storage', error);
71
+ const key = options?.service || 'default';
72
+ delete mockCredentialStorage[key];
73
+ return true;
74
+ }
75
+ };
76
+
6
77
  export const useCredentials = () => {
7
78
  const hasCredentials = useCallback(async (): Promise<boolean> => {
8
79
  try {
9
- const credentials = await Keychain.getGenericPassword({
80
+ const credentials = await safeGetGenericPassword({
10
81
  service: STORAGE_KEYS.credentials
11
82
  });
12
83
  return !!credentials;
@@ -18,7 +89,7 @@ export const useCredentials = () => {
18
89
 
19
90
  const getCredentials = useCallback(async () => {
20
91
  try {
21
- const credentials = await Keychain.getGenericPassword({
92
+ const credentials = await safeGetGenericPassword({
22
93
  service: STORAGE_KEYS.credentials
23
94
  });
24
95
  if (credentials) {
@@ -37,13 +108,17 @@ export const useCredentials = () => {
37
108
  accessToken: string
38
109
  ): Promise<boolean> => {
39
110
  try {
40
- const options: Keychain.Options = {
41
- accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
42
- accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
111
+ const options: any = {
43
112
  service: STORAGE_KEYS.credentials
44
113
  };
45
114
 
46
- await Keychain.setGenericPassword(
115
+ // Only use secure storage options on real devices
116
+ if (isKeychainAvailable()) {
117
+ options.accessControl = Keychain.ACCESS_CONTROL?.BIOMETRY_ANY;
118
+ options.accessible = Keychain.ACCESSIBLE?.WHEN_UNLOCKED;
119
+ }
120
+
121
+ await safeSetGenericPassword(
47
122
  username,
48
123
  JSON.stringify({ userPin, accessToken }),
49
124
  options
@@ -57,7 +132,7 @@ export const useCredentials = () => {
57
132
 
58
133
  const clearCredentials = useCallback(async (): Promise<void> => {
59
134
  try {
60
- await Keychain.resetGenericPassword({
135
+ await safeResetGenericPassword({
61
136
  service: STORAGE_KEYS.credentials
62
137
  });
63
138
  } catch (error) {