@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.
- package/lib/commonjs/assets/images/onairos_logo.svg +6 -0
- package/lib/commonjs/components/Onairos.js +214 -60
- package/lib/commonjs/components/Onairos.js.map +1 -1
- package/lib/commonjs/components/OnairosButton.js +105 -42
- package/lib/commonjs/components/OnairosButton.js.map +1 -1
- package/lib/commonjs/components/Overlay.js +26 -23
- package/lib/commonjs/components/Overlay.js.map +1 -1
- package/lib/commonjs/components/UniversalOnboarding.js +98 -26
- package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
- package/lib/module/assets/images/onairos_logo.svg +6 -0
- package/lib/module/components/Onairos.js +217 -62
- package/lib/module/components/Onairos.js.map +1 -1
- package/lib/module/components/OnairosButton.js +107 -43
- package/lib/module/components/OnairosButton.js.map +1 -1
- package/lib/module/components/Overlay.js +27 -24
- package/lib/module/components/Overlay.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +100 -28
- package/lib/module/components/UniversalOnboarding.js.map +1 -1
- package/lib/typescript/components/Onairos.d.ts +26 -24
- package/lib/typescript/components/Onairos.d.ts.map +1 -1
- package/lib/typescript/components/OnairosButton.d.ts +5 -1
- package/lib/typescript/components/OnairosButton.d.ts.map +1 -1
- package/lib/typescript/components/Overlay.d.ts.map +1 -1
- package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +2 -1
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/src/assets/images/onairos_logo.svg +6 -0
- package/src/components/Onairos.tsx +283 -89
- package/src/components/OnairosButton.tsx +110 -46
- package/src/components/Overlay.tsx +118 -110
- package/src/components/UniversalOnboarding.tsx +105 -28
- 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
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
58
|
+
prefillUrl,
|
|
59
|
+
AppName,
|
|
40
60
|
buttonType = 'normal',
|
|
41
|
-
|
|
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
|
-
|
|
71
|
+
debug = false,
|
|
72
|
+
darkMode = false,
|
|
46
73
|
preferredPlatform,
|
|
47
|
-
onResolved,
|
|
48
|
-
onRejection,
|
|
49
74
|
testMode = false,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const [
|
|
53
|
-
const [
|
|
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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 (
|
|
62
|
-
//
|
|
149
|
+
if (hasStoredCreds) {
|
|
150
|
+
// If credentials exist, fetch them and verify
|
|
63
151
|
const credentials = await getCredentials();
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
-
|
|
190
|
+
setIsLoading(false);
|
|
72
191
|
}
|
|
73
|
-
}
|
|
192
|
+
};
|
|
74
193
|
|
|
75
|
-
|
|
76
|
-
|
|
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,
|
|
197
|
+
onResolved(apiUrl, token, data);
|
|
82
198
|
}
|
|
83
199
|
}, [onResolved]);
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
}, [
|
|
206
|
+
}, [onResolved]);
|
|
90
207
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
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 =
|
|
42
|
+
buttonWidth = 180,
|
|
31
43
|
buttonHeight = 48,
|
|
32
|
-
hasStroke =
|
|
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
|
-
|
|
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
|
|
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
|
|
144
|
-
const
|
|
145
|
-
|
|
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={
|
|
222
|
+
color={textColor}
|
|
163
223
|
/>
|
|
164
224
|
) : (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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:
|
|
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
|
});
|