@onairos/react-native 1.0.3 → 2.0.1

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.
@@ -1,49 +1,79 @@
1
1
  import React, { useState } from 'react';
2
- import { TouchableOpacity, Text, StyleSheet, View } from 'react-native';
2
+ import { TouchableOpacity, Text, StyleSheet, View, Dimensions } from 'react-native';
3
3
  import { UniversalOnboarding } from './UniversalOnboarding';
4
4
  import { COLORS } from '../constants';
5
5
  import type { OnairosButtonProps } from '../types';
6
6
 
7
7
  export const OnairosButton: React.FC<OnairosButtonProps> = ({
8
- title,
9
- onPress,
10
- style,
11
- buttonType = 'default',
12
- buttonForm = 'signup',
8
+ returnLink,
9
+ prefillUrl,
13
10
  AppName,
11
+ buttonType = 'normal',
14
12
  requestData,
15
- returnLink,
16
- onComplete,
17
- embedd = false,
13
+ buttonWidth = 180,
14
+ buttonHeight,
15
+ hasStroke = false,
16
+ enabled = true,
17
+ buttonForm = 'default',
18
+ onRejection,
19
+ onResolved,
20
+ preCheck,
21
+ color,
22
+ swerv = false,
18
23
  debug = false,
19
- test = false,
24
+ preferredPlatform,
25
+ testMode = false,
20
26
  }) => {
21
27
  const [showOnboarding, setShowOnboarding] = useState(false);
22
28
 
23
- const handlePress = () => {
29
+ const handlePress = async () => {
30
+ if (!enabled) return;
31
+
32
+ if (preCheck) {
33
+ const shouldProceed = await preCheck();
34
+ if (!shouldProceed) {
35
+ onRejection?.();
36
+ return;
37
+ }
38
+ }
39
+
24
40
  if (buttonForm === 'signup') {
25
41
  setShowOnboarding(true);
26
42
  } else {
27
- onPress();
43
+ // Handle login flow
44
+ // TODO: Implement login flow according to account.md
28
45
  }
29
46
  };
30
47
 
31
48
  const handleOnboardingComplete = (apiUrl: string, token: string, data: any) => {
32
49
  setShowOnboarding(false);
33
- onComplete(apiUrl, token, data);
50
+ onResolved?.(apiUrl, token, data);
34
51
  };
35
52
 
53
+ const buttonStyle = [
54
+ styles.button,
55
+ buttonType === 'pill' && styles.pillButton,
56
+ hasStroke && styles.strokedButton,
57
+ { width: buttonWidth },
58
+ buttonHeight && { height: buttonHeight },
59
+ color && { backgroundColor: color },
60
+ swerv && styles.swervButton,
61
+ ];
62
+
63
+ const textStyle = [
64
+ styles.buttonText,
65
+ hasStroke && styles.strokedButtonText,
66
+ color && styles.customColorText,
67
+ ];
68
+
36
69
  return (
37
70
  <View>
38
71
  <TouchableOpacity
39
- style={[
40
- styles.button,
41
- buttonType === 'pill' && styles.pillButton,
42
- style,
43
- ]}
72
+ style={buttonStyle}
44
73
  onPress={handlePress}
74
+ disabled={!enabled}
45
75
  >
46
- <Text style={styles.buttonText}>{title}</Text>
76
+ <Text style={textStyle}>{AppName}</Text>
47
77
  </TouchableOpacity>
48
78
 
49
79
  <UniversalOnboarding
@@ -53,9 +83,9 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
53
83
  requestData={requestData}
54
84
  returnLink={returnLink}
55
85
  onComplete={handleOnboardingComplete}
56
- embedd={embedd}
86
+ preferredPlatform={preferredPlatform}
57
87
  debug={debug}
58
- test={test}
88
+ test={testMode}
59
89
  />
60
90
  </View>
61
91
  );
@@ -63,7 +93,7 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
63
93
 
64
94
  const styles = StyleSheet.create({
65
95
  button: {
66
- backgroundColor: COLORS.primary,
96
+ backgroundColor: '#000',
67
97
  paddingVertical: 12,
68
98
  paddingHorizontal: 24,
69
99
  borderRadius: 8,
@@ -73,9 +103,23 @@ const styles = StyleSheet.create({
73
103
  pillButton: {
74
104
  borderRadius: 20,
75
105
  },
106
+ strokedButton: {
107
+ backgroundColor: 'transparent',
108
+ borderWidth: 1,
109
+ borderColor: '#000',
110
+ },
111
+ swervButton: {
112
+ transform: [{ rotate: '-2deg' }],
113
+ },
76
114
  buttonText: {
77
115
  color: '#fff',
78
116
  fontSize: 16,
79
117
  fontWeight: '600',
80
118
  },
119
+ strokedButtonText: {
120
+ color: '#000',
121
+ },
122
+ customColorText: {
123
+ color: '#fff',
124
+ },
81
125
  });
@@ -1,272 +1,285 @@
1
- import React, { useCallback, useEffect, useMemo, useRef } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
5
5
  StyleSheet,
6
6
  TouchableOpacity,
7
7
  ScrollView,
8
+ Alert,
8
9
  Platform,
9
- Dimensions,
10
10
  } from 'react-native';
11
- import BottomSheet from '@gorhom/bottom-sheet';
12
- import Icon from 'react-native-vector-icons/MaterialIcons';
11
+ import { BottomSheetModal, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
13
12
  import { COLORS } from '../constants';
13
+ import { onairosApi } from '../api';
14
+ import { encryptModelKey, getServerPublicKey } from '../utils/encryption';
14
15
 
15
16
  interface OverlayProps {
16
- visible: boolean;
17
- onClose: () => void;
18
- onAccept: () => void;
19
- AppName: string;
20
- requestData: Record<string, Record<string, string>>;
21
- biometricType?: 'FaceID' | 'TouchID' | 'BiometricID';
17
+ data: {
18
+ [key: string]: {
19
+ type: string;
20
+ descriptions: string;
21
+ reward: string;
22
+ };
23
+ };
24
+ username: string;
25
+ modelKey: string;
26
+ onResolved: (apiUrl: string, accessToken: string, loginDetails: any) => void;
22
27
  }
23
28
 
24
- const { height } = Dimensions.get('window');
25
-
26
29
  export const Overlay: React.FC<OverlayProps> = ({
27
- visible,
28
- onClose,
29
- onAccept,
30
- AppName,
31
- requestData,
32
- biometricType = Platform.OS === 'ios' ? 'FaceID' : 'BiometricID',
30
+ data,
31
+ username,
32
+ modelKey,
33
+ onResolved,
33
34
  }) => {
34
- const bottomSheetRef = useRef<BottomSheet>(null);
35
- const snapPoints = useMemo(() => ['85%'], []);
35
+ const [selections, setSelections] = useState<{ [key: string]: boolean }>({});
36
+ const [details, setDetails] = useState<string>('');
37
+ const bottomSheetRef = React.useRef<BottomSheetModal>(null);
36
38
 
37
- // Expand or collapse the bottom sheet based on visibility
38
39
  useEffect(() => {
39
- if (visible) {
40
- bottomSheetRef.current?.expand();
41
- } else {
42
- bottomSheetRef.current?.close();
40
+ // Initialize selection state
41
+ const initialSelections: { [key: string]: boolean } = {};
42
+ Object.keys(data).forEach((key) => {
43
+ initialSelections[key] = false;
44
+ });
45
+ setSelections(initialSelections);
46
+ getDetails();
47
+ }, []);
48
+
49
+ const getDetails = async () => {
50
+ try {
51
+ const response = await onairosApi.post('getAccountInfo', {
52
+ Info: {
53
+ username: username,
54
+ },
55
+ });
56
+ setDetails(response.AccountInfo);
57
+ } catch (e) {
58
+ console.error('Error getting account info:', e);
43
59
  }
44
- }, [visible]);
60
+ };
61
+
62
+ const closeOverlay = () => {
63
+ bottomSheetRef.current?.dismiss();
64
+ };
65
+
66
+ const confirmSelection = async () => {
67
+ try {
68
+ let appId = 'unknown';
69
+ try {
70
+ // Try to get package info, but don't fail if it's not available
71
+ const PackageInfo = require('react-native-package-info');
72
+ const packageInfo = await PackageInfo.getPackageInfo();
73
+ appId = packageInfo.packageName;
74
+ } catch (e) {
75
+ console.warn('Package info not available:', e);
76
+ }
77
+
78
+ const serverPublicKey = await getServerPublicKey();
79
+ const encryptedModelKey = encryptModelKey(serverPublicKey, modelKey);
45
80
 
46
- // Get the icon for the biometric type
47
- const getBiometricIcon = useCallback(() => {
48
- switch (biometricType) {
49
- case 'FaceID':
50
- return 'face';
51
- case 'TouchID':
52
- return 'fingerprint';
53
- default:
54
- return 'security';
81
+ const response = await onairosApi.post('getAPIUrlMobile', {
82
+ Info: {
83
+ storage: 'local',
84
+ appId: appId,
85
+ confirmations: selections,
86
+ developerURL: 'devURL',
87
+ EncryptedUserPin: encryptedModelKey,
88
+ account: username,
89
+ proofMode: false,
90
+ },
91
+ });
92
+
93
+ if (response.apiUrl && response.token) {
94
+ onResolved(response.apiUrl, response.token, { username });
95
+ closeOverlay();
96
+ }
97
+ } catch (e) {
98
+ console.error('Error in confirmSelection:', e);
99
+ showErrorModal('Failed to confirm selection. Please try again.');
55
100
  }
56
- }, [biometricType]);
101
+ };
57
102
 
58
- // Render the requested data sections
59
- const renderDataCategories = () => {
60
- return Object.entries(requestData).map(([category, items]) => (
61
- <View key={category} style={styles.categoryContainer}>
62
- <Text style={styles.categoryTitle}>{category}</Text>
63
-
64
- {Object.entries(items).map(([item, description]) => (
65
- <View key={item} style={styles.itemContainer}>
66
- <Icon name="check-circle" size={20} color={COLORS.primary} />
67
- <View style={styles.itemContent}>
68
- <Text style={styles.itemTitle}>{item}</Text>
69
- <Text style={styles.itemDescription}>{description}</Text>
70
- </View>
71
- </View>
72
- ))}
73
- </View>
74
- ));
103
+ const showErrorModal = (errorMessage: string) => {
104
+ Alert.alert('Notice', errorMessage, [{ text: 'OK' }]);
75
105
  };
76
106
 
107
+ const selectedCount = Object.values(selections).filter(Boolean).length;
108
+
77
109
  return (
78
- <BottomSheet
110
+ <BottomSheetModal
79
111
  ref={bottomSheetRef}
80
- index={visible ? 0 : -1}
81
- snapPoints={snapPoints}
82
- enablePanDownToClose
83
- onClose={onClose}
84
- backdropComponent={({ animatedIndex }) => (
85
- <View
86
- style={[
87
- styles.backdrop,
88
- {
89
- opacity: animatedIndex.interpolate({
90
- inputRange: [0, 1],
91
- outputRange: [0.5, 0],
92
- }),
93
- },
94
- ]}
112
+ snapPoints={['80%']}
113
+ backdropComponent={(props) => (
114
+ <BottomSheetBackdrop
115
+ {...props}
116
+ appearsOnIndex={0}
117
+ disappearsOnIndex={-1}
95
118
  />
96
119
  )}
97
120
  >
98
121
  <View style={styles.container}>
99
122
  <View style={styles.header}>
100
- <Icon name="shield" size={24} color={COLORS.primary} />
101
- <Text style={styles.headerTitle}>Data Request</Text>
102
- <TouchableOpacity onPress={onClose} style={styles.closeButton}>
103
- <Icon name="close" size={24} color="#000" />
123
+ <Text style={styles.headerTitle}>Onairos</Text>
124
+ <TouchableOpacity onPress={closeOverlay}>
125
+ <Text style={styles.closeButton}>×</Text>
104
126
  </TouchableOpacity>
127
+ <Text style={styles.username}>{username}</Text>
105
128
  </View>
106
-
129
+
107
130
  <ScrollView style={styles.content}>
108
- <View style={styles.appInfoContainer}>
109
- <Text style={styles.appName}>{AppName}</Text>
110
- <Text style={styles.requestText}>
111
- is requesting access to the following data:
112
- </Text>
113
- </View>
114
-
115
- {renderDataCategories()}
116
-
117
- <View style={styles.securityNotice}>
118
- <Icon name="security" size={20} color="#666" />
119
- <Text style={styles.securityText}>
120
- Your data is securely processed and never shared with third parties.
121
- </Text>
122
- </View>
131
+ {Object.entries(data).map(([key, value]) => (
132
+ <View key={key} style={styles.card}>
133
+ <TouchableOpacity
134
+ style={styles.checkboxContainer}
135
+ onPress={() =>
136
+ setSelections((prev) => ({
137
+ ...prev,
138
+ [key]: !prev[key],
139
+ }))
140
+ }
141
+ >
142
+ <View
143
+ style={[
144
+ styles.checkbox,
145
+ selections[key] && styles.checkboxSelected,
146
+ ]}
147
+ />
148
+ <View style={styles.cardContent}>
149
+ <Text style={styles.cardTitle}>{value.type} Insight</Text>
150
+ <Text style={styles.cardDescription}>
151
+ Description: {value.descriptions}
152
+ </Text>
153
+ {value.reward && (
154
+ <Text style={styles.cardReward}>
155
+ Reward: {value.reward}
156
+ </Text>
157
+ )}
158
+ </View>
159
+ </TouchableOpacity>
160
+ </View>
161
+ ))}
123
162
  </ScrollView>
124
-
163
+
125
164
  <View style={styles.footer}>
126
165
  <TouchableOpacity
127
- style={styles.cancelButton}
128
- onPress={onClose}
166
+ style={styles.footerButton}
167
+ onPress={closeOverlay}
129
168
  >
130
- <Text style={styles.cancelButtonText}>Deny</Text>
169
+ <Text style={styles.footerButtonText}>Cancel</Text>
131
170
  </TouchableOpacity>
132
-
171
+ <Text style={styles.selectedCount}>Selected: {selectedCount}</Text>
133
172
  <TouchableOpacity
134
- style={styles.acceptButton}
135
- onPress={onAccept}
173
+ style={styles.footerButton}
174
+ onPress={confirmSelection}
136
175
  >
137
- <Text style={styles.acceptButtonText}>Accept</Text>
138
- <Icon name={getBiometricIcon()} size={20} color="#fff" style={styles.buttonIcon} />
176
+ <Text style={styles.footerButtonText}>Confirm</Text>
139
177
  </TouchableOpacity>
140
178
  </View>
141
179
  </View>
142
- </BottomSheet>
180
+ </BottomSheetModal>
143
181
  );
144
182
  };
145
183
 
146
184
  const styles = StyleSheet.create({
147
185
  container: {
148
186
  flex: 1,
149
- backgroundColor: '#fff',
150
- },
151
- backdrop: {
152
- ...StyleSheet.absoluteFillObject,
153
- backgroundColor: '#000',
187
+ backgroundColor: COLORS.white,
188
+ borderTopLeftRadius: 10,
189
+ borderTopRightRadius: 10,
154
190
  },
155
191
  header: {
156
192
  flexDirection: 'row',
157
193
  alignItems: 'center',
194
+ justifyContent: 'space-between',
158
195
  padding: 16,
159
- backgroundColor: COLORS.headerBg,
160
- borderTopLeftRadius: 16,
161
- borderTopRightRadius: 16,
196
+ backgroundColor: COLORS.primary,
197
+ borderTopLeftRadius: 10,
198
+ borderTopRightRadius: 10,
162
199
  },
163
200
  headerTitle: {
164
- flex: 1,
165
- fontSize: 18,
166
- fontWeight: '600',
167
- marginLeft: 12,
168
- color: '#000',
201
+ fontSize: 24,
202
+ color: COLORS.white,
203
+ fontWeight: 'bold',
169
204
  },
170
205
  closeButton: {
206
+ fontSize: 24,
207
+ color: COLORS.white,
171
208
  padding: 8,
172
209
  },
210
+ username: {
211
+ fontSize: 18,
212
+ color: COLORS.primary,
213
+ },
173
214
  content: {
174
215
  flex: 1,
175
216
  padding: 16,
176
217
  },
177
- appInfoContainer: {
178
- marginBottom: 24,
179
- },
180
- appName: {
181
- fontSize: 20,
182
- fontWeight: 'bold',
183
- color: '#000',
184
- marginBottom: 4,
185
- },
186
- requestText: {
187
- fontSize: 16,
188
- color: '#666',
189
- },
190
- categoryContainer: {
191
- marginBottom: 20,
192
- },
193
- categoryTitle: {
194
- fontSize: 18,
195
- fontWeight: '600',
196
- color: '#000',
218
+ card: {
219
+ backgroundColor: COLORS.white,
220
+ borderRadius: 8,
197
221
  marginBottom: 12,
222
+ shadowColor: COLORS.black,
223
+ shadowOffset: { width: 0, height: 2 },
224
+ shadowOpacity: 0.1,
225
+ shadowRadius: 4,
226
+ elevation: 3,
198
227
  },
199
- itemContainer: {
228
+ checkboxContainer: {
200
229
  flexDirection: 'row',
201
- marginBottom: 12,
202
- paddingLeft: 8,
230
+ padding: 16,
231
+ alignItems: 'center',
203
232
  },
204
- itemContent: {
233
+ checkbox: {
234
+ width: 24,
235
+ height: 24,
236
+ borderRadius: 4,
237
+ borderWidth: 2,
238
+ borderColor: COLORS.primary,
239
+ marginRight: 12,
240
+ },
241
+ checkboxSelected: {
242
+ backgroundColor: COLORS.primary,
243
+ },
244
+ cardContent: {
205
245
  flex: 1,
206
- marginLeft: 12,
207
246
  },
208
- itemTitle: {
209
- fontSize: 16,
210
- fontWeight: '500',
211
- color: '#000',
212
- marginBottom: 2,
247
+ cardTitle: {
248
+ fontSize: 18,
249
+ fontWeight: 'bold',
250
+ marginBottom: 4,
213
251
  },
214
- itemDescription: {
252
+ cardDescription: {
215
253
  fontSize: 14,
216
- color: '#666',
217
- },
218
- securityNotice: {
219
- flexDirection: 'row',
220
- alignItems: 'flex-start',
221
- backgroundColor: '#f5f5f5',
222
- padding: 12,
223
- borderRadius: 8,
224
- marginBottom: 24,
254
+ color: COLORS.gray,
255
+ marginBottom: 4,
225
256
  },
226
- securityText: {
227
- flex: 1,
257
+ cardReward: {
228
258
  fontSize: 14,
229
- color: '#666',
230
- marginLeft: 8,
259
+ color: COLORS.success,
260
+ fontWeight: 'bold',
231
261
  },
232
262
  footer: {
233
263
  flexDirection: 'row',
264
+ justifyContent: 'space-between',
265
+ alignItems: 'center',
234
266
  padding: 16,
235
267
  borderTopWidth: 1,
236
- borderTopColor: '#eee',
237
- },
238
- cancelButton: {
239
- flex: 1,
240
- justifyContent: 'center',
241
- alignItems: 'center',
242
- paddingVertical: 12,
243
- borderWidth: 1,
244
- borderColor: '#ddd',
245
- borderRadius: 25,
246
- marginRight: 8,
268
+ borderTopColor: COLORS.lightGray,
247
269
  },
248
- cancelButtonText: {
249
- fontSize: 16,
250
- fontWeight: '600',
251
- color: '#666',
252
- },
253
- acceptButton: {
254
- flex: 2,
255
- flexDirection: 'row',
256
- justifyContent: 'center',
257
- alignItems: 'center',
258
- paddingVertical: 12,
270
+ footerButton: {
271
+ paddingVertical: 8,
272
+ paddingHorizontal: 16,
273
+ borderRadius: 8,
259
274
  backgroundColor: COLORS.primary,
260
- borderRadius: 25,
261
- marginLeft: 8,
262
275
  },
263
- acceptButtonText: {
276
+ footerButtonText: {
277
+ color: COLORS.white,
264
278
  fontSize: 16,
265
279
  fontWeight: '600',
266
- color: '#fff',
267
- marginRight: 8,
268
280
  },
269
- buttonIcon: {
270
- marginLeft: 4,
281
+ selectedCount: {
282
+ fontSize: 16,
283
+ color: COLORS.gray,
271
284
  },
272
285
  });
@@ -1,19 +1,23 @@
1
1
  import type { PlatformConfig } from '../types';
2
2
 
3
3
  export const COLORS = {
4
- primary: '#00BFA5',
5
- headerBg: '#E0F2F1',
4
+ primary: '#1BA9D4',
5
+ headerBg: '#F8F9FA',
6
6
  text: {
7
7
  primary: '#000000',
8
- secondary: '#757575',
8
+ secondary: '#666666',
9
9
  },
10
- border: '#EEEEEE',
11
- success: '#4CAF50',
12
- error: '#F44336',
13
- instagram: '#E4405F',
14
- pinterest: '#BD081C',
10
+ border: '#E5E5E5',
11
+ success: '#34C759',
12
+ error: '#FF3B30',
13
+ instagram: '#E1306C',
14
+ pinterest: '#E60023',
15
15
  reddit: '#FF4500',
16
16
  youtube: '#FF0000',
17
+ white: '#FFFFFF',
18
+ black: '#000000',
19
+ gray: '#666666',
20
+ lightGray: '#E5E5E5',
17
21
  };
18
22
 
19
23
  export const PLATFORMS: Record<string, PlatformConfig> = {