@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,356 @@
1
+ import React, { useState, useCallback, useEffect } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ Keyboard,
9
+ KeyboardAvoidingView,
10
+ Platform,
11
+ } from 'react-native';
12
+ import Icon from 'react-native-vector-icons/MaterialIcons';
13
+ import { COLORS } from '../../constants';
14
+ import type { PinInputProps } from '../../types';
15
+
16
+ export const PinInput: React.FC<PinInputProps> = ({
17
+ onSubmit,
18
+ minLength = 6,
19
+ requireSpecialChar = true,
20
+ requireNumber = true,
21
+ }) => {
22
+ const [pin, setPin] = useState('');
23
+ const [confirmPin, setConfirmPin] = useState('');
24
+ const [showPin, setShowPin] = useState(false);
25
+ const [errors, setErrors] = useState<string[]>([]);
26
+ const [step, setStep] = useState<'create' | 'confirm'>('create');
27
+
28
+ // Validate pin whenever it changes
29
+ useEffect(() => {
30
+ if (step === 'create') {
31
+ validatePin(pin);
32
+ }
33
+ }, [pin, step]);
34
+
35
+ // Validate PIN strength and requirements
36
+ const validatePin = useCallback(
37
+ (value: string) => {
38
+ const newErrors: string[] = [];
39
+
40
+ if (value.length < minLength) {
41
+ newErrors.push(`PIN must be at least ${minLength} characters`);
42
+ }
43
+
44
+ if (requireNumber && !/\d/.test(value)) {
45
+ newErrors.push('PIN must include at least one number');
46
+ }
47
+
48
+ if (requireSpecialChar && !/[!@#$%^&*(),.?":{}|<>]/.test(value)) {
49
+ newErrors.push('PIN must include at least one special character');
50
+ }
51
+
52
+ setErrors(newErrors);
53
+ return newErrors.length === 0;
54
+ },
55
+ [minLength, requireNumber, requireSpecialChar]
56
+ );
57
+
58
+ // Handle PIN submission
59
+ const handleSubmit = useCallback(() => {
60
+ if (step === 'create') {
61
+ if (validatePin(pin)) {
62
+ setStep('confirm');
63
+ }
64
+ } else {
65
+ // Confirm PIN
66
+ if (pin === confirmPin) {
67
+ Keyboard.dismiss();
68
+ onSubmit(pin);
69
+ } else {
70
+ setErrors(['PINs do not match']);
71
+ setConfirmPin('');
72
+ }
73
+ }
74
+ }, [pin, confirmPin, step, validatePin, onSubmit]);
75
+
76
+ // Go back to PIN creation step
77
+ const handleBack = useCallback(() => {
78
+ setStep('create');
79
+ setConfirmPin('');
80
+ setErrors([]);
81
+ }, []);
82
+
83
+ // Toggle PIN visibility
84
+ const togglePinVisibility = useCallback(() => {
85
+ setShowPin(!showPin);
86
+ }, [showPin]);
87
+
88
+ return (
89
+ <KeyboardAvoidingView
90
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
91
+ style={styles.container}
92
+ >
93
+ <View style={styles.content}>
94
+ <Text style={styles.title}>
95
+ {step === 'create' ? 'Create a PIN' : 'Confirm your PIN'}
96
+ </Text>
97
+ <Text style={styles.subtitle}>
98
+ {step === 'create'
99
+ ? 'This PIN will protect your Onairos account'
100
+ : 'Please re-enter your PIN to confirm'}
101
+ </Text>
102
+
103
+ <View style={styles.inputContainer}>
104
+ <TextInput
105
+ style={styles.input}
106
+ value={step === 'create' ? pin : confirmPin}
107
+ onChangeText={step === 'create' ? setPin : setConfirmPin}
108
+ secureTextEntry={!showPin}
109
+ keyboardType="default"
110
+ autoCapitalize="none"
111
+ autoCorrect={false}
112
+ placeholder={step === 'create' ? 'Enter PIN' : 'Confirm PIN'}
113
+ placeholderTextColor="#999"
114
+ returnKeyType="done"
115
+ onSubmitEditing={handleSubmit}
116
+ />
117
+ <TouchableOpacity
118
+ style={styles.visibilityToggle}
119
+ onPress={togglePinVisibility}
120
+ >
121
+ <Icon
122
+ name={showPin ? 'visibility-off' : 'visibility'}
123
+ size={24}
124
+ color="#999"
125
+ />
126
+ </TouchableOpacity>
127
+ </View>
128
+
129
+ {errors.length > 0 && (
130
+ <View style={styles.errorContainer}>
131
+ {errors.map((error, index) => (
132
+ <View key={index} style={styles.errorItem}>
133
+ <Icon name="error-outline" size={16} color="#f44336" />
134
+ <Text style={styles.errorText}>{error}</Text>
135
+ </View>
136
+ ))}
137
+ </View>
138
+ )}
139
+
140
+ {step === 'create' && (
141
+ <View style={styles.requirementsContainer}>
142
+ <Text style={styles.requirementsTitle}>PIN Requirements:</Text>
143
+ <View style={styles.requirement}>
144
+ <Icon
145
+ name={pin.length >= minLength ? 'check-circle' : 'radio-button-unchecked'}
146
+ size={16}
147
+ color={pin.length >= minLength ? '#4caf50' : '#999'}
148
+ />
149
+ <Text
150
+ style={[
151
+ styles.requirementText,
152
+ {
153
+ color: pin.length >= minLength ? '#4caf50' : '#999',
154
+ },
155
+ ]}
156
+ >
157
+ At least {minLength} characters
158
+ </Text>
159
+ </View>
160
+
161
+ {requireNumber && (
162
+ <View style={styles.requirement}>
163
+ <Icon
164
+ name={/\d/.test(pin) ? 'check-circle' : 'radio-button-unchecked'}
165
+ size={16}
166
+ color={/\d/.test(pin) ? '#4caf50' : '#999'}
167
+ />
168
+ <Text
169
+ style={[
170
+ styles.requirementText,
171
+ {
172
+ color: /\d/.test(pin) ? '#4caf50' : '#999',
173
+ },
174
+ ]}
175
+ >
176
+ Include at least one number
177
+ </Text>
178
+ </View>
179
+ )}
180
+
181
+ {requireSpecialChar && (
182
+ <View style={styles.requirement}>
183
+ <Icon
184
+ name={
185
+ /[!@#$%^&*(),.?":{}|<>]/.test(pin)
186
+ ? 'check-circle'
187
+ : 'radio-button-unchecked'
188
+ }
189
+ size={16}
190
+ color={
191
+ /[!@#$%^&*(),.?":{}|<>]/.test(pin) ? '#4caf50' : '#999'
192
+ }
193
+ />
194
+ <Text
195
+ style={[
196
+ styles.requirementText,
197
+ {
198
+ color: /[!@#$%^&*(),.?":{}|<>]/.test(pin)
199
+ ? '#4caf50'
200
+ : '#999',
201
+ },
202
+ ]}
203
+ >
204
+ Include at least one special character
205
+ </Text>
206
+ </View>
207
+ )}
208
+ </View>
209
+ )}
210
+
211
+ <View style={styles.buttonContainer}>
212
+ {step === 'confirm' && (
213
+ <TouchableOpacity
214
+ style={[styles.button, styles.backButton]}
215
+ onPress={handleBack}
216
+ >
217
+ <Icon name="arrow-back" size={20} color={COLORS.primary} />
218
+ <Text style={[styles.buttonText, styles.backButtonText]}>
219
+ Back
220
+ </Text>
221
+ </TouchableOpacity>
222
+ )}
223
+
224
+ <TouchableOpacity
225
+ style={[
226
+ styles.button,
227
+ styles.nextButton,
228
+ (step === 'create' && errors.length > 0) ||
229
+ (step === 'confirm' && !confirmPin)
230
+ ? styles.disabledButton
231
+ : null,
232
+ ]}
233
+ onPress={handleSubmit}
234
+ disabled={
235
+ (step === 'create' && errors.length > 0) ||
236
+ (step === 'confirm' && !confirmPin)
237
+ }
238
+ >
239
+ <Text style={[styles.buttonText, styles.nextButtonText]}>
240
+ {step === 'create' ? 'Next' : 'Submit'}
241
+ </Text>
242
+ <Icon name="arrow-forward" size={20} color="#fff" />
243
+ </TouchableOpacity>
244
+ </View>
245
+ </View>
246
+ </KeyboardAvoidingView>
247
+ );
248
+ };
249
+
250
+ const styles = StyleSheet.create({
251
+ container: {
252
+ flex: 1,
253
+ },
254
+ content: {
255
+ padding: 24,
256
+ },
257
+ title: {
258
+ fontSize: 24,
259
+ fontWeight: 'bold',
260
+ marginBottom: 8,
261
+ color: '#000',
262
+ },
263
+ subtitle: {
264
+ fontSize: 16,
265
+ color: '#666',
266
+ marginBottom: 24,
267
+ },
268
+ inputContainer: {
269
+ flexDirection: 'row',
270
+ alignItems: 'center',
271
+ borderWidth: 1,
272
+ borderColor: '#ddd',
273
+ borderRadius: 8,
274
+ backgroundColor: '#f9f9f9',
275
+ marginBottom: 16,
276
+ },
277
+ input: {
278
+ flex: 1,
279
+ paddingHorizontal: 16,
280
+ paddingVertical: 12,
281
+ fontSize: 16,
282
+ color: '#000',
283
+ },
284
+ visibilityToggle: {
285
+ padding: 12,
286
+ },
287
+ errorContainer: {
288
+ marginBottom: 16,
289
+ },
290
+ errorItem: {
291
+ flexDirection: 'row',
292
+ alignItems: 'center',
293
+ marginBottom: 4,
294
+ },
295
+ errorText: {
296
+ color: '#f44336',
297
+ marginLeft: 6,
298
+ },
299
+ requirementsContainer: {
300
+ backgroundColor: '#f5f5f5',
301
+ padding: 16,
302
+ borderRadius: 8,
303
+ marginBottom: 24,
304
+ },
305
+ requirementsTitle: {
306
+ fontSize: 14,
307
+ fontWeight: '600',
308
+ marginBottom: 8,
309
+ color: '#000',
310
+ },
311
+ requirement: {
312
+ flexDirection: 'row',
313
+ alignItems: 'center',
314
+ marginBottom: 8,
315
+ },
316
+ requirementText: {
317
+ marginLeft: 8,
318
+ fontSize: 14,
319
+ },
320
+ buttonContainer: {
321
+ flexDirection: 'row',
322
+ justifyContent: 'space-between',
323
+ },
324
+ button: {
325
+ flexDirection: 'row',
326
+ alignItems: 'center',
327
+ paddingVertical: 12,
328
+ paddingHorizontal: 20,
329
+ borderRadius: 25,
330
+ },
331
+ backButton: {
332
+ backgroundColor: 'transparent',
333
+ borderWidth: 1,
334
+ borderColor: COLORS.primary,
335
+ },
336
+ nextButton: {
337
+ backgroundColor: COLORS.primary,
338
+ minWidth: 120,
339
+ justifyContent: 'center',
340
+ },
341
+ disabledButton: {
342
+ backgroundColor: '#ccc',
343
+ },
344
+ buttonText: {
345
+ fontSize: 16,
346
+ fontWeight: '600',
347
+ },
348
+ backButtonText: {
349
+ color: COLORS.primary,
350
+ marginLeft: 8,
351
+ },
352
+ nextButtonText: {
353
+ color: '#fff',
354
+ marginRight: 8,
355
+ },
356
+ });
@@ -0,0 +1,297 @@
1
+ import React, { useState } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ActivityIndicator,
8
+ ScrollView,
9
+ } from 'react-native';
10
+ import Icon from 'react-native-vector-icons/MaterialIcons';
11
+ import { COLORS } from '../../constants';
12
+ import { OAuthWebView } from './OAuthWebView';
13
+ import type { ConnectionStatus, PlatformConfig } from '../../types';
14
+
15
+ interface PlatformConnectorProps {
16
+ connections: ConnectionStatus;
17
+ onConnect: (platform: string) => Promise<void>;
18
+ onDisconnect: (platform: string) => Promise<void>;
19
+ isLoading: boolean;
20
+ canProceed: boolean;
21
+ onProceed: () => void;
22
+ }
23
+
24
+ // Platform definitions
25
+ const PLATFORMS: Record<string, PlatformConfig> = {
26
+ instagram: {
27
+ name: 'Instagram',
28
+ icon: 'photo-camera',
29
+ color: '#E1306C',
30
+ description: 'Connect to share and analyze your Instagram content',
31
+ },
32
+ youtube: {
33
+ name: 'YouTube',
34
+ icon: 'smart-display',
35
+ color: '#FF0000',
36
+ description: 'Connect for YouTube video recommendations and analysis',
37
+ },
38
+ pinterest: {
39
+ name: 'Pinterest',
40
+ icon: 'push-pin',
41
+ color: '#E60023',
42
+ description: 'Connect to enhance Pinterest board recommendations',
43
+ },
44
+ reddit: {
45
+ name: 'Reddit',
46
+ icon: 'forum',
47
+ color: '#FF4500',
48
+ description: 'Connect to personalize Reddit content insights',
49
+ },
50
+ };
51
+
52
+ export const PlatformConnector: React.FC<PlatformConnectorProps> = ({
53
+ connections,
54
+ onConnect,
55
+ onDisconnect,
56
+ isLoading,
57
+ canProceed,
58
+ onProceed,
59
+ }) => {
60
+ const [activeWebview, setActiveWebview] = useState<string | null>(null);
61
+
62
+ const handleConnectPress = async (platform: string) => {
63
+ setActiveWebview(platform);
64
+ };
65
+
66
+ const handleWebViewComplete = () => {
67
+ setActiveWebview(null);
68
+ };
69
+
70
+ const handleWebViewClose = () => {
71
+ setActiveWebview(null);
72
+ };
73
+
74
+ const handleDisconnectPress = async (platform: string) => {
75
+ await onDisconnect(platform);
76
+ };
77
+
78
+ const getPlatformStatus = (platform: string) => {
79
+ const platformKey = platform as keyof ConnectionStatus;
80
+ return connections[platformKey] ? true : false;
81
+ };
82
+
83
+ const getConnectedCount = () => {
84
+ return Object.values(connections).filter(Boolean).length;
85
+ };
86
+
87
+ return (
88
+ <View style={styles.container}>
89
+ <Text style={styles.title}>Connect Your Platforms</Text>
90
+ <Text style={styles.subtitle}>
91
+ Connect at least 2 platforms to create your Onairos account
92
+ </Text>
93
+
94
+ <ScrollView style={styles.platformList}>
95
+ {Object.keys(PLATFORMS).map((platform) => {
96
+ const platformInfo = PLATFORMS[platform];
97
+ const isConnected = getPlatformStatus(platform);
98
+ const userName = isConnected
99
+ ? connections[platform as keyof ConnectionStatus]?.userName
100
+ : null;
101
+
102
+ return (
103
+ <View key={platform} style={styles.platformItem}>
104
+ <View style={styles.platformInfo}>
105
+ <View
106
+ style={[
107
+ styles.platformIcon,
108
+ { backgroundColor: platformInfo.color },
109
+ ]}
110
+ >
111
+ <Icon name={platformInfo.icon} size={24} color="#fff" />
112
+ </View>
113
+ <View style={styles.platformText}>
114
+ <Text style={styles.platformName}>{platformInfo.name}</Text>
115
+ <Text style={styles.platformDescription}>
116
+ {isConnected
117
+ ? `Connected as ${userName}`
118
+ : platformInfo.description}
119
+ </Text>
120
+ </View>
121
+ </View>
122
+
123
+ <TouchableOpacity
124
+ style={[
125
+ styles.platformButton,
126
+ isConnected ? styles.disconnectButton : styles.connectButton,
127
+ ]}
128
+ onPress={() =>
129
+ isConnected
130
+ ? handleDisconnectPress(platform)
131
+ : handleConnectPress(platform)
132
+ }
133
+ disabled={isLoading}
134
+ >
135
+ {isLoading ? (
136
+ <ActivityIndicator size="small" color="#fff" />
137
+ ) : (
138
+ <Text style={styles.buttonText}>
139
+ {isConnected ? 'Disconnect' : 'Connect'}
140
+ </Text>
141
+ )}
142
+ </TouchableOpacity>
143
+ </View>
144
+ );
145
+ })}
146
+ </ScrollView>
147
+
148
+ <View style={styles.footer}>
149
+ <Text style={styles.connectionStatus}>
150
+ {getConnectedCount()} of {Object.keys(PLATFORMS).length} platforms connected
151
+ </Text>
152
+ <TouchableOpacity
153
+ style={[
154
+ styles.proceedButton,
155
+ canProceed ? styles.proceedActive : styles.proceedInactive,
156
+ ]}
157
+ onPress={onProceed}
158
+ disabled={!canProceed}
159
+ >
160
+ <Text
161
+ style={[
162
+ styles.proceedText,
163
+ canProceed ? styles.proceedTextActive : styles.proceedTextInactive,
164
+ ]}
165
+ >
166
+ Continue
167
+ </Text>
168
+ <Icon
169
+ name="arrow-forward"
170
+ size={20}
171
+ color={canProceed ? '#fff' : '#999'}
172
+ />
173
+ </TouchableOpacity>
174
+ </View>
175
+
176
+ {activeWebview && (
177
+ <OAuthWebView
178
+ url={`https://oauth.example.com/${activeWebview}`} // This would be replaced with actual OAuth URL
179
+ platform={activeWebview}
180
+ onComplete={handleWebViewComplete}
181
+ onClose={handleWebViewClose}
182
+ />
183
+ )}
184
+ </View>
185
+ );
186
+ };
187
+
188
+ const styles = StyleSheet.create({
189
+ container: {
190
+ flex: 1,
191
+ padding: 16,
192
+ },
193
+ title: {
194
+ fontSize: 24,
195
+ fontWeight: 'bold',
196
+ marginBottom: 8,
197
+ color: '#000',
198
+ },
199
+ subtitle: {
200
+ fontSize: 16,
201
+ color: '#666',
202
+ marginBottom: 24,
203
+ },
204
+ platformList: {
205
+ flex: 1,
206
+ },
207
+ platformItem: {
208
+ flexDirection: 'row',
209
+ alignItems: 'center',
210
+ marginBottom: 16,
211
+ padding: 12,
212
+ backgroundColor: '#f8f8f8',
213
+ borderRadius: 12,
214
+ borderWidth: 1,
215
+ borderColor: '#eee',
216
+ },
217
+ platformInfo: {
218
+ flex: 1,
219
+ flexDirection: 'row',
220
+ alignItems: 'center',
221
+ },
222
+ platformIcon: {
223
+ width: 40,
224
+ height: 40,
225
+ borderRadius: 20,
226
+ justifyContent: 'center',
227
+ alignItems: 'center',
228
+ marginRight: 12,
229
+ },
230
+ platformText: {
231
+ flex: 1,
232
+ },
233
+ platformName: {
234
+ fontSize: 16,
235
+ fontWeight: '600',
236
+ marginBottom: 4,
237
+ color: '#000',
238
+ },
239
+ platformDescription: {
240
+ fontSize: 14,
241
+ color: '#666',
242
+ },
243
+ platformButton: {
244
+ paddingHorizontal: 16,
245
+ paddingVertical: 8,
246
+ borderRadius: 20,
247
+ justifyContent: 'center',
248
+ alignItems: 'center',
249
+ minWidth: 100,
250
+ },
251
+ connectButton: {
252
+ backgroundColor: COLORS.primary,
253
+ },
254
+ disconnectButton: {
255
+ backgroundColor: '#f44336',
256
+ },
257
+ buttonText: {
258
+ color: '#fff',
259
+ fontWeight: '600',
260
+ },
261
+ footer: {
262
+ flexDirection: 'row',
263
+ alignItems: 'center',
264
+ justifyContent: 'space-between',
265
+ paddingTop: 16,
266
+ borderTopWidth: 1,
267
+ borderTopColor: '#eee',
268
+ marginTop: 16,
269
+ },
270
+ connectionStatus: {
271
+ fontSize: 14,
272
+ color: '#666',
273
+ },
274
+ proceedButton: {
275
+ flexDirection: 'row',
276
+ alignItems: 'center',
277
+ paddingHorizontal: 16,
278
+ paddingVertical: 10,
279
+ borderRadius: 24,
280
+ },
281
+ proceedActive: {
282
+ backgroundColor: COLORS.primary,
283
+ },
284
+ proceedInactive: {
285
+ backgroundColor: '#eee',
286
+ },
287
+ proceedText: {
288
+ fontWeight: '600',
289
+ marginRight: 8,
290
+ },
291
+ proceedTextActive: {
292
+ color: '#fff',
293
+ },
294
+ proceedTextInactive: {
295
+ color: '#999',
296
+ },
297
+ });