@onairos/react-native 3.0.8 → 3.0.10

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 (33) hide show
  1. package/lib/commonjs/assets/images/onairos_logo.svg +6 -0
  2. package/lib/commonjs/components/Onairos.js +214 -60
  3. package/lib/commonjs/components/Onairos.js.map +1 -1
  4. package/lib/commonjs/components/OnairosButton.js +105 -42
  5. package/lib/commonjs/components/OnairosButton.js.map +1 -1
  6. package/lib/commonjs/components/Overlay.js +26 -23
  7. package/lib/commonjs/components/Overlay.js.map +1 -1
  8. package/lib/commonjs/components/UniversalOnboarding.js +98 -26
  9. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
  10. package/lib/module/assets/images/onairos_logo.svg +6 -0
  11. package/lib/module/components/Onairos.js +217 -62
  12. package/lib/module/components/Onairos.js.map +1 -1
  13. package/lib/module/components/OnairosButton.js +107 -43
  14. package/lib/module/components/OnairosButton.js.map +1 -1
  15. package/lib/module/components/Overlay.js +27 -24
  16. package/lib/module/components/Overlay.js.map +1 -1
  17. package/lib/module/components/UniversalOnboarding.js +100 -28
  18. package/lib/module/components/UniversalOnboarding.js.map +1 -1
  19. package/lib/typescript/components/Onairos.d.ts +26 -24
  20. package/lib/typescript/components/Onairos.d.ts.map +1 -1
  21. package/lib/typescript/components/OnairosButton.d.ts +5 -1
  22. package/lib/typescript/components/OnairosButton.d.ts.map +1 -1
  23. package/lib/typescript/components/Overlay.d.ts.map +1 -1
  24. package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
  25. package/lib/typescript/types.d.ts +2 -1
  26. package/lib/typescript/types.d.ts.map +1 -1
  27. package/package.json +5 -3
  28. package/src/assets/images/onairos_logo.svg +6 -0
  29. package/src/components/Onairos.tsx +283 -89
  30. package/src/components/OnairosButton.tsx +110 -46
  31. package/src/components/Overlay.tsx +118 -110
  32. package/src/components/UniversalOnboarding.tsx +105 -28
  33. package/src/types.ts +2 -1
@@ -0,0 +1,6 @@
1
+ <svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="40" height="40" rx="8" fill="currentColor"/>
3
+ <path d="M20 10C14.48 10 10 14.48 10 20C10 25.52 14.48 30 20 30C25.52 30 30 25.52 30 20C30 14.48 25.52 10 20 10ZM20 28C15.59 28 12 24.41 12 20C12 15.59 15.59 12 20 12C24.41 12 28 15.59 28 20C28 24.41 24.41 28 20 28Z" fill="white"/>
4
+ <path d="M20 15C18.9 15 18 15.9 18 17V19C18 20.1 18.9 21 20 21C21.1 21 22 20.1 22 19V17C22 15.9 21.1 15 20 15Z" fill="white"/>
5
+ <path d="M20 24C19.448 24 19 24.448 19 25C19 25.552 19.448 26 20 26C20.552 26 21 25.552 21 25C21 24.448 20.552 24 20 24Z" fill="white"/>
6
+ </svg>
@@ -1,117 +1,311 @@
1
- import React, { useState, useCallback, useEffect } from 'react';
2
- import { Platform, View } from 'react-native';
3
- import { OnairosButton } from './OnairosButton';
4
- import type { OnairosButtonProps } from '../types';
5
- import { hasCredentials, getCredentials } from '../utils/secureStorage';
6
-
7
- export interface OnairosProps extends Omit<OnairosButtonProps, 'onResolved' | 'onRejection'> {
8
- /**
9
- * Callback function called when the Onairos flow completes successfully
10
- * @param apiUrl The API URL for accessing the data
11
- * @param token The authentication token
12
- * @param userData The user data returned by the server
13
- */
14
- onResolved?: (apiUrl: string, token: string, userData: any) => void;
15
-
16
- /**
17
- * Callback function called when the Onairos flow is rejected or fails
18
- * @param error Optional error message
19
- */
1
+ import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
2
+ import {
3
+ StyleSheet,
4
+ View,
5
+ TouchableOpacity,
6
+ Text,
7
+ ViewStyle,
8
+ TextStyle,
9
+ Platform,
10
+ } from 'react-native';
11
+ import { SvgXml } from 'react-native-svg';
12
+ import { UniversalOnboarding } from './UniversalOnboarding';
13
+ import { Overlay } from './Overlay';
14
+ import { COLORS } from '../constants';
15
+ import { hasCredentials, getCredentials, deleteCredentials } from '../utils/secureStorage';
16
+ import { onairosApi } from '../api';
17
+ import { Portal } from '../utils/Portal';
18
+ import { DataTier } from '../types';
19
+
20
+ // Import the Onairos logo SVG
21
+ const onairosLogoSvg = `<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
22
+ <rect width="40" height="40" rx="8" fill="currentColor"/>
23
+ <path d="M20 10C14.48 10 10 14.48 10 20C10 25.52 14.48 30 20 30C25.52 30 30 25.52 30 20C30 14.48 25.52 10 20 10ZM20 28C15.59 28 12 24.41 12 20C12 15.59 15.59 12 20 12C24.41 12 28 15.59 28 20C28 24.41 24.41 28 20 28Z" fill="white"/>
24
+ <path d="M20 15C18.9 15 18 15.9 18 17V19C18 20.1 18.9 21 20 21C21.1 21 22 20.1 22 19V17C22 15.9 21.1 15 20 15Z" fill="white"/>
25
+ <path d="M20 24C19.448 24 19 24.448 19 25C19 25.552 19.448 26 20 26C20.552 26 21 25.552 21 25C21 24.448 20.552 24 20 24Z" fill="white"/>
26
+ </svg>`;
27
+
28
+ interface OnairosProps {
29
+ returnLink?: string;
30
+ prefillUrl?: string;
31
+ AppName: string;
32
+ buttonType?: 'normal' | 'pill';
33
+ requestData?: {
34
+ [key: string]: DataTier;
35
+ };
36
+ buttonWidth?: number;
37
+ buttonHeight?: number;
38
+ hasStroke?: boolean;
39
+ enabled?: boolean;
40
+ buttonForm?: 'default' | 'connect';
20
41
  onRejection?: (error?: string) => void;
21
-
22
- /**
23
- * Optional test mode flag - simplifies flows for testing
24
- */
42
+ onResolved?: (apiUrl: string, token: string, userData: any) => void;
43
+ preCheck?: () => Promise<boolean>;
44
+ color?: string;
45
+ debug?: boolean;
46
+ darkMode?: boolean;
47
+ preferredPlatform?: string;
25
48
  testMode?: boolean;
26
-
27
- /**
28
- * Optional style for the container
29
- */
30
- containerStyle?: any;
31
49
  }
32
50
 
33
- /**
34
- * Main Onairos component - wraps OnairosButton with additional state management and logic
35
- */
36
- export const Onairos: React.FC<OnairosProps> = ({
37
- AppName,
51
+ export interface OnairosRef {
52
+ trigger: () => Promise<void>;
53
+ reset: () => Promise<void>;
54
+ }
55
+
56
+ export const Onairos = forwardRef<OnairosRef, OnairosProps>(({
38
57
  returnLink,
39
- requestData,
58
+ prefillUrl,
59
+ AppName,
40
60
  buttonType = 'normal',
41
- buttonForm = 'default',
42
- buttonWidth,
61
+ requestData,
62
+ buttonWidth = 180,
43
63
  buttonHeight,
64
+ hasStroke = false,
65
+ enabled = true,
66
+ buttonForm = 'default',
67
+ onRejection,
68
+ onResolved,
69
+ preCheck,
44
70
  color,
45
- hasStroke,
71
+ debug = false,
72
+ darkMode = false,
46
73
  preferredPlatform,
47
- onResolved,
48
- onRejection,
49
74
  testMode = false,
50
- containerStyle,
51
- }) => {
52
- const [isInitialized, setIsInitialized] = useState(false);
53
- const [hasExistingCredentials, setHasExistingCredentials] = useState(false);
75
+ }, ref) => {
76
+ const [showOnboarding, setShowOnboarding] = useState(false);
77
+ const [showOverlay, setShowOverlay] = useState(false);
78
+ const [storedCredentials, setStoredCredentials] = useState<any>(null);
79
+ const [isLoading, setIsLoading] = useState(false);
80
+ const [isPressed, setIsPressed] = useState(false);
54
81
 
55
- const initialize = useCallback(async () => {
82
+ // Expose methods for external control
83
+ useImperativeHandle(ref, () => ({
84
+ trigger: async () => {
85
+ await handlePress();
86
+ },
87
+ reset: async () => {
88
+ await deleteCredentials();
89
+ }
90
+ }));
91
+
92
+ // Compute button text based on buttonForm
93
+ const getButtonText = () => {
94
+ if (buttonForm === 'connect') {
95
+ return 'Connect with Onairos';
96
+ } else {
97
+ return 'Sign in with Onairos';
98
+ }
99
+ };
100
+
101
+ // Calculate background color based on props and state
102
+ const getBackgroundColor = (): string => {
103
+ if (!enabled) {
104
+ return darkMode ? '#3A3A3A' : '#e0e0e0';
105
+ }
106
+
107
+ if (isPressed) {
108
+ return color ?
109
+ (typeof color === 'string' ? `${color}80` : color) :
110
+ (darkMode ? '#32323280' : '#f5f5f580');
111
+ }
112
+
113
+ return color || (darkMode ? '#2A2A2A' : '#ffffff');
114
+ };
115
+
116
+ // Calculate text color based on background luminance
117
+ const getTextColor = (): string => {
118
+ if (!enabled) {
119
+ return darkMode ? '#777777' : '#AAAAAA';
120
+ }
121
+
122
+ if (darkMode) {
123
+ return '#FFFFFF';
124
+ }
125
+
126
+ const bgColor = getBackgroundColor();
127
+ // Simple luminance check - in a real app, this would use a proper algorithm
128
+ return bgColor === '#ffffff' || bgColor === '#f5f5f580' || bgColor.includes('#f') ? '#000000' : '#FFFFFF';
129
+ };
130
+
131
+ const handlePress = async () => {
132
+ if (!enabled || isLoading) return;
133
+
134
+ setIsLoading(true);
135
+
56
136
  try {
57
- // Check for existing credentials
58
- const credentialsExist = await hasCredentials();
59
- setHasExistingCredentials(credentialsExist);
137
+ if (preCheck) {
138
+ const shouldProceed = await preCheck();
139
+ if (!shouldProceed) {
140
+ onRejection?.('Precheck validation failed');
141
+ setIsLoading(false);
142
+ return;
143
+ }
144
+ }
145
+
146
+ // Check if credentials exist
147
+ const hasStoredCreds = await hasCredentials();
60
148
 
61
- if (credentialsExist) {
62
- // Load credentials to verify they are valid
149
+ if (hasStoredCreds) {
150
+ // If credentials exist, fetch them and verify
63
151
  const credentials = await getCredentials();
64
- if (!credentials || !credentials.username || !credentials.userPin) {
65
- setHasExistingCredentials(false);
152
+
153
+ if (!credentials || !credentials.username) {
154
+ // Invalid credentials, clear and start fresh
155
+ await deleteCredentials();
156
+ setShowOnboarding(true);
157
+ setIsLoading(false);
158
+ return;
159
+ }
160
+
161
+ try {
162
+ // Validate credentials with server
163
+ const isValid = await onairosApi.validateCredentials(credentials.username);
164
+
165
+ if (!isValid) {
166
+ // Clear invalid credentials
167
+ await deleteCredentials();
168
+ setShowOnboarding(true);
169
+ setIsLoading(false);
170
+ return;
171
+ }
172
+
173
+ // Store and display overlay with valid credentials
174
+ setStoredCredentials(credentials);
175
+ setShowOverlay(true);
176
+ } catch (validationError) {
177
+ console.warn('Validation error, proceeding to onboarding:', validationError);
178
+ setShowOnboarding(true);
66
179
  }
180
+ } else {
181
+ // If no credentials, show onboarding
182
+ setShowOnboarding(true);
67
183
  }
68
184
  } catch (error) {
69
- console.error('Error initializing Onairos:', error);
185
+ console.error('Error during button press flow:', error);
186
+ // Fall back to onboarding on error
187
+ setShowOnboarding(true);
188
+ onRejection?.(error instanceof Error ? error.message : 'Unknown error');
70
189
  } finally {
71
- setIsInitialized(true);
190
+ setIsLoading(false);
72
191
  }
73
- }, []);
192
+ };
74
193
 
75
- useEffect(() => {
76
- initialize();
77
- }, [initialize]);
78
-
79
- const handleResolved = useCallback((apiUrl: string, token: string, userData: any) => {
194
+ const handleOnboardingComplete = useCallback((apiUrl: string, token: string, data: any) => {
195
+ setShowOnboarding(false);
80
196
  if (onResolved) {
81
- onResolved(apiUrl, token, userData);
197
+ onResolved(apiUrl, token, data);
82
198
  }
83
199
  }, [onResolved]);
84
-
85
- const handleRejection = useCallback((error?: string) => {
86
- if (onRejection) {
87
- onRejection(error);
200
+
201
+ const handleOverlayResolved = useCallback((apiUrl: string, token: string, data: any) => {
202
+ setShowOverlay(false);
203
+ if (onResolved) {
204
+ onResolved(apiUrl, token, data);
88
205
  }
89
- }, [onRejection]);
206
+ }, [onResolved]);
90
207
 
91
- if (!isInitialized) {
92
- // Could render a loading state here if needed
93
- return null;
94
- }
208
+ // Apply custom styling based on props
209
+ const buttonStyle: ViewStyle[] = [
210
+ styles.button,
211
+ {
212
+ width: buttonWidth,
213
+ height: buttonHeight || 48,
214
+ backgroundColor: getBackgroundColor(),
215
+ borderWidth: hasStroke ? 1 : 0,
216
+ borderColor: darkMode ? '#555555' : '#000000',
217
+ borderRadius: buttonType === 'pill' ? 24 : 8,
218
+ },
219
+ ];
220
+
221
+ const textStyle: TextStyle = {
222
+ ...styles.buttonText,
223
+ color: getTextColor(),
224
+ opacity: enabled ? 1 : 0.7,
225
+ };
226
+
227
+ const logoColor = getTextColor();
95
228
 
96
229
  return (
97
- <View style={containerStyle}>
98
- <OnairosButton
99
- AppName={AppName}
100
- returnLink={returnLink}
101
- requestData={requestData}
102
- buttonType={buttonType}
103
- buttonForm={buttonForm}
104
- buttonWidth={buttonWidth}
105
- buttonHeight={buttonHeight}
106
- color={color}
107
- hasStroke={hasStroke}
108
- enabled={true}
109
- preferredPlatform={preferredPlatform}
110
- onResolved={handleResolved}
111
- onRejection={handleRejection}
112
- preCheck={async () => true}
113
- testMode={testMode}
114
- />
115
- </View>
230
+ <>
231
+ <TouchableOpacity
232
+ style={buttonStyle}
233
+ onPress={handlePress}
234
+ disabled={!enabled || isLoading}
235
+ accessibilityLabel="Sign in with Onairos"
236
+ onPressIn={() => setIsPressed(true)}
237
+ onPressOut={() => setIsPressed(false)}
238
+ >
239
+ <View style={styles.buttonContent}>
240
+ <SvgXml
241
+ xml={onairosLogoSvg.replace('currentColor', logoColor)}
242
+ width={24}
243
+ height={24}
244
+ style={styles.logo}
245
+ />
246
+ <Text style={textStyle}>{getButtonText()}</Text>
247
+ </View>
248
+ </TouchableOpacity>
249
+
250
+ {/* Overlay and Onboarding components rendered outside the button */}
251
+ {showOnboarding && (
252
+ <UniversalOnboarding
253
+ visible={showOnboarding}
254
+ onClose={() => {
255
+ setShowOnboarding(false);
256
+ onRejection?.('User closed onboarding');
257
+ }}
258
+ AppName={AppName}
259
+ requestData={requestData as any}
260
+ returnLink={returnLink || ''}
261
+ onComplete={handleOnboardingComplete}
262
+ debug={debug}
263
+ test={testMode}
264
+ preferredPlatform={preferredPlatform}
265
+ />
266
+ )}
267
+
268
+ {/* Overlay rendered through Portal to ensure it appears at root level */}
269
+ {showOverlay && storedCredentials && (
270
+ <Portal>
271
+ <Overlay
272
+ data={requestData || {}}
273
+ username={storedCredentials.username}
274
+ modelKey={storedCredentials.userPin || ''}
275
+ onResolved={handleOverlayResolved}
276
+ appName={AppName}
277
+ darkMode={darkMode}
278
+ />
279
+ </Portal>
280
+ )}
281
+ </>
116
282
  );
117
- };
283
+ });
284
+
285
+ const styles = StyleSheet.create({
286
+ button: {
287
+ flexDirection: 'row',
288
+ alignItems: 'center',
289
+ justifyContent: 'center',
290
+ paddingVertical: 12,
291
+ paddingHorizontal: 16,
292
+ shadowColor: '#000',
293
+ shadowOffset: { width: 0, height: 2 },
294
+ shadowOpacity: 0.1,
295
+ shadowRadius: 4,
296
+ elevation: 2,
297
+ },
298
+ buttonContent: {
299
+ flexDirection: 'row',
300
+ alignItems: 'center',
301
+ justifyContent: 'center',
302
+ },
303
+ logo: {
304
+ marginRight: 8,
305
+ },
306
+ buttonText: {
307
+ fontSize: 16,
308
+ fontWeight: '600',
309
+ textAlign: 'center',
310
+ },
311
+ });
@@ -1,15 +1,14 @@
1
- import React, { useState, useCallback } from 'react';
1
+ import React, { useState, useCallback, forwardRef, useImperativeHandle } from 'react';
2
2
  import {
3
3
  TouchableOpacity,
4
4
  Text,
5
5
  StyleSheet,
6
6
  View,
7
7
  ViewStyle,
8
- TextStyle,
9
- Image,
8
+ TextStyle,
10
9
  ActivityIndicator,
11
- Alert
12
10
  } from 'react-native';
11
+ import { SvgXml } from 'react-native-svg';
13
12
  import { UniversalOnboarding } from './UniversalOnboarding';
14
13
  import { Overlay } from './Overlay';
15
14
  import { COLORS } from '../constants';
@@ -18,18 +17,31 @@ import { hasCredentials, getCredentials, deleteCredentials as clearCredentials }
18
17
  import { onairosApi } from '../api';
19
18
  import { Portal } from '../utils/Portal';
20
19
 
20
+ // Import the Onairos logo SVG
21
+ const onairosLogoSvg = `<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
22
+ <rect width="40" height="40" rx="8" fill="currentColor"/>
23
+ <path d="M20 10C14.48 10 10 14.48 10 20C10 25.52 14.48 30 20 30C25.52 30 30 25.52 30 20C30 14.48 25.52 10 20 10ZM20 28C15.59 28 12 24.41 12 20C12 15.59 15.59 12 20 12C24.41 12 28 15.59 28 20C28 24.41 24.41 28 20 28Z" fill="white"/>
24
+ <path d="M20 15C18.9 15 18 15.9 18 17V19C18 20.1 18.9 21 20 21C21.1 21 22 20.1 22 19V17C22 15.9 21.1 15 20 15Z" fill="white"/>
25
+ <path d="M20 24C19.448 24 19 24.448 19 25C19 25.552 19.448 26 20 26C20.552 26 21 25.552 21 25C21 24.448 20.552 24 20 24Z" fill="white"/>
26
+ </svg>`;
27
+
28
+ export interface OnairosButtonRef {
29
+ trigger: () => Promise<void>;
30
+ reset: () => Promise<void>;
31
+ }
32
+
21
33
  /**
22
34
  * OnairosButton Component - A sign-in button similar to Google/Apple sign-in
23
35
  */
24
- export const OnairosButton: React.FC<OnairosButtonProps> = ({
36
+ export const OnairosButton = forwardRef<OnairosButtonRef, OnairosButtonProps>(({
25
37
  returnLink,
26
38
  prefillUrl,
27
39
  AppName,
28
40
  buttonType = 'normal',
29
41
  requestData,
30
- buttonWidth = 240,
42
+ buttonWidth = 180,
31
43
  buttonHeight = 48,
32
- hasStroke = true,
44
+ hasStroke = false,
33
45
  enabled = true,
34
46
  buttonForm = 'default',
35
47
  onRejection,
@@ -38,15 +50,64 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
38
50
  color,
39
51
  swerv = false,
40
52
  debug = false,
53
+ darkMode = false,
41
54
  preferredPlatform,
42
55
  testMode = false,
43
- }) => {
56
+ }, ref) => {
44
57
  const [showOnboarding, setShowOnboarding] = useState(false);
45
58
  const [showOverlay, setShowOverlay] = useState(false);
46
59
  const [storedCredentials, setStoredCredentials] = useState<any>(null);
47
60
  const [isLoading, setIsLoading] = useState(false);
61
+ const [isPressed, setIsPressed] = useState(false);
62
+
63
+ // Expose methods via ref
64
+ useImperativeHandle(ref, () => ({
65
+ trigger: async () => {
66
+ await handlePress();
67
+ },
68
+ reset: async () => {
69
+ await clearCredentials();
70
+ }
71
+ }));
72
+
73
+ // Compute button text based on buttonForm
74
+ const getButtonText = () => {
75
+ if (buttonForm === 'connect' || buttonForm === 'signup') {
76
+ return 'Connect with Onairos';
77
+ } else {
78
+ return 'Sign in with Onairos';
79
+ }
80
+ };
48
81
 
49
- const isDarkMode = color === 'black' || (!color && !hasStroke);
82
+ // Calculate background color based on props and state
83
+ const getBackgroundColor = (): string => {
84
+ if (!enabled) {
85
+ return darkMode ? '#3A3A3A' : '#e0e0e0';
86
+ }
87
+
88
+ if (isPressed) {
89
+ return color ?
90
+ (typeof color === 'string' ? `${color}80` : color) :
91
+ (darkMode ? '#32323280' : '#f5f5f580');
92
+ }
93
+
94
+ return color || (darkMode ? '#2A2A2A' : '#ffffff');
95
+ };
96
+
97
+ // Calculate text color based on background luminance
98
+ const getTextColor = (): string => {
99
+ if (!enabled) {
100
+ return darkMode ? '#777777' : '#AAAAAA';
101
+ }
102
+
103
+ if (darkMode) {
104
+ return '#FFFFFF';
105
+ }
106
+
107
+ const bgColor = getBackgroundColor();
108
+ // Simple luminance check
109
+ return bgColor === '#ffffff' || bgColor === '#f5f5f580' || bgColor.includes('#f') ? '#000000' : '#FFFFFF';
110
+ };
50
111
 
51
112
  const handlePress = async () => {
52
113
  if (!enabled || isLoading) return;
@@ -79,7 +140,7 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
79
140
  }
80
141
 
81
142
  try {
82
- // Validate credentials with server - catch errors here to prevent crashing
143
+ // Validate credentials with server
83
144
  const isValid = await onairosApi.validateCredentials(credentials.username);
84
145
 
85
146
  if (!isValid) {
@@ -132,20 +193,17 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
132
193
  hasStroke && styles.strokedButton,
133
194
  {
134
195
  width: buttonWidth,
135
- height: buttonHeight
196
+ height: buttonHeight,
197
+ backgroundColor: getBackgroundColor(),
198
+ borderColor: darkMode ? '#555555' : '#000000',
136
199
  },
137
- color ? { backgroundColor: color } : null,
138
- isDarkMode ? styles.darkButton : styles.lightButton,
139
200
  swerv && styles.swervButton,
140
201
  !enabled && styles.disabledButton
141
202
  ].filter(Boolean) as ViewStyle[];
142
203
 
143
- // Calculate text styles based on props
144
- const textStyle: TextStyle[] = [
145
- styles.buttonText,
146
- isDarkMode ? styles.lightText : styles.darkText,
147
- !enabled && styles.disabledText
148
- ].filter(Boolean) as TextStyle[];
204
+ // Calculate text color
205
+ const textColor = getTextColor();
206
+ const logoColor = textColor;
149
207
 
150
208
  // Render components
151
209
  return (
@@ -155,17 +213,26 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
155
213
  onPress={handlePress}
156
214
  disabled={!enabled || isLoading}
157
215
  accessibilityLabel={`Sign in with Onairos`}
216
+ onPressIn={() => setIsPressed(true)}
217
+ onPressOut={() => setIsPressed(false)}
158
218
  >
159
219
  {isLoading ? (
160
220
  <ActivityIndicator
161
221
  size="small"
162
- color={isDarkMode ? '#fff' : '#000'}
222
+ color={textColor}
163
223
  />
164
224
  ) : (
165
- <>
166
- {/* Optional Onairos logo could be added here */}
167
- <Text style={textStyle}>Sign in with Onairos</Text>
168
- </>
225
+ <View style={styles.buttonContent}>
226
+ <SvgXml
227
+ xml={onairosLogoSvg.replace('currentColor', logoColor)}
228
+ width={24}
229
+ height={24}
230
+ style={styles.logo}
231
+ />
232
+ <Text style={[styles.buttonText, { color: textColor }]}>
233
+ {getButtonText()}
234
+ </Text>
235
+ </View>
169
236
  )}
170
237
  </TouchableOpacity>
171
238
 
@@ -178,11 +245,12 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
178
245
  onRejection?.('User closed onboarding');
179
246
  }}
180
247
  AppName={AppName}
181
- requestData={requestData}
182
- returnLink={returnLink}
248
+ requestData={requestData as any}
249
+ returnLink={returnLink || ''}
183
250
  onComplete={handleOnboardingComplete}
184
251
  debug={debug}
185
252
  test={testMode}
253
+ preferredPlatform={preferredPlatform}
186
254
  />
187
255
  )}
188
256
 
@@ -195,12 +263,13 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
195
263
  modelKey={storedCredentials.userPin || ''}
196
264
  onResolved={handleOverlayResolved}
197
265
  appName={AppName}
266
+ darkMode={darkMode}
198
267
  />
199
268
  </Portal>
200
269
  )}
201
270
  </>
202
271
  );
203
- };
272
+ });
204
273
 
205
274
  const styles = StyleSheet.create({
206
275
  button: {
@@ -209,7 +278,20 @@ const styles = StyleSheet.create({
209
278
  justifyContent: 'center',
210
279
  paddingVertical: 12,
211
280
  paddingHorizontal: 16,
212
- borderRadius: 4,
281
+ borderRadius: 8,
282
+ shadowColor: '#000',
283
+ shadowOffset: { width: 0, height: 2 },
284
+ shadowOpacity: 0.1,
285
+ shadowRadius: 4,
286
+ elevation: 2,
287
+ },
288
+ buttonContent: {
289
+ flexDirection: 'row',
290
+ alignItems: 'center',
291
+ justifyContent: 'center',
292
+ },
293
+ logo: {
294
+ marginRight: 8,
213
295
  },
214
296
  pillButton: {
215
297
  borderRadius: 24,
@@ -217,19 +299,10 @@ const styles = StyleSheet.create({
217
299
  strokedButton: {
218
300
  backgroundColor: 'transparent',
219
301
  borderWidth: 1,
220
- borderColor: '#000',
221
302
  },
222
303
  swervButton: {
223
304
  transform: [{ rotate: '-2deg' }],
224
305
  },
225
- darkButton: {
226
- backgroundColor: '#000',
227
- borderColor: '#000',
228
- },
229
- lightButton: {
230
- backgroundColor: '#fff',
231
- borderColor: '#000',
232
- },
233
306
  disabledButton: {
234
307
  opacity: 0.6,
235
308
  },
@@ -238,13 +311,4 @@ const styles = StyleSheet.create({
238
311
  fontWeight: '600',
239
312
  textAlign: 'center',
240
313
  },
241
- lightText: {
242
- color: '#fff',
243
- },
244
- darkText: {
245
- color: '#000',
246
- },
247
- disabledText: {
248
- opacity: 0.7,
249
- },
250
314
  });