@onairos/react-native 3.0.37 → 3.0.39

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,9 +255,70 @@ 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 Onairos login WebView');
261
+ setShowLoginWebView(true);
262
+ }, []);
263
+
264
+ // Function to check for existing account (spoofed for now)
265
+ const checkExistingAccount = useCallback(async () => {
266
+ console.log('Checking for existing account...');
267
+ // TODO: Implement actual logic to check cookies/storage for existing account
268
+ // For now, this is spoofed and doesn't do anything
269
+ return false;
270
+ }, []);
271
+
272
+ // Function to handle login WebView completion
273
+ const handleLoginWebViewComplete = useCallback((result?: any) => {
274
+ console.log('Login WebView completed with result:', result);
275
+ setShowLoginWebView(false);
276
+
277
+ // If login was successful (detected existing cookies/session)
278
+ if (result === 'onairos_login_success') {
279
+ console.log('Existing Onairos account detected, skipping onboarding');
280
+ // Skip the entire onboarding flow and call onComplete directly
281
+ onComplete('https://api2.onairos.uk', 'existing-session-token', {
282
+ existingAccount: true,
283
+ username: 'existing_user',
284
+ skipOnboarding: true,
285
+ });
286
+ }
287
+ // If no existing account found, continue with normal onboarding
288
+ }, [onComplete]);
289
+
279
290
  const handlePinSubmit = useCallback(async (userPin: string) => {
280
291
  setPin(userPin);
281
292
  setStep('training');
293
+
294
+ // Save session data for "Never Connect Again" functionality
295
+ try {
296
+ const sessionData = {
297
+ pin: userPin,
298
+ connections,
299
+ platformToggles,
300
+ selectedTier,
301
+ username,
302
+ timestamp: Date.now(),
303
+ appName: AppName,
304
+ };
305
+
306
+ // Store session data in secure storage for future use
307
+ console.log('Saving session data for future "Never Connect Again" functionality:', sessionData);
308
+
309
+ // TODO: Implement actual secure storage of session data
310
+ // This would typically involve:
311
+ // 1. Storing encrypted session data locally
312
+ // 2. Setting cookies in WebView for onairos.uk domain
313
+ // 3. Storing authentication tokens securely
314
+
315
+ // For now, we'll simulate this with console logging
316
+ console.log('Session data saved - future apps will detect existing account');
317
+
318
+ } catch (error) {
319
+ console.error('Failed to save session data:', error);
320
+ }
321
+
282
322
  // Simulate training progress over 10 seconds
283
323
  let progress = 0;
284
324
  const interval = setInterval(() => {
@@ -296,10 +336,11 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
296
336
  platformToggles,
297
337
  selectedTier,
298
338
  tierData: requestData?.[selectedTier],
339
+ sessionSaved: true, // Indicate that session was saved
299
340
  });
300
341
  }
301
342
  }, 1000); // Update every 1 second
302
- }, [connections, onComplete, selectedTier, requestData, platformToggles]);
343
+ }, [connections, onComplete, selectedTier, requestData, platformToggles, username, AppName]);
303
344
 
304
345
  const canProceedToPin = useCallback(() => {
305
346
  // Check if at least one platform is toggled on
@@ -307,9 +348,14 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
307
348
  }, [platformToggles]);
308
349
 
309
350
  const handleProceed = () => {
310
- if (canProceedToPin()) {
351
+ console.log('Proceeding to next step');
352
+ // Show success screen first
353
+ setStep('success');
354
+
355
+ // After a delay, proceed to PIN
356
+ setTimeout(() => {
311
357
  setStep('pin');
312
- }
358
+ }, 3000);
313
359
  };
314
360
 
315
361
  return (
@@ -351,6 +397,14 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
351
397
  <Text style={styles.onairosIconText}>O</Text>
352
398
  </View>
353
399
  </View>
400
+
401
+ {/* Already have an account button */}
402
+ <TouchableOpacity
403
+ style={styles.alreadyHaveAccountButton}
404
+ onPress={handleAlreadyHaveAccount}
405
+ >
406
+ <Text style={styles.alreadyHaveAccountText}>Already have an account?</Text>
407
+ </TouchableOpacity>
354
408
  </View>
355
409
 
356
410
  <ScrollView
@@ -375,7 +429,12 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
375
429
  {/* Platform connection options */}
376
430
  <View style={styles.platformsContainer}>
377
431
  {platforms.map((platform) => (
378
- <View key={platform.id} style={styles.platformItem}>
432
+ <TouchableOpacity
433
+ key={platform.id}
434
+ style={styles.platformItem}
435
+ onPress={() => togglePlatform(platform.id)}
436
+ disabled={isConnectingPlatform}
437
+ >
379
438
  <View style={styles.platformInfo}>
380
439
  <Image
381
440
  source={platform.icon}
@@ -386,13 +445,20 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
386
445
  {platform.name}
387
446
  </Text>
388
447
  </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>
448
+
449
+ {isConnectingPlatform && currentPlatform === platform.id ? (
450
+ <ActivityIndicator size="small" color={COLORS.primary} />
451
+ ) : (
452
+ <View style={[
453
+ styles.platformToggle,
454
+ platformToggles[platform.id] && styles.platformToggleActive
455
+ ]}>
456
+ {platformToggles[platform.id] && (
457
+ <Icon name="check" size={16} color="#fff" />
458
+ )}
459
+ </View>
460
+ )}
461
+ </TouchableOpacity>
396
462
  ))}
397
463
  </View>
398
464
  </ScrollView>
@@ -419,6 +485,36 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
419
485
  </>
420
486
  )}
421
487
 
488
+ {step === 'success' && (
489
+ <View style={styles.successContainer}>
490
+ <View style={styles.successContent}>
491
+ {/* Big green checkmark */}
492
+ <View style={styles.successIcon}>
493
+ <Icon name="check" size={48} color="#fff" />
494
+ </View>
495
+
496
+ <Text style={styles.successTitle}>Never Connect Again!</Text>
497
+ <Text style={styles.successSubtitle}>
498
+ Your login session has been saved
499
+ </Text>
500
+
501
+ <View style={styles.successMessage}>
502
+ <Text style={styles.successMessageText}>
503
+ Your Onairos account and platform connections are now saved in your browser cookies.
504
+ Next time you use any app with Onairos, you'll be automatically signed in without
505
+ needing to reconnect your accounts.
506
+ </Text>
507
+ </View>
508
+
509
+ {/* Auto-progress indicator */}
510
+ <View style={styles.progressIndicator}>
511
+ <ActivityIndicator size="small" color="#4CAF50" />
512
+ <Text style={styles.progressText}>Continuing...</Text>
513
+ </View>
514
+ </View>
515
+ </View>
516
+ )}
517
+
422
518
  {step === 'pin' && (
423
519
  <PinInput
424
520
  onSubmit={handlePinSubmit}
@@ -461,6 +557,17 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
461
557
  onComplete={() => setStep('connect')}
462
558
  />
463
559
  )}
560
+
561
+ {/* Login WebView for existing account check */}
562
+ {showLoginWebView && (
563
+ <OAuthWebView
564
+ url="https://onairos.uk/signin"
565
+ platform="onairos"
566
+ onClose={() => setShowLoginWebView(false)}
567
+ onSuccess={handleLoginWebViewComplete}
568
+ onComplete={handleLoginWebViewComplete}
569
+ />
570
+ )}
464
571
  </SafeAreaView>
465
572
  </Animated.View>
466
573
  </TouchableWithoutFeedback>
@@ -539,20 +646,20 @@ const styles = StyleSheet.create({
539
646
  color: '#000',
540
647
  },
541
648
  titleContainer: {
542
- marginBottom: 30,
649
+ marginBottom: 20,
543
650
  },
544
651
  mainTitle: {
545
- fontSize: 22,
652
+ fontSize: 20,
546
653
  fontWeight: '600',
547
654
  color: '#000',
548
655
  textAlign: 'center',
549
- marginBottom: 16,
656
+ marginBottom: 12,
550
657
  },
551
658
  privacyMessage: {
552
659
  fontSize: 14,
553
660
  color: '#666',
554
661
  textAlign: 'center',
555
- marginBottom: 16,
662
+ marginBottom: 12,
556
663
  },
557
664
  content: {
558
665
  flex: 1,
@@ -560,8 +667,7 @@ const styles = StyleSheet.create({
560
667
  },
561
668
  scrollContent: {
562
669
  flexGrow: 1,
563
- paddingBottom: 40,
564
- minHeight: '100%',
670
+ paddingBottom: 20,
565
671
  },
566
672
  platformsContainer: {
567
673
  marginTop: 16,
@@ -570,20 +676,21 @@ const styles = StyleSheet.create({
570
676
  flexDirection: 'row',
571
677
  justifyContent: 'space-between',
572
678
  alignItems: 'center',
573
- padding: 16,
679
+ padding: 12,
574
680
  backgroundColor: '#fff',
575
- borderRadius: 16,
576
- marginBottom: 16,
681
+ borderRadius: 12,
682
+ marginBottom: 8,
577
683
  borderWidth: 1,
578
684
  borderColor: '#eee',
579
685
  },
580
686
  platformInfo: {
581
687
  flexDirection: 'row',
582
688
  alignItems: 'center',
689
+ flex: 1,
583
690
  },
584
691
  platformIcon: {
585
- width: 32,
586
- height: 32,
692
+ width: 24,
693
+ height: 24,
587
694
  marginRight: 12,
588
695
  },
589
696
  platformName: {
@@ -624,4 +731,88 @@ const styles = StyleSheet.create({
624
731
  fontSize: 16,
625
732
  fontWeight: '600',
626
733
  },
734
+ alreadyHaveAccountButton: {
735
+ paddingVertical: 8,
736
+ paddingHorizontal: 16,
737
+ },
738
+ alreadyHaveAccountText: {
739
+ color: '#666',
740
+ fontSize: 16,
741
+ },
742
+ successContainer: {
743
+ flex: 1,
744
+ justifyContent: 'center',
745
+ alignItems: 'center',
746
+ },
747
+ successContent: {
748
+ backgroundColor: '#fff',
749
+ padding: 24,
750
+ borderRadius: 16,
751
+ alignItems: 'center',
752
+ },
753
+ successIcon: {
754
+ backgroundColor: '#4CAF50',
755
+ borderRadius: 24,
756
+ padding: 12,
757
+ marginBottom: 16,
758
+ },
759
+ successTitle: {
760
+ fontSize: 22,
761
+ fontWeight: '600',
762
+ color: '#000',
763
+ textAlign: 'center',
764
+ marginBottom: 16,
765
+ },
766
+ successSubtitle: {
767
+ fontSize: 14,
768
+ color: '#666',
769
+ textAlign: 'center',
770
+ marginBottom: 16,
771
+ },
772
+ successMessage: {
773
+ backgroundColor: '#f0f0f0',
774
+ padding: 16,
775
+ borderRadius: 8,
776
+ marginBottom: 16,
777
+ },
778
+ successMessageText: {
779
+ fontSize: 14,
780
+ color: '#666',
781
+ },
782
+ platformToggle: {
783
+ width: 24,
784
+ height: 24,
785
+ borderRadius: 12,
786
+ borderWidth: 1,
787
+ borderColor: '#ccc',
788
+ alignItems: 'center',
789
+ justifyContent: 'center',
790
+ backgroundColor: '#fff',
791
+ },
792
+ platformToggleActive: {
793
+ borderColor: '#000',
794
+ backgroundColor: '#000',
795
+ },
796
+ // Dark mode styles
797
+ darkPlatformItem: {
798
+ backgroundColor: '#333',
799
+ borderColor: '#555',
800
+ },
801
+ darkText: {
802
+ color: '#fff',
803
+ },
804
+ darkSubText: {
805
+ color: '#ccc',
806
+ },
807
+ progressIndicator: {
808
+ flexDirection: 'row',
809
+ alignItems: 'center',
810
+ marginTop: 16,
811
+ },
812
+ progressText: {
813
+ fontSize: 16,
814
+ fontWeight: '500',
815
+ color: '#000',
816
+ marginLeft: 8,
817
+ },
627
818
  });
@@ -71,6 +71,11 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
71
71
  const handleNavigationStateChange = (navState: any) => {
72
72
  console.log(`Navigation state changed for ${platform}:`, navState.url);
73
73
 
74
+ // Don't process navigation changes while still loading the initial page
75
+ if (isLoading) {
76
+ return;
77
+ }
78
+
74
79
  // Check for error states first
75
80
  if (navState.url.includes('error=') || navState.url.includes('access_denied')) {
76
81
  console.log('OAuth error detected:', navState.url);
@@ -78,138 +83,71 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
78
83
  return;
79
84
  }
80
85
 
81
- // Check for the final redirect to onairos.uk domain (this means backend callback completed)
82
- const isFinalRedirect = (
83
- navState.url.includes('onairos.uk/Home') ||
84
- navState.url.includes('onairos.uk/home') ||
85
- navState.url.includes('onairos.uk/success') ||
86
- navState.url.startsWith('https://onairos.uk/Home') ||
87
- navState.url.includes('onairos.uk') && navState.url.includes('success')
88
- );
89
-
90
- // Enhanced platform-specific success patterns
91
- const platformSuccessPatterns: Record<string, RegExp[]> = {
92
- reddit: [
93
- /reddit\.com\/api\/v1\/authorize\?done=true/,
94
- /reddit\.com\/api\/v1\/authorize\/success/,
95
- /reddit\.com.*code=/
96
- ],
97
- pinterest: [
98
- /pinterest\.com\/oauth\/success/,
99
- /pinterest\.com\/oauth\/complete/,
100
- /pinterest\.com.*code=/,
101
- /api2\.onairos\.uk\/pinterest\/callback/,
102
- /onairos\.uk.*pinterest/
103
- ],
104
- linkedin: [
105
- /linkedin\.com\/oauth\/success/,
106
- /linkedin\.com\/oauth\/complete/,
107
- /linkedin\.com\/uas\/oauth2\/authorization\/success/,
108
- /linkedin\.com.*code=/
109
- ],
110
- email: [/success/, /complete/],
111
- instagram: [
112
- /instagram\.com\/oauth\/authorize\?done=true/,
113
- /instagram\.com\/oauth\/success/,
114
- /instagram\.com.*code=/,
115
- /api2\.onairos\.uk\/instagram\/callback/,
116
- /onairos\.uk.*instagram/
117
- ],
118
- youtube: [
119
- /accounts\.google\.com\/o\/oauth2\/approval/,
120
- /youtube\.com\/oauth\/success/,
121
- /accounts\.google\.com.*code=/,
122
- /api2\.onairos\.uk\/youtube\/callback/
123
- ]
124
- };
125
-
126
- // Check for platform-specific success patterns
127
- const isPlatformSuccess = platform && platformSuccessPatterns[platform] ?
128
- platformSuccessPatterns[platform].some(pattern => pattern.test(navState.url)) :
129
- false;
130
-
131
- // Check for callback URLs that might contain the authorization code
132
- const isCallbackUrl = (
133
- navState.url.includes('/callback') ||
134
- navState.url.includes('code=') ||
135
- navState.url.includes('token=') ||
136
- navState.url.includes('access_token=') ||
137
- navState.url.includes('api2.onairos.uk') ||
138
- navState.url.includes('oauth_token=')
139
- );
140
-
141
- // Extract authorization code or token if present
142
- let authCode = null;
143
- if (isCallbackUrl) {
144
- console.log('Detected callback URL with possible code/token');
145
-
146
- // Try to extract code or token using different patterns
147
- const codeMatch = navState.url.match(/code=([^&]+)/);
148
- const tokenMatch = navState.url.match(/(?:token|access_token)=([^&]+)/);
149
- const oauthTokenMatch = navState.url.match(/oauth_token=([^&]+)/);
150
-
151
- if (codeMatch && codeMatch[1]) {
152
- authCode = decodeURIComponent(codeMatch[1]);
153
- console.log('OAuth code extracted:', authCode);
154
- } else if (tokenMatch && tokenMatch[1]) {
155
- authCode = decodeURIComponent(tokenMatch[1]);
156
- console.log('OAuth token extracted:', authCode);
157
- } else if (oauthTokenMatch && oauthTokenMatch[1]) {
158
- authCode = decodeURIComponent(oauthTokenMatch[1]);
159
- console.log('OAuth token extracted:', authCode);
160
- }
161
-
162
- // Call onSuccess with the extracted code/token
163
- if (authCode) {
164
- console.log(`Calling onSuccess for ${platform} with code:`, authCode);
86
+ // Special handling for Onairos login page (existing account check)
87
+ if (platform === 'onairos') {
88
+ // Check if user successfully logged in to Onairos
89
+ if (navState.url.includes('onairos.uk/Home') ||
90
+ navState.url.includes('onairos.uk/dashboard') ||
91
+ navState.url.includes('onairos.uk/profile')) {
92
+ console.log('Onairos login successful - existing account detected');
165
93
 
166
- // Clear timeout since OAuth completed successfully
94
+ // Clear timeout since login completed successfully
167
95
  if (timeoutRef.current) {
168
96
  clearTimeout(timeoutRef.current);
169
97
  }
170
98
 
171
- onSuccess(authCode);
172
-
173
- // Close the OAuth window after a short delay
174
- setTimeout(() => {
175
- if (onComplete) {
176
- console.log('Calling onComplete to close OAuth window');
177
- onComplete();
178
- }
179
- }, 1000);
99
+ onSuccess('onairos_login_success');
100
+ return;
101
+ }
102
+
103
+ // If still on login page, don't close
104
+ if (navState.url.includes('onairos.uk/signin') ||
105
+ navState.url.includes('onairos.uk/login')) {
106
+ console.log('Still on Onairos login page, waiting for user to complete login');
180
107
  return;
181
108
  }
182
109
  }
183
110
 
184
- // If we see the final redirect or platform-specific success, close the OAuth window
185
- if (isFinalRedirect || isPlatformSuccess) {
186
- console.log(`Detected success for ${platform}`);
111
+ // For platform OAuth flows, only close when we get the FINAL redirect from Onairos backend
112
+ // This happens AFTER the user has logged in and the backend has processed the OAuth callback
113
+ const isFinalOAuthRedirect = (
114
+ navState.url.includes('onairos.uk/Home') ||
115
+ navState.url.includes('onairos.uk/home') ||
116
+ navState.url.includes('onairos.uk/success') ||
117
+ navState.url.includes('onairos.uk/dashboard') ||
118
+ (navState.url.includes('onairos.uk') && navState.url.includes('complete'))
119
+ );
120
+
121
+ // Only close when we get the final redirect from Onairos backend
122
+ if (isFinalOAuthRedirect) {
123
+ console.log(`Final OAuth redirect detected for ${platform} - user has completed login`);
187
124
 
188
- // If we haven't already extracted a code/token, consider this a generic success
189
- if (!authCode) {
190
- console.log(`Calling onSuccess for ${platform} with generic success`);
191
- onSuccess('success');
125
+ // Clear timeout since OAuth completed successfully
126
+ if (timeoutRef.current) {
127
+ clearTimeout(timeoutRef.current);
192
128
  }
193
129
 
194
- // Close the OAuth window
195
- if (onComplete) {
196
- console.log('Calling onComplete to close OAuth window');
197
- onComplete();
198
- }
199
- }
200
-
201
- // Handle specific redirect patterns that indicate completion
202
- if (navState.url.includes('api2.onairos.uk') &&
203
- (navState.url.includes('callback') || navState.url.includes('success'))) {
204
- console.log(`Backend callback detected for ${platform}`);
205
- onSuccess('backend_callback_success');
130
+ onSuccess('oauth_complete');
206
131
 
132
+ // Close the OAuth window after a short delay
207
133
  setTimeout(() => {
208
134
  if (onComplete) {
135
+ console.log('Calling onComplete to close OAuth window');
209
136
  onComplete();
210
137
  }
211
138
  }, 1500);
139
+ return;
212
140
  }
141
+
142
+ // Log intermediate steps but don't close the WebView
143
+ if (navState.url.includes('/callback') || navState.url.includes('code=')) {
144
+ console.log(`OAuth callback detected for ${platform}, but waiting for final redirect from Onairos backend`);
145
+ // Don't close here - wait for the final redirect
146
+ return;
147
+ }
148
+
149
+ // Log other navigation for debugging
150
+ console.log(`${platform} OAuth navigation:`, navState.url);
213
151
  };
214
152
 
215
153
  const handleLoadEnd = () => {