@onairos/react-native 1.0.0

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 (130) hide show
  1. package/README.md +334 -0
  2. package/lib/commonjs/components/DataRequestModal.js +176 -0
  3. package/lib/commonjs/components/DataRequestModal.js.map +1 -0
  4. package/lib/commonjs/components/Notification.js +106 -0
  5. package/lib/commonjs/components/Notification.js.map +1 -0
  6. package/lib/commonjs/components/OnairosButton.js +575 -0
  7. package/lib/commonjs/components/OnairosButton.js.map +1 -0
  8. package/lib/commonjs/components/Overlay.js +818 -0
  9. package/lib/commonjs/components/Overlay.js.map +1 -0
  10. package/lib/commonjs/components/UniversalOnboarding.js +173 -0
  11. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -0
  12. package/lib/commonjs/components/onboarding/OAuthWebView.js +137 -0
  13. package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -0
  14. package/lib/commonjs/components/onboarding/OnboardingHeader.js +74 -0
  15. package/lib/commonjs/components/onboarding/OnboardingHeader.js.map +1 -0
  16. package/lib/commonjs/components/onboarding/PinInput.js +283 -0
  17. package/lib/commonjs/components/onboarding/PinInput.js.map +1 -0
  18. package/lib/commonjs/components/onboarding/PlatformConnector.js +244 -0
  19. package/lib/commonjs/components/onboarding/PlatformConnector.js.map +1 -0
  20. package/lib/commonjs/components/screens/ConnectorScreen.js +145 -0
  21. package/lib/commonjs/components/screens/ConnectorScreen.js.map +1 -0
  22. package/lib/commonjs/components/screens/LoadingScreen.js +91 -0
  23. package/lib/commonjs/components/screens/LoadingScreen.js.map +1 -0
  24. package/lib/commonjs/components/screens/PinCreationScreen.js +61 -0
  25. package/lib/commonjs/components/screens/PinCreationScreen.js.map +1 -0
  26. package/lib/commonjs/constants/index.js +78 -0
  27. package/lib/commonjs/constants/index.js.map +1 -0
  28. package/lib/commonjs/hooks/useConnections.js +89 -0
  29. package/lib/commonjs/hooks/useConnections.js.map +1 -0
  30. package/lib/commonjs/hooks/useCredentials.js +85 -0
  31. package/lib/commonjs/hooks/useCredentials.js.map +1 -0
  32. package/lib/commonjs/index.js +282 -0
  33. package/lib/commonjs/index.js.map +1 -0
  34. package/lib/commonjs/services/oauthService.js +362 -0
  35. package/lib/commonjs/services/oauthService.js.map +1 -0
  36. package/lib/commonjs/types/declarations.d.js +2 -0
  37. package/lib/commonjs/types/declarations.d.js.map +1 -0
  38. package/lib/commonjs/types/index.js +2 -0
  39. package/lib/commonjs/types/index.js.map +1 -0
  40. package/lib/commonjs/utils/api.js +129 -0
  41. package/lib/commonjs/utils/api.js.map +1 -0
  42. package/lib/commonjs/utils/auth.js +111 -0
  43. package/lib/commonjs/utils/auth.js.map +1 -0
  44. package/lib/commonjs/utils/crypto.js +62 -0
  45. package/lib/commonjs/utils/crypto.js.map +1 -0
  46. package/lib/commonjs/utils/debugHelper.js +64 -0
  47. package/lib/commonjs/utils/debugHelper.js.map +1 -0
  48. package/lib/commonjs/utils/onairosApi.js +270 -0
  49. package/lib/commonjs/utils/onairosApi.js.map +1 -0
  50. package/lib/commonjs/utils/secureStorage.js +210 -0
  51. package/lib/commonjs/utils/secureStorage.js.map +1 -0
  52. package/lib/module/components/DataRequestModal.js +168 -0
  53. package/lib/module/components/DataRequestModal.js.map +1 -0
  54. package/lib/module/components/Notification.js +99 -0
  55. package/lib/module/components/Notification.js.map +1 -0
  56. package/lib/module/components/OnairosButton.js +550 -0
  57. package/lib/module/components/OnairosButton.js.map +1 -0
  58. package/lib/module/components/Overlay.js +825 -0
  59. package/lib/module/components/Overlay.js.map +1 -0
  60. package/lib/module/components/UniversalOnboarding.js +164 -0
  61. package/lib/module/components/UniversalOnboarding.js.map +1 -0
  62. package/lib/module/components/onboarding/OAuthWebView.js +128 -0
  63. package/lib/module/components/onboarding/OAuthWebView.js.map +1 -0
  64. package/lib/module/components/onboarding/OnboardingHeader.js +66 -0
  65. package/lib/module/components/onboarding/OnboardingHeader.js.map +1 -0
  66. package/lib/module/components/onboarding/PinInput.js +274 -0
  67. package/lib/module/components/onboarding/PinInput.js.map +1 -0
  68. package/lib/module/components/onboarding/PlatformConnector.js +235 -0
  69. package/lib/module/components/onboarding/PlatformConnector.js.map +1 -0
  70. package/lib/module/components/screens/ConnectorScreen.js +137 -0
  71. package/lib/module/components/screens/ConnectorScreen.js.map +1 -0
  72. package/lib/module/components/screens/LoadingScreen.js +83 -0
  73. package/lib/module/components/screens/LoadingScreen.js.map +1 -0
  74. package/lib/module/components/screens/PinCreationScreen.js +53 -0
  75. package/lib/module/components/screens/PinCreationScreen.js.map +1 -0
  76. package/lib/module/constants/index.js +72 -0
  77. package/lib/module/constants/index.js.map +1 -0
  78. package/lib/module/hooks/useConnections.js +81 -0
  79. package/lib/module/hooks/useConnections.js.map +1 -0
  80. package/lib/module/hooks/useCredentials.js +77 -0
  81. package/lib/module/hooks/useCredentials.js.map +1 -0
  82. package/lib/module/index.js +34 -0
  83. package/lib/module/index.js.map +1 -0
  84. package/lib/module/services/oauthService.js +352 -0
  85. package/lib/module/services/oauthService.js.map +1 -0
  86. package/lib/module/types/declarations.d.js +2 -0
  87. package/lib/module/types/declarations.d.js.map +1 -0
  88. package/lib/module/types/index.js +2 -0
  89. package/lib/module/types/index.js.map +1 -0
  90. package/lib/module/utils/api.js +117 -0
  91. package/lib/module/utils/api.js.map +1 -0
  92. package/lib/module/utils/auth.js +99 -0
  93. package/lib/module/utils/auth.js.map +1 -0
  94. package/lib/module/utils/crypto.js +54 -0
  95. package/lib/module/utils/crypto.js.map +1 -0
  96. package/lib/module/utils/debugHelper.js +54 -0
  97. package/lib/module/utils/debugHelper.js.map +1 -0
  98. package/lib/module/utils/onairosApi.js +256 -0
  99. package/lib/module/utils/onairosApi.js.map +1 -0
  100. package/lib/module/utils/secureStorage.js +196 -0
  101. package/lib/module/utils/secureStorage.js.map +1 -0
  102. package/package.json +115 -0
  103. package/src/components/DataRequestModal.tsx +187 -0
  104. package/src/components/Notification.js +101 -0
  105. package/src/components/OnairosButton.js +604 -0
  106. package/src/components/OnairosButton.tsx +182 -0
  107. package/src/components/Overlay.js +854 -0
  108. package/src/components/Overlay.tsx +272 -0
  109. package/src/components/UniversalOnboarding.tsx +184 -0
  110. package/src/components/onboarding/OAuthWebView.tsx +134 -0
  111. package/src/components/onboarding/OnboardingHeader.tsx +70 -0
  112. package/src/components/onboarding/PinInput.tsx +356 -0
  113. package/src/components/onboarding/PlatformConnector.tsx +297 -0
  114. package/src/components/screens/ConnectorScreen.tsx +152 -0
  115. package/src/components/screens/LoadingScreen.tsx +100 -0
  116. package/src/components/screens/PinCreationScreen.tsx +67 -0
  117. package/src/constants/index.ts +78 -0
  118. package/src/hooks/useConnections.ts +90 -0
  119. package/src/hooks/useCredentials.ts +83 -0
  120. package/src/index.js +14 -0
  121. package/src/index.ts +82 -0
  122. package/src/services/oauthService.ts +360 -0
  123. package/src/types/declarations.d.ts +26 -0
  124. package/src/types/index.ts +82 -0
  125. package/src/utils/api.js +112 -0
  126. package/src/utils/auth.js +104 -0
  127. package/src/utils/crypto.js +60 -0
  128. package/src/utils/debugHelper.ts +53 -0
  129. package/src/utils/onairosApi.ts +303 -0
  130. package/src/utils/secureStorage.ts +230 -0
@@ -0,0 +1,272 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ Platform,
9
+ Dimensions,
10
+ } from 'react-native';
11
+ import BottomSheet from '@gorhom/bottom-sheet';
12
+ import Icon from 'react-native-vector-icons/MaterialIcons';
13
+ import { COLORS } from '../constants';
14
+
15
+ 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';
22
+ }
23
+
24
+ const { height } = Dimensions.get('window');
25
+
26
+ export const Overlay: React.FC<OverlayProps> = ({
27
+ visible,
28
+ onClose,
29
+ onAccept,
30
+ AppName,
31
+ requestData,
32
+ biometricType = Platform.OS === 'ios' ? 'FaceID' : 'BiometricID',
33
+ }) => {
34
+ const bottomSheetRef = useRef<BottomSheet>(null);
35
+ const snapPoints = useMemo(() => ['85%'], []);
36
+
37
+ // Expand or collapse the bottom sheet based on visibility
38
+ useEffect(() => {
39
+ if (visible) {
40
+ bottomSheetRef.current?.expand();
41
+ } else {
42
+ bottomSheetRef.current?.close();
43
+ }
44
+ }, [visible]);
45
+
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';
55
+ }
56
+ }, [biometricType]);
57
+
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
+ ));
75
+ };
76
+
77
+ return (
78
+ <BottomSheet
79
+ 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
+ ]}
95
+ />
96
+ )}
97
+ >
98
+ <View style={styles.container}>
99
+ <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" />
104
+ </TouchableOpacity>
105
+ </View>
106
+
107
+ <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>
123
+ </ScrollView>
124
+
125
+ <View style={styles.footer}>
126
+ <TouchableOpacity
127
+ style={styles.cancelButton}
128
+ onPress={onClose}
129
+ >
130
+ <Text style={styles.cancelButtonText}>Deny</Text>
131
+ </TouchableOpacity>
132
+
133
+ <TouchableOpacity
134
+ style={styles.acceptButton}
135
+ onPress={onAccept}
136
+ >
137
+ <Text style={styles.acceptButtonText}>Accept</Text>
138
+ <Icon name={getBiometricIcon()} size={20} color="#fff" style={styles.buttonIcon} />
139
+ </TouchableOpacity>
140
+ </View>
141
+ </View>
142
+ </BottomSheet>
143
+ );
144
+ };
145
+
146
+ const styles = StyleSheet.create({
147
+ container: {
148
+ flex: 1,
149
+ backgroundColor: '#fff',
150
+ },
151
+ backdrop: {
152
+ ...StyleSheet.absoluteFillObject,
153
+ backgroundColor: '#000',
154
+ },
155
+ header: {
156
+ flexDirection: 'row',
157
+ alignItems: 'center',
158
+ padding: 16,
159
+ backgroundColor: COLORS.headerBg,
160
+ borderTopLeftRadius: 16,
161
+ borderTopRightRadius: 16,
162
+ },
163
+ headerTitle: {
164
+ flex: 1,
165
+ fontSize: 18,
166
+ fontWeight: '600',
167
+ marginLeft: 12,
168
+ color: '#000',
169
+ },
170
+ closeButton: {
171
+ padding: 8,
172
+ },
173
+ content: {
174
+ flex: 1,
175
+ padding: 16,
176
+ },
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',
197
+ marginBottom: 12,
198
+ },
199
+ itemContainer: {
200
+ flexDirection: 'row',
201
+ marginBottom: 12,
202
+ paddingLeft: 8,
203
+ },
204
+ itemContent: {
205
+ flex: 1,
206
+ marginLeft: 12,
207
+ },
208
+ itemTitle: {
209
+ fontSize: 16,
210
+ fontWeight: '500',
211
+ color: '#000',
212
+ marginBottom: 2,
213
+ },
214
+ itemDescription: {
215
+ 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,
225
+ },
226
+ securityText: {
227
+ flex: 1,
228
+ fontSize: 14,
229
+ color: '#666',
230
+ marginLeft: 8,
231
+ },
232
+ footer: {
233
+ flexDirection: 'row',
234
+ padding: 16,
235
+ 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,
247
+ },
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,
259
+ backgroundColor: COLORS.primary,
260
+ borderRadius: 25,
261
+ marginLeft: 8,
262
+ },
263
+ acceptButtonText: {
264
+ fontSize: 16,
265
+ fontWeight: '600',
266
+ color: '#fff',
267
+ marginRight: 8,
268
+ },
269
+ buttonIcon: {
270
+ marginLeft: 4,
271
+ },
272
+ });
@@ -0,0 +1,184 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ActivityIndicator,
8
+ Dimensions,
9
+ } from 'react-native';
10
+ import BottomSheet from '@gorhom/bottom-sheet';
11
+ import Icon from 'react-native-vector-icons/MaterialIcons';
12
+ import { PlatformList } from './PlatformList';
13
+ import { PinInput } from './PinInput';
14
+ import { TrainingModal } from './TrainingModal';
15
+ import { useConnections } from '../hooks/useConnections';
16
+ import { COLORS } from '../constants';
17
+ import type { UniversalOnboardingProps, ConnectionStatus } from '../types';
18
+
19
+ const { height } = Dimensions.get('window');
20
+
21
+ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
22
+ visible,
23
+ onClose,
24
+ AppName,
25
+ requestData,
26
+ returnLink,
27
+ onComplete,
28
+ embedd,
29
+ debug,
30
+ test,
31
+ }) => {
32
+ const bottomSheetRef = useRef<BottomSheet>(null);
33
+ const [step, setStep] = useState<'connect' | 'pin' | 'training'>('connect');
34
+ const [connections, setConnections] = useState<ConnectionStatus>({});
35
+ const [pin, setPin] = useState<string>('');
36
+ const [training, setTraining] = useState<{
37
+ progress: number;
38
+ eta: string;
39
+ }>({ progress: 0, eta: '' });
40
+
41
+ const {
42
+ connectPlatform,
43
+ disconnectPlatform,
44
+ getConnectionStatus,
45
+ isConnecting,
46
+ } = useConnections();
47
+
48
+ const snapPoints = useMemo(() => ['90%'], []);
49
+
50
+ useEffect(() => {
51
+ if (visible) {
52
+ bottomSheetRef.current?.expand();
53
+ loadInitialStatus();
54
+ } else {
55
+ bottomSheetRef.current?.close();
56
+ }
57
+ }, [visible]);
58
+
59
+ const loadInitialStatus = useCallback(async () => {
60
+ const status = await getConnectionStatus();
61
+ setConnections(status);
62
+ }, [getConnectionStatus]);
63
+
64
+ const handlePlatformToggle = useCallback(async (platform: string, connect: boolean) => {
65
+ try {
66
+ if (connect) {
67
+ await connectPlatform(platform);
68
+ } else {
69
+ await disconnectPlatform(platform);
70
+ }
71
+ await loadInitialStatus();
72
+ } catch (error) {
73
+ console.error(`Error toggling ${platform}:`, error);
74
+ }
75
+ }, [connectPlatform, disconnectPlatform, loadInitialStatus]);
76
+
77
+ const handlePinSubmit = useCallback(async (userPin: string) => {
78
+ setPin(userPin);
79
+ setStep('training');
80
+ // Simulate training progress
81
+ let progress = 0;
82
+ const interval = setInterval(() => {
83
+ progress += 0.1;
84
+ setTraining({
85
+ progress,
86
+ eta: `${Math.round((1 - progress) * 100)} seconds remaining`,
87
+ });
88
+ if (progress >= 1) {
89
+ clearInterval(interval);
90
+ onComplete('https://api2.onairos.uk', 'dummy-token', {
91
+ pin: userPin,
92
+ connections,
93
+ });
94
+ }
95
+ }, 1000);
96
+ }, [connections, onComplete]);
97
+
98
+ const canProceedToPin = useMemo(() => {
99
+ const connectedPlatforms = Object.values(connections).filter(Boolean).length;
100
+ return connectedPlatforms >= 2;
101
+ }, [connections]);
102
+
103
+ const renderHeader = () => (
104
+ <View style={styles.header}>
105
+ <Icon name="auto_awesome" size={24} color={COLORS.primary} />
106
+ <Text style={styles.headerTitle}>Connect with Onairos</Text>
107
+ <TouchableOpacity onPress={onClose} style={styles.closeButton}>
108
+ <Icon name="close" size={24} color="#000" />
109
+ </TouchableOpacity>
110
+ </View>
111
+ );
112
+
113
+ const renderContent = () => {
114
+ switch (step) {
115
+ case 'connect':
116
+ return (
117
+ <PlatformList
118
+ connections={connections}
119
+ onToggle={handlePlatformToggle}
120
+ isLoading={isConnecting}
121
+ canProceed={canProceedToPin}
122
+ onProceed={() => setStep('pin')}
123
+ />
124
+ );
125
+ case 'pin':
126
+ return (
127
+ <PinInput
128
+ onSubmit={handlePinSubmit}
129
+ minLength={8}
130
+ requireSpecialChar
131
+ requireNumber
132
+ />
133
+ );
134
+ case 'training':
135
+ return (
136
+ <TrainingModal
137
+ progress={training.progress}
138
+ eta={training.eta}
139
+ onCancel={onClose}
140
+ />
141
+ );
142
+ }
143
+ };
144
+
145
+ return (
146
+ <BottomSheet
147
+ ref={bottomSheetRef}
148
+ snapPoints={snapPoints}
149
+ enablePanDownToClose
150
+ onClose={onClose}
151
+ index={visible ? 0 : -1}
152
+ >
153
+ <View style={styles.container}>
154
+ {renderHeader()}
155
+ {renderContent()}
156
+ </View>
157
+ </BottomSheet>
158
+ );
159
+ };
160
+
161
+ const styles = StyleSheet.create({
162
+ container: {
163
+ flex: 1,
164
+ backgroundColor: '#fff',
165
+ },
166
+ header: {
167
+ flexDirection: 'row',
168
+ alignItems: 'center',
169
+ padding: 16,
170
+ backgroundColor: COLORS.headerBg,
171
+ borderTopLeftRadius: 16,
172
+ borderTopRightRadius: 16,
173
+ },
174
+ headerTitle: {
175
+ flex: 1,
176
+ fontSize: 18,
177
+ fontWeight: '600',
178
+ marginLeft: 12,
179
+ color: '#000',
180
+ },
181
+ closeButton: {
182
+ padding: 8,
183
+ },
184
+ });
@@ -0,0 +1,134 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { View, StyleSheet, ActivityIndicator, SafeAreaView, TouchableOpacity } from 'react-native';
3
+ import { WebView, WebViewNavigation } from 'react-native-webview';
4
+ import Icon from 'react-native-vector-icons/MaterialIcons';
5
+ import { COLORS } from '../../constants';
6
+ import type { OAuthWebViewProps } from '../../types';
7
+
8
+ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
9
+ url,
10
+ platform,
11
+ onComplete,
12
+ onClose,
13
+ }) => {
14
+ const [loading, setLoading] = useState(true);
15
+
16
+ const handleNavigationStateChange = useCallback(
17
+ (navState: WebViewNavigation) => {
18
+ // Check if the URL includes our redirect URI
19
+ if (navState.url.startsWith('onairosreact://auth/')) {
20
+ // OAuth flow is complete - callback will be handled by the deep link handler
21
+ onComplete();
22
+ return;
23
+ }
24
+ },
25
+ [onComplete]
26
+ );
27
+
28
+ const handleLoadEnd = useCallback(() => {
29
+ setLoading(false);
30
+ }, []);
31
+
32
+ return (
33
+ <SafeAreaView style={styles.container}>
34
+ <View style={styles.header}>
35
+ <TouchableOpacity onPress={onClose} style={styles.closeButton}>
36
+ <Icon name="close" size={24} color="#000" />
37
+ </TouchableOpacity>
38
+ <View style={styles.titleContainer}>
39
+ <Icon
40
+ name={getPlatformIcon(platform)}
41
+ size={20}
42
+ color={getPlatformColor(platform)}
43
+ />
44
+ </View>
45
+ </View>
46
+
47
+ <WebView
48
+ source={{ uri: url }}
49
+ onNavigationStateChange={handleNavigationStateChange}
50
+ onLoadEnd={handleLoadEnd}
51
+ startInLoadingState={true}
52
+ renderLoading={() => null} // We'll handle our own loading UI
53
+ style={styles.webView}
54
+ />
55
+
56
+ {loading && (
57
+ <View style={styles.loadingContainer}>
58
+ <ActivityIndicator size="large" color={COLORS.primary} />
59
+ </View>
60
+ )}
61
+ </SafeAreaView>
62
+ );
63
+ };
64
+
65
+ /**
66
+ * Get platform-specific icon
67
+ */
68
+ const getPlatformIcon = (platform: string): string => {
69
+ switch (platform) {
70
+ case 'instagram':
71
+ return 'photo-camera';
72
+ case 'youtube':
73
+ return 'smart-display';
74
+ case 'pinterest':
75
+ return 'push-pin';
76
+ case 'reddit':
77
+ return 'forum';
78
+ default:
79
+ return 'link';
80
+ }
81
+ };
82
+
83
+ /**
84
+ * Get platform-specific color
85
+ */
86
+ const getPlatformColor = (platform: string): string => {
87
+ switch (platform) {
88
+ case 'instagram':
89
+ return '#E1306C';
90
+ case 'youtube':
91
+ return '#FF0000';
92
+ case 'pinterest':
93
+ return '#E60023';
94
+ case 'reddit':
95
+ return '#FF4500';
96
+ default:
97
+ return COLORS.primary;
98
+ }
99
+ };
100
+
101
+ const styles = StyleSheet.create({
102
+ container: {
103
+ flex: 1,
104
+ backgroundColor: '#fff',
105
+ },
106
+ header: {
107
+ flexDirection: 'row',
108
+ alignItems: 'center',
109
+ height: 56,
110
+ backgroundColor: '#f8f8f8',
111
+ borderBottomWidth: 1,
112
+ borderBottomColor: '#e0e0e0',
113
+ paddingHorizontal: 16,
114
+ },
115
+ titleContainer: {
116
+ flex: 1,
117
+ alignItems: 'center',
118
+ },
119
+ closeButton: {
120
+ padding: 8,
121
+ position: 'absolute',
122
+ left: 16,
123
+ zIndex: 10,
124
+ },
125
+ webView: {
126
+ flex: 1,
127
+ },
128
+ loadingContainer: {
129
+ ...StyleSheet.absoluteFillObject,
130
+ backgroundColor: 'rgba(255, 255, 255, 0.8)',
131
+ alignItems: 'center',
132
+ justifyContent: 'center',
133
+ },
134
+ });
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
3
+ import Icon from 'react-native-vector-icons/MaterialIcons';
4
+ import { COLORS } from '../../constants';
5
+
6
+ interface OnboardingHeaderProps {
7
+ title: string;
8
+ subtitle?: string;
9
+ showBackButton?: boolean;
10
+ onBack?: () => void;
11
+ onClose: () => void;
12
+ }
13
+
14
+ export const OnboardingHeader: React.FC<OnboardingHeaderProps> = ({
15
+ title,
16
+ subtitle,
17
+ showBackButton = false,
18
+ onBack,
19
+ onClose,
20
+ }) => {
21
+ return (
22
+ <View style={styles.header}>
23
+ {showBackButton && (
24
+ <TouchableOpacity style={styles.backButton} onPress={onBack}>
25
+ <Icon name="arrow-back" size={24} color="#000" />
26
+ </TouchableOpacity>
27
+ )}
28
+
29
+ <View style={styles.titleContainer}>
30
+ <Text style={styles.title}>{title}</Text>
31
+ {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
32
+ </View>
33
+
34
+ <TouchableOpacity style={styles.closeButton} onPress={onClose}>
35
+ <Icon name="close" size={24} color="#000" />
36
+ </TouchableOpacity>
37
+ </View>
38
+ );
39
+ };
40
+
41
+ const styles = StyleSheet.create({
42
+ header: {
43
+ flexDirection: 'row',
44
+ alignItems: 'center',
45
+ padding: 16,
46
+ backgroundColor: COLORS.headerBg,
47
+ borderTopLeftRadius: 16,
48
+ borderTopRightRadius: 16,
49
+ },
50
+ backButton: {
51
+ padding: 8,
52
+ marginRight: 8,
53
+ },
54
+ titleContainer: {
55
+ flex: 1,
56
+ },
57
+ title: {
58
+ fontSize: 18,
59
+ fontWeight: '600',
60
+ color: '#000',
61
+ },
62
+ subtitle: {
63
+ fontSize: 14,
64
+ color: '#666',
65
+ marginTop: 4,
66
+ },
67
+ closeButton: {
68
+ padding: 8,
69
+ },
70
+ });