@onairos/react-native 3.0.9 → 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 (29) 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/UniversalOnboarding.js +98 -26
  7. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
  8. package/lib/module/assets/images/onairos_logo.svg +6 -0
  9. package/lib/module/components/Onairos.js +217 -62
  10. package/lib/module/components/Onairos.js.map +1 -1
  11. package/lib/module/components/OnairosButton.js +107 -43
  12. package/lib/module/components/OnairosButton.js.map +1 -1
  13. package/lib/module/components/UniversalOnboarding.js +100 -28
  14. package/lib/module/components/UniversalOnboarding.js.map +1 -1
  15. package/lib/module/index.js +10 -9
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/typescript/components/Onairos.d.ts +26 -24
  18. package/lib/typescript/components/Onairos.d.ts.map +1 -1
  19. package/lib/typescript/components/OnairosButton.d.ts +5 -1
  20. package/lib/typescript/components/OnairosButton.d.ts.map +1 -1
  21. package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
  22. package/lib/typescript/types.d.ts +2 -1
  23. package/lib/typescript/types.d.ts.map +1 -1
  24. package/package.json +5 -3
  25. package/src/assets/images/onairos_logo.svg +6 -0
  26. package/src/components/Onairos.tsx +283 -89
  27. package/src/components/OnairosButton.tsx +110 -46
  28. package/src/components/UniversalOnboarding.tsx +105 -28
  29. package/src/types.ts +2 -1
@@ -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
  });
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -7,8 +7,11 @@ import {
7
7
  ActivityIndicator,
8
8
  Dimensions,
9
9
  Platform,
10
+ Modal,
11
+ Animated,
12
+ SafeAreaView,
13
+ TouchableWithoutFeedback,
10
14
  } from 'react-native';
11
- import BottomSheet from '@gorhom/bottom-sheet';
12
15
  import Icon from 'react-native-vector-icons/MaterialIcons';
13
16
  import { PlatformList } from './PlatformList';
14
17
  import { PinInput } from './PinInput';
@@ -17,7 +20,7 @@ import { useConnections } from '../hooks/useConnections';
17
20
  import { COLORS } from '../constants';
18
21
  import type { UniversalOnboardingProps, ConnectionStatus } from '../types';
19
22
 
20
- const { height } = Dimensions.get('window');
23
+ const { height, width } = Dimensions.get('window');
21
24
 
22
25
  export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
23
26
  visible,
@@ -29,8 +32,8 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
29
32
  embedd = false,
30
33
  debug = false,
31
34
  test = false,
35
+ preferredPlatform,
32
36
  }) => {
33
- const bottomSheetRef = useRef<BottomSheet>(null);
34
37
  const [step, setStep] = useState<'connect' | 'pin' | 'training'>('connect');
35
38
  const [connections, setConnections] = useState<ConnectionStatus>({});
36
39
  const [pin, setPin] = useState<string>('');
@@ -39,6 +42,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
39
42
  progress: number;
40
43
  eta: string;
41
44
  }>({ progress: 0, eta: '' });
45
+ const [slideAnim] = useState(new Animated.Value(height));
42
46
 
43
47
  const {
44
48
  connectPlatform,
@@ -47,12 +51,15 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
47
51
  isConnecting,
48
52
  } = useConnections();
49
53
 
50
- const snapPoints = useMemo(() => ['90%'], []);
51
-
52
54
  useEffect(() => {
53
55
  if (visible) {
54
- bottomSheetRef.current?.expand();
55
56
  loadInitialStatus();
57
+ // Animate in
58
+ Animated.spring(slideAnim, {
59
+ toValue: 0,
60
+ useNativeDriver: true,
61
+ bounciness: 0,
62
+ }).start();
56
63
 
57
64
  // Debug mode for Expo Go
58
65
  if (debug || Platform.OS === 'web') {
@@ -65,10 +72,36 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
65
72
  });
66
73
  }
67
74
  }
75
+
76
+ // If there's a preferred platform, pre-connect
77
+ if (preferredPlatform && debug) {
78
+ setConnections(prev => ({
79
+ ...prev,
80
+ [preferredPlatform]: { userName: `${preferredPlatform}_user`, connected: true }
81
+ }));
82
+ }
68
83
  } else {
69
- bottomSheetRef.current?.close();
84
+ // Animate out
85
+ Animated.timing(slideAnim, {
86
+ toValue: height,
87
+ duration: 250,
88
+ useNativeDriver: true,
89
+ }).start(() => {
90
+ // Reset state if needed
91
+ });
70
92
  }
71
- }, [visible]);
93
+ }, [visible, preferredPlatform]);
94
+
95
+ const handleClose = () => {
96
+ // Animate out and then call onClose
97
+ Animated.timing(slideAnim, {
98
+ toValue: height,
99
+ duration: 250,
100
+ useNativeDriver: true,
101
+ }).start(() => {
102
+ onClose();
103
+ });
104
+ };
72
105
 
73
106
  const loadInitialStatus = useCallback(async () => {
74
107
  const status = await getConnectionStatus();
@@ -111,7 +144,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
111
144
  }, 1000);
112
145
  }, [connections, onComplete, selectedTier, requestData]);
113
146
 
114
- const canProceedToPin = useMemo(() => {
147
+ const canProceedToPin = useCallback(() => {
115
148
  const connectedPlatforms = Object.values(connections).filter(Boolean).length;
116
149
  return connectedPlatforms >= 2;
117
150
  }, [connections]);
@@ -119,8 +152,8 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
119
152
  const renderHeader = () => (
120
153
  <View style={styles.header}>
121
154
  <Icon name="auto_awesome" size={24} color={COLORS.primary} />
122
- <Text style={styles.headerTitle}>Connect with Onairos</Text>
123
- <TouchableOpacity onPress={onClose} style={styles.closeButton}>
155
+ <Text style={styles.headerTitle}>Connect with {AppName}</Text>
156
+ <TouchableOpacity onPress={handleClose} style={styles.closeButton}>
124
157
  <Icon name="close" size={24} color="#000" />
125
158
  </TouchableOpacity>
126
159
  </View>
@@ -134,7 +167,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
134
167
  connections={connections}
135
168
  onToggle={handlePlatformToggle}
136
169
  isLoading={isConnecting}
137
- canProceed={canProceedToPin}
170
+ canProceed={canProceedToPin()}
138
171
  onProceed={() => setStep('pin')}
139
172
  />
140
173
  );
@@ -151,7 +184,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
151
184
  return (
152
185
  <TrainingModal
153
186
  visible={step === 'training'}
154
- onClose={onClose}
187
+ onClose={handleClose}
155
188
  onComplete={() => {
156
189
  onComplete('https://api2.onairos.uk', 'dummy-token', {
157
190
  pin,
@@ -164,40 +197,84 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
164
197
  username="demo_user"
165
198
  progress={training.progress}
166
199
  eta={training.eta}
167
- onCancel={onClose}
200
+ onCancel={handleClose}
168
201
  />
169
202
  );
170
203
  }
171
204
  };
172
205
 
206
+ if (!visible) return null;
207
+
173
208
  return (
174
- <BottomSheet
175
- ref={bottomSheetRef}
176
- snapPoints={snapPoints}
177
- enablePanDownToClose
178
- onClose={onClose}
179
- index={visible ? 0 : -1}
209
+ <Modal
210
+ visible={visible}
211
+ transparent
212
+ animationType="none"
213
+ statusBarTranslucent
214
+ onRequestClose={handleClose}
180
215
  >
181
- <View style={styles.container}>
182
- {renderHeader()}
183
- {renderContent()}
184
- </View>
185
- </BottomSheet>
216
+ <TouchableWithoutFeedback onPress={handleClose}>
217
+ <View style={styles.modalOverlay}>
218
+ <TouchableWithoutFeedback onPress={e => e.stopPropagation()}>
219
+ <Animated.View
220
+ style={[
221
+ styles.bottomSheet,
222
+ {
223
+ transform: [{ translateY: slideAnim }],
224
+ }
225
+ ]}
226
+ >
227
+ <SafeAreaView style={styles.container}>
228
+ <View style={styles.handleContainer}>
229
+ <View style={styles.handle} />
230
+ </View>
231
+ {renderHeader()}
232
+ {renderContent()}
233
+ </SafeAreaView>
234
+ </Animated.View>
235
+ </TouchableWithoutFeedback>
236
+ </View>
237
+ </TouchableWithoutFeedback>
238
+ </Modal>
186
239
  );
187
240
  };
188
241
 
189
242
  const styles = StyleSheet.create({
243
+ modalOverlay: {
244
+ flex: 1,
245
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
246
+ justifyContent: 'flex-end',
247
+ alignItems: 'center',
248
+ },
249
+ bottomSheet: {
250
+ backgroundColor: '#fff',
251
+ width: width,
252
+ height: height * 0.9,
253
+ borderTopLeftRadius: 24,
254
+ borderTopRightRadius: 24,
255
+ overflow: 'hidden',
256
+ },
190
257
  container: {
191
258
  flex: 1,
192
259
  backgroundColor: '#fff',
193
260
  },
261
+ handleContainer: {
262
+ width: '100%',
263
+ alignItems: 'center',
264
+ paddingTop: 12,
265
+ paddingBottom: 8,
266
+ },
267
+ handle: {
268
+ width: 40,
269
+ height: 5,
270
+ borderRadius: 3,
271
+ backgroundColor: '#E0E0E0',
272
+ },
194
273
  header: {
195
274
  flexDirection: 'row',
196
275
  alignItems: 'center',
197
276
  padding: 16,
198
277
  backgroundColor: COLORS.headerBg,
199
- borderTopLeftRadius: 16,
200
- borderTopRightRadius: 16,
201
278
  },
202
279
  headerTitle: {
203
280
  flex: 1,
package/src/types.ts CHANGED
@@ -44,13 +44,14 @@ export interface OnairosButtonProps {
44
44
  buttonHeight?: number;
45
45
  hasStroke?: boolean;
46
46
  enabled?: boolean;
47
- buttonForm?: 'default' | 'login' | 'signup';
47
+ buttonForm?: 'default' | 'login' | 'signup' | 'connect';
48
48
  onRejection?: (error?: string) => void;
49
49
  onResolved?: (apiUrl: string, token: string, userData: any) => void;
50
50
  preCheck?: () => Promise<boolean>;
51
51
  color?: string;
52
52
  swerv?: boolean;
53
53
  debug?: boolean;
54
+ darkMode?: boolean;
54
55
  preferredPlatform?: string;
55
56
  testMode?: boolean;
56
57
  }