@onairos/react-native 3.0.9 → 3.0.11

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.png +0 -0
  2. package/lib/commonjs/components/Onairos.js +206 -60
  3. package/lib/commonjs/components/Onairos.js.map +1 -1
  4. package/lib/commonjs/components/OnairosButton.js +97 -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.png +0 -0
  9. package/lib/module/components/Onairos.js +209 -62
  10. package/lib/module/components/Onairos.js.map +1 -1
  11. package/lib/module/components/OnairosButton.js +99 -44
  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 +4 -3
  25. package/src/assets/images/onairos_logo.png +0 -0
  26. package/src/components/Onairos.tsx +273 -88
  27. package/src/components/OnairosButton.tsx +102 -46
  28. package/src/components/UniversalOnboarding.tsx +105 -28
  29. package/src/types.ts +2 -1
@@ -1,14 +1,13 @@
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
10
+ Image,
12
11
  } from 'react-native';
13
12
  import { UniversalOnboarding } from './UniversalOnboarding';
14
13
  import { Overlay } from './Overlay';
@@ -18,18 +17,23 @@ import { hasCredentials, getCredentials, deleteCredentials as clearCredentials }
18
17
  import { onairosApi } from '../api';
19
18
  import { Portal } from '../utils/Portal';
20
19
 
20
+ export interface OnairosButtonRef {
21
+ trigger: () => Promise<void>;
22
+ reset: () => Promise<void>;
23
+ }
24
+
21
25
  /**
22
26
  * OnairosButton Component - A sign-in button similar to Google/Apple sign-in
23
27
  */
24
- export const OnairosButton: React.FC<OnairosButtonProps> = ({
28
+ export const OnairosButton = forwardRef<OnairosButtonRef, OnairosButtonProps>(({
25
29
  returnLink,
26
30
  prefillUrl,
27
31
  AppName,
28
32
  buttonType = 'normal',
29
33
  requestData,
30
- buttonWidth = 240,
34
+ buttonWidth = 180,
31
35
  buttonHeight = 48,
32
- hasStroke = true,
36
+ hasStroke = false,
33
37
  enabled = true,
34
38
  buttonForm = 'default',
35
39
  onRejection,
@@ -38,15 +42,64 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
38
42
  color,
39
43
  swerv = false,
40
44
  debug = false,
45
+ darkMode = false,
41
46
  preferredPlatform,
42
47
  testMode = false,
43
- }) => {
48
+ }, ref) => {
44
49
  const [showOnboarding, setShowOnboarding] = useState(false);
45
50
  const [showOverlay, setShowOverlay] = useState(false);
46
51
  const [storedCredentials, setStoredCredentials] = useState<any>(null);
47
52
  const [isLoading, setIsLoading] = useState(false);
53
+ const [isPressed, setIsPressed] = useState(false);
54
+
55
+ // Expose methods via ref
56
+ useImperativeHandle(ref, () => ({
57
+ trigger: async () => {
58
+ await handlePress();
59
+ },
60
+ reset: async () => {
61
+ await clearCredentials();
62
+ }
63
+ }));
64
+
65
+ // Compute button text based on buttonForm
66
+ const getButtonText = () => {
67
+ if (buttonForm === 'connect' || buttonForm === 'signup') {
68
+ return 'Connect with Onairos';
69
+ } else {
70
+ return 'Sign in with Onairos';
71
+ }
72
+ };
73
+
74
+ // Calculate background color based on props and state
75
+ const getBackgroundColor = (): string => {
76
+ if (!enabled) {
77
+ return darkMode ? '#3A3A3A' : '#e0e0e0';
78
+ }
79
+
80
+ if (isPressed) {
81
+ return color ?
82
+ (typeof color === 'string' ? `${color}80` : color) :
83
+ (darkMode ? '#32323280' : '#f5f5f580');
84
+ }
85
+
86
+ return color || (darkMode ? '#2A2A2A' : '#ffffff');
87
+ };
48
88
 
49
- const isDarkMode = color === 'black' || (!color && !hasStroke);
89
+ // Calculate text color based on background luminance
90
+ const getTextColor = (): string => {
91
+ if (!enabled) {
92
+ return darkMode ? '#777777' : '#AAAAAA';
93
+ }
94
+
95
+ if (darkMode) {
96
+ return '#FFFFFF';
97
+ }
98
+
99
+ const bgColor = getBackgroundColor();
100
+ // Simple luminance check
101
+ return bgColor === '#ffffff' || bgColor === '#f5f5f580' || bgColor.includes('#f') ? '#000000' : '#FFFFFF';
102
+ };
50
103
 
51
104
  const handlePress = async () => {
52
105
  if (!enabled || isLoading) return;
@@ -79,7 +132,7 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
79
132
  }
80
133
 
81
134
  try {
82
- // Validate credentials with server - catch errors here to prevent crashing
135
+ // Validate credentials with server
83
136
  const isValid = await onairosApi.validateCredentials(credentials.username);
84
137
 
85
138
  if (!isValid) {
@@ -132,20 +185,16 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
132
185
  hasStroke && styles.strokedButton,
133
186
  {
134
187
  width: buttonWidth,
135
- height: buttonHeight
188
+ height: buttonHeight,
189
+ backgroundColor: getBackgroundColor(),
190
+ borderColor: darkMode ? '#555555' : '#000000',
136
191
  },
137
- color ? { backgroundColor: color } : null,
138
- isDarkMode ? styles.darkButton : styles.lightButton,
139
192
  swerv && styles.swervButton,
140
193
  !enabled && styles.disabledButton
141
194
  ].filter(Boolean) as ViewStyle[];
142
195
 
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[];
196
+ // Calculate text color
197
+ const textColor = getTextColor();
149
198
 
150
199
  // Render components
151
200
  return (
@@ -155,17 +204,25 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
155
204
  onPress={handlePress}
156
205
  disabled={!enabled || isLoading}
157
206
  accessibilityLabel={`Sign in with Onairos`}
207
+ onPressIn={() => setIsPressed(true)}
208
+ onPressOut={() => setIsPressed(false)}
158
209
  >
159
210
  {isLoading ? (
160
211
  <ActivityIndicator
161
212
  size="small"
162
- color={isDarkMode ? '#fff' : '#000'}
213
+ color={textColor}
163
214
  />
164
215
  ) : (
165
- <>
166
- {/* Optional Onairos logo could be added here */}
167
- <Text style={textStyle}>Sign in with Onairos</Text>
168
- </>
216
+ <View style={styles.buttonContent}>
217
+ <Image
218
+ source={require('../assets/images/onairos_logo.png')}
219
+ style={styles.logo}
220
+ resizeMode="contain"
221
+ />
222
+ <Text style={[styles.buttonText, { color: textColor }]}>
223
+ {getButtonText()}
224
+ </Text>
225
+ </View>
169
226
  )}
170
227
  </TouchableOpacity>
171
228
 
@@ -178,11 +235,12 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
178
235
  onRejection?.('User closed onboarding');
179
236
  }}
180
237
  AppName={AppName}
181
- requestData={requestData}
182
- returnLink={returnLink}
238
+ requestData={requestData as any}
239
+ returnLink={returnLink || ''}
183
240
  onComplete={handleOnboardingComplete}
184
241
  debug={debug}
185
242
  test={testMode}
243
+ preferredPlatform={preferredPlatform}
186
244
  />
187
245
  )}
188
246
 
@@ -195,12 +253,13 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
195
253
  modelKey={storedCredentials.userPin || ''}
196
254
  onResolved={handleOverlayResolved}
197
255
  appName={AppName}
256
+ darkMode={darkMode}
198
257
  />
199
258
  </Portal>
200
259
  )}
201
260
  </>
202
261
  );
203
- };
262
+ });
204
263
 
205
264
  const styles = StyleSheet.create({
206
265
  button: {
@@ -209,7 +268,22 @@ const styles = StyleSheet.create({
209
268
  justifyContent: 'center',
210
269
  paddingVertical: 12,
211
270
  paddingHorizontal: 16,
212
- borderRadius: 4,
271
+ borderRadius: 8,
272
+ shadowColor: '#000',
273
+ shadowOffset: { width: 0, height: 2 },
274
+ shadowOpacity: 0.1,
275
+ shadowRadius: 4,
276
+ elevation: 2,
277
+ },
278
+ buttonContent: {
279
+ flexDirection: 'row',
280
+ alignItems: 'center',
281
+ justifyContent: 'center',
282
+ },
283
+ logo: {
284
+ width: 24,
285
+ height: 24,
286
+ marginRight: 8,
213
287
  },
214
288
  pillButton: {
215
289
  borderRadius: 24,
@@ -217,19 +291,10 @@ const styles = StyleSheet.create({
217
291
  strokedButton: {
218
292
  backgroundColor: 'transparent',
219
293
  borderWidth: 1,
220
- borderColor: '#000',
221
294
  },
222
295
  swervButton: {
223
296
  transform: [{ rotate: '-2deg' }],
224
297
  },
225
- darkButton: {
226
- backgroundColor: '#000',
227
- borderColor: '#000',
228
- },
229
- lightButton: {
230
- backgroundColor: '#fff',
231
- borderColor: '#000',
232
- },
233
298
  disabledButton: {
234
299
  opacity: 0.6,
235
300
  },
@@ -238,13 +303,4 @@ const styles = StyleSheet.create({
238
303
  fontWeight: '600',
239
304
  textAlign: 'center',
240
305
  },
241
- lightText: {
242
- color: '#fff',
243
- },
244
- darkText: {
245
- color: '#000',
246
- },
247
- disabledText: {
248
- opacity: 0.7,
249
- },
250
306
  });
@@ -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
  }