@onairos/react-native 3.1.16 → 3.1.18

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 (207) hide show
  1. package/README.md +404 -0
  2. package/lib/commonjs/assets/images/Checkbox.svg +3 -3
  3. package/lib/commonjs/assets/images/EnochE.svg +19 -19
  4. package/lib/commonjs/assets/images/Personalityprofile.svg +3 -3
  5. package/lib/commonjs/assets/images/Personalitytraits.svg +3 -3
  6. package/lib/commonjs/assets/images/Userpreferences.svg +3 -3
  7. package/lib/commonjs/assets/images/arrow.svg +20 -20
  8. package/lib/commonjs/assets/images/basicproficon.svg +43 -43
  9. package/lib/commonjs/assets/images/basicprofile.svg +3 -3
  10. package/lib/commonjs/assets/images/checkmark.svg +4 -4
  11. package/lib/commonjs/assets/images/contentanalysis.svg +3 -3
  12. package/lib/commonjs/assets/images/contenticon.svg +23 -23
  13. package/lib/commonjs/assets/images/personalityicon.svg +18 -18
  14. package/lib/commonjs/assets/images/x-close.svg +3 -3
  15. package/lib/commonjs/components/ModalSheet.js +8 -2
  16. package/lib/commonjs/components/ModalSheet.js.map +1 -1
  17. package/lib/commonjs/components/OnairosButton.js +290 -0
  18. package/lib/commonjs/components/OnairosButton.js.map +1 -0
  19. package/lib/commonjs/components/OnairosSignInButton.js +32 -8
  20. package/lib/commonjs/components/OnairosSignInButton.js.map +1 -1
  21. package/lib/commonjs/components/UniversalOnboarding.js +4 -4
  22. package/lib/commonjs/components/WelcomeScreen.js +29 -13
  23. package/lib/commonjs/components/WelcomeScreen.js.map +1 -1
  24. package/lib/commonjs/config/api.js +2 -2
  25. package/lib/commonjs/hooks/useConnections.js +6 -6
  26. package/lib/commonjs/hooks/useUserConnections.js +10 -10
  27. package/lib/commonjs/index.js +13 -6
  28. package/lib/commonjs/index.js.map +1 -1
  29. package/lib/commonjs/services/apiClient.js +35 -35
  30. package/lib/commonjs/services/apiKeyService.js +99 -99
  31. package/lib/commonjs/services/authService.js +82 -82
  32. package/lib/commonjs/services/biometricPinService.js +10 -10
  33. package/lib/commonjs/services/connectedAccountsService.js +32 -32
  34. package/lib/commonjs/services/googleAuthService.js +15 -15
  35. package/lib/commonjs/services/imageCompressionService.js +15 -15
  36. package/lib/commonjs/services/jwtStorageService.js +59 -59
  37. package/lib/commonjs/services/mobileTrainingService.js +14 -14
  38. package/lib/commonjs/services/pinEncryptionService.js +10 -10
  39. package/lib/commonjs/services/pinStorageUtils.js +15 -15
  40. package/lib/commonjs/services/platformAuthService.js +47 -47
  41. package/lib/commonjs/services/storageService.js +31 -31
  42. package/lib/commonjs/services/trainingApiHelpers.js +33 -33
  43. package/lib/commonjs/services/userConnectionsService.js +24 -24
  44. package/lib/commonjs/utils/Portal.js +4 -4
  45. package/lib/commonjs/utils/api.js +24 -24
  46. package/lib/commonjs/utils/auth.js +18 -18
  47. package/lib/commonjs/utils/crypto.js +13 -13
  48. package/lib/commonjs/utils/encryption.js +12 -12
  49. package/lib/commonjs/utils/eventUtils.js +52 -52
  50. package/lib/commonjs/utils/programmaticFlow.js +16 -16
  51. package/lib/commonjs/utils/retryHelper.js +27 -27
  52. package/lib/module/assets/images/Checkbox.svg +3 -3
  53. package/lib/module/assets/images/EnochE.svg +19 -19
  54. package/lib/module/assets/images/Personalityprofile.svg +3 -3
  55. package/lib/module/assets/images/Personalitytraits.svg +3 -3
  56. package/lib/module/assets/images/Userpreferences.svg +3 -3
  57. package/lib/module/assets/images/arrow.svg +20 -20
  58. package/lib/module/assets/images/basicproficon.svg +43 -43
  59. package/lib/module/assets/images/basicprofile.svg +3 -3
  60. package/lib/module/assets/images/checkmark.svg +4 -4
  61. package/lib/module/assets/images/contentanalysis.svg +3 -3
  62. package/lib/module/assets/images/contenticon.svg +23 -23
  63. package/lib/module/assets/images/personalityicon.svg +18 -18
  64. package/lib/module/assets/images/x-close.svg +3 -3
  65. package/lib/module/components/ModalSheet.js +7 -2
  66. package/lib/module/components/ModalSheet.js.map +1 -1
  67. package/lib/module/components/OnairosButton.js +282 -0
  68. package/lib/module/components/OnairosButton.js.map +1 -0
  69. package/lib/module/components/OnairosSignInButton.js +32 -8
  70. package/lib/module/components/OnairosSignInButton.js.map +1 -1
  71. package/lib/module/components/UniversalOnboarding.js +4 -4
  72. package/lib/module/components/WelcomeScreen.js +25 -10
  73. package/lib/module/components/WelcomeScreen.js.map +1 -1
  74. package/lib/module/config/api.js +2 -2
  75. package/lib/module/hooks/useConnections.js +6 -6
  76. package/lib/module/hooks/useUserConnections.js +10 -10
  77. package/lib/module/index.js +11 -11
  78. package/lib/module/index.js.map +1 -1
  79. package/lib/module/services/apiClient.js +35 -35
  80. package/lib/module/services/apiKeyService.js +99 -99
  81. package/lib/module/services/authService.js +82 -82
  82. package/lib/module/services/biometricPinService.js +10 -10
  83. package/lib/module/services/connectedAccountsService.js +32 -32
  84. package/lib/module/services/googleAuthService.js +15 -15
  85. package/lib/module/services/imageCompressionService.js +15 -15
  86. package/lib/module/services/jwtStorageService.js +59 -59
  87. package/lib/module/services/mobileTrainingService.js +14 -14
  88. package/lib/module/services/pinEncryptionService.js +10 -10
  89. package/lib/module/services/pinStorageUtils.js +15 -15
  90. package/lib/module/services/platformAuthService.js +47 -47
  91. package/lib/module/services/storageService.js +31 -31
  92. package/lib/module/services/trainingApiHelpers.js +33 -33
  93. package/lib/module/services/userConnectionsService.js +24 -24
  94. package/lib/module/utils/Portal.js +4 -4
  95. package/lib/module/utils/api.js +24 -24
  96. package/lib/module/utils/auth.js +18 -18
  97. package/lib/module/utils/crypto.js +13 -13
  98. package/lib/module/utils/encryption.js +12 -12
  99. package/lib/module/utils/eventUtils.js +52 -52
  100. package/lib/module/utils/programmaticFlow.js +16 -16
  101. package/lib/module/utils/retryHelper.js +27 -27
  102. package/lib/typescript/components/ModalSheet.d.ts.map +1 -1
  103. package/lib/typescript/components/OnairosButton.d.ts +37 -0
  104. package/lib/typescript/components/OnairosButton.d.ts.map +1 -0
  105. package/lib/typescript/components/OnairosSignInButton.d.ts.map +1 -1
  106. package/lib/typescript/components/WelcomeScreen.d.ts.map +1 -1
  107. package/lib/typescript/index.d.ts +3 -1
  108. package/lib/typescript/index.d.ts.map +1 -1
  109. package/package.json +145 -163
  110. package/src/api/index.ts +151 -151
  111. package/src/assets/images/Checkbox.svg +3 -3
  112. package/src/assets/images/EnochE.svg +19 -19
  113. package/src/assets/images/Personalityprofile.svg +3 -3
  114. package/src/assets/images/Personalitytraits.svg +3 -3
  115. package/src/assets/images/Userpreferences.svg +3 -3
  116. package/src/assets/images/arrow.svg +20 -20
  117. package/src/assets/images/basicproficon.svg +43 -43
  118. package/src/assets/images/basicprofile.svg +3 -3
  119. package/src/assets/images/checkmark.svg +4 -4
  120. package/src/assets/images/contentanalysis.svg +3 -3
  121. package/src/assets/images/contenticon.svg +23 -23
  122. package/src/assets/images/personalityicon.svg +18 -18
  123. package/src/assets/images/x-close.svg +3 -3
  124. package/src/components/BodyText.tsx +33 -33
  125. package/src/components/BrandMark.tsx +62 -62
  126. package/src/components/CodeInput.tsx +32 -32
  127. package/src/components/DataRequestScreen.tsx +355 -355
  128. package/src/components/EmailInput.tsx +31 -31
  129. package/src/components/EmailVerificationModal.tsx +363 -363
  130. package/src/components/ExistingUserDataConfirmation.tsx +506 -506
  131. package/src/components/GoogleButton.tsx +55 -55
  132. package/src/components/HeadingGroup.tsx +49 -49
  133. package/src/components/ModalHeader.tsx +125 -125
  134. package/src/components/ModalSheet.tsx +59 -57
  135. package/src/components/Onairos.tsx +422 -422
  136. package/src/components/OnairosButton.tsx +339 -0
  137. package/src/components/OnairosSignInButton.tsx +31 -9
  138. package/src/components/Overlay.tsx +506 -506
  139. package/src/components/PersonaImage.tsx +79 -79
  140. package/src/components/PersonaLoadingScreen.tsx +201 -201
  141. package/src/components/PersonalizationConsentScreen.tsx +410 -410
  142. package/src/components/PinCreationScreen.tsx +492 -492
  143. package/src/components/PinInput.tsx +555 -555
  144. package/src/components/PlatformConnectorsStep.tsx +891 -891
  145. package/src/components/PlatformList.tsx +144 -144
  146. package/src/components/PlatformToggle.tsx +226 -226
  147. package/src/components/PrimaryButton.tsx +213 -213
  148. package/src/components/SignInMatchAnimation.tsx +225 -225
  149. package/src/components/SignInStep.tsx +217 -217
  150. package/src/components/TrainingModal.tsx +1047 -1047
  151. package/src/components/UniversalOnboarding.tsx +2887 -2887
  152. package/src/components/VerificationStep.tsx +198 -198
  153. package/src/components/WelcomeScreen.tsx +490 -473
  154. package/src/components/icons/Basicproficon.tsx +30 -30
  155. package/src/components/icons/Basicprofile.tsx +17 -17
  156. package/src/components/icons/Checkbox.tsx +17 -17
  157. package/src/components/icons/Checkmark.tsx +24 -24
  158. package/src/components/icons/Contentanalysis.tsx +17 -17
  159. package/src/components/icons/Contenticon.tsx +30 -30
  160. package/src/components/icons/EnochE.tsx +39 -39
  161. package/src/components/icons/Personalityicon.tsx +22 -22
  162. package/src/components/icons/Personalityprofile.tsx +17 -17
  163. package/src/components/icons/Personalitytraits.tsx +17 -17
  164. package/src/components/icons/Userpreferences.tsx +17 -17
  165. package/src/components/icons/index.ts +12 -12
  166. package/src/components/onboarding/OAuthWebView.tsx +232 -232
  167. package/src/config/api.ts +25 -25
  168. package/src/context/AuthContext.tsx +393 -393
  169. package/src/hooks/useConnectedAccounts.ts +138 -138
  170. package/src/hooks/useConnections.ts +161 -161
  171. package/src/hooks/useCredentials.ts +174 -174
  172. package/src/hooks/useUserConnections.ts +165 -165
  173. package/src/index.js +14 -0
  174. package/src/index.ts +99 -96
  175. package/src/services/apiClient.ts +336 -336
  176. package/src/services/apiKeyService.ts +919 -919
  177. package/src/services/authService.ts +1008 -1008
  178. package/src/services/biometricPinService.ts +192 -192
  179. package/src/services/connectedAccountsService.ts +289 -289
  180. package/src/services/googleAuthService.ts +279 -279
  181. package/src/services/imageCompressionService.ts +302 -302
  182. package/src/services/jwtStorageService.ts +256 -256
  183. package/src/services/mobileTrainingService.ts +203 -203
  184. package/src/services/pinEncryptionService.ts +75 -75
  185. package/src/services/pinStorageUtils.ts +96 -96
  186. package/src/services/platformAuthService.ts +1346 -1346
  187. package/src/services/storageService.ts +451 -451
  188. package/src/services/trainingApiHelpers.ts +66 -66
  189. package/src/services/userConnectionsService.ts +556 -556
  190. package/src/services/youtubeMigrationService.ts +453 -453
  191. package/src/theme/index.ts +239 -239
  192. package/src/types/ambient.d.ts +28 -28
  193. package/src/types/index.ts +265 -265
  194. package/src/types/node-fix.d.ts +18 -18
  195. package/src/types/node-override.d.ts +23 -23
  196. package/src/types/opacity.d.ts +15 -15
  197. package/src/types/types.d.ts +17 -17
  198. package/src/utils/Portal.tsx +82 -82
  199. package/src/utils/api.js +111 -111
  200. package/src/utils/auth.js +103 -103
  201. package/src/utils/crypto.js +59 -59
  202. package/src/utils/encryption.ts +68 -68
  203. package/src/utils/eventUtils.ts +302 -302
  204. package/src/utils/haptics.ts +58 -58
  205. package/src/utils/imagePreloader.ts +2 -2
  206. package/src/utils/programmaticFlow.ts +112 -112
  207. package/src/utils/retryHelper.ts +274 -274
@@ -1,555 +1,555 @@
1
- import React, { useState, useCallback } from 'react';
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- TextInput,
7
- TouchableOpacity,
8
- Dimensions,
9
- Keyboard,
10
- ActivityIndicator,
11
- } from 'react-native';
12
- import { biometricPinService } from '../services/biometricPinService';
13
-
14
- const { width } = Dimensions.get('window');
15
-
16
- export interface PinInputProps {
17
- onSubmit: (pin: string) => void;
18
- minLength?: number;
19
- requireSpecialChar?: boolean;
20
- requireNumber?: boolean;
21
- onBack?: () => void;
22
- // New prop to enable biometric storage
23
- enableBiometricStorage?: boolean;
24
- // ✅ NEW: Background training optimization props
25
- onBackgroundTrainingStart?: () => Promise<void>;
26
- showBackgroundProgress?: boolean;
27
- backgroundProgressText?: string;
28
- }
29
-
30
- type InfoStatus = 'default' | 'biometric-unavailable' | 'biometric-failed' | 'storage-error' | 'authenticating';
31
-
32
- export const PinInput: React.FC<PinInputProps> = ({
33
- onSubmit,
34
- minLength = 8,
35
- requireSpecialChar = true,
36
- requireNumber = true,
37
- onBack,
38
- enableBiometricStorage = true,
39
- // ✅ NEW: Background training optimization props
40
- onBackgroundTrainingStart,
41
- showBackgroundProgress = false,
42
- backgroundProgressText = "Training is starting in the background...",
43
- }) => {
44
- const [pin, setPin] = useState('');
45
- const [error, setError] = useState<string | null>(null);
46
- const [showPin, setShowPin] = useState(false);
47
- const [isStoringPin, setIsStoringPin] = useState(false);
48
- const [infoStatus, setInfoStatus] = useState<InfoStatus>('default');
49
- // ✅ NEW: Background training state
50
- const [isBackgroundTrainingStarted, setIsBackgroundTrainingStarted] = useState(false);
51
-
52
- // ✅ UPDATED: Make background training conditional, not automatic on mount
53
- React.useEffect(() => {
54
- // Only start background training if explicitly requested and not already started
55
- // This prevents automatic training on component mount
56
- if (onBackgroundTrainingStart && !isBackgroundTrainingStarted && showBackgroundProgress) {
57
- console.log('🚀 [PIN INPUT] Background training explicitly requested...');
58
- setIsBackgroundTrainingStarted(true);
59
-
60
- onBackgroundTrainingStart().catch(error => {
61
- console.error('❌ [PIN INPUT] Background training failed:', error);
62
- setIsBackgroundTrainingStarted(false);
63
- });
64
- }
65
- }, [onBackgroundTrainingStart, isBackgroundTrainingStarted, showBackgroundProgress]);
66
-
67
- const validatePin = useCallback((value: string) => {
68
- if (value.length < minLength) {
69
- return `PIN must be at least ${minLength} characters`;
70
- }
71
- if (requireSpecialChar && !/[!@#$%^&*(),.?":{}|<>]/.test(value)) {
72
- return 'PIN must include a special character';
73
- }
74
- if (requireNumber && !/\d/.test(value)) {
75
- return 'PIN must include a number';
76
- }
77
- return null;
78
- }, [minLength, requireSpecialChar, requireNumber]);
79
-
80
- const handleSubmit = useCallback(async () => {
81
- console.log('🔍 [PIN INPUT] handleSubmit called with PIN length:', pin.length);
82
-
83
- const validationError = validatePin(pin);
84
- if (validationError) {
85
- console.log('❌ [PIN INPUT] Validation failed:', validationError);
86
- setError(validationError);
87
- return;
88
- }
89
-
90
- console.log('✅ [PIN INPUT] PIN validation passed');
91
-
92
- // If biometric storage is enabled, store PIN securely
93
- if (enableBiometricStorage) {
94
- console.log('🔐 [PIN INPUT] Biometric storage enabled, starting biometric flow...');
95
- setIsStoringPin(true);
96
- setInfoStatus('authenticating');
97
-
98
- try {
99
- // Check if biometric is available
100
- const biometricAvailable = await biometricPinService.isBiometricAvailable();
101
- console.log('📱 [PIN INPUT] Biometric available:', biometricAvailable);
102
-
103
- if (!biometricAvailable) {
104
- console.error('❌ [PIN INPUT] Face ID not available on this device');
105
- setInfoStatus('biometric-unavailable');
106
- setIsStoringPin(false);
107
- return;
108
- }
109
-
110
- // Immediately attempt to store PIN with biometric authentication
111
- console.log('🔐 [PIN INPUT] Triggering biometric authentication for PIN storage...');
112
-
113
- const stored = await biometricPinService.storePinWithBiometric(pin);
114
-
115
- if (stored) {
116
- console.log('✅ [PIN INPUT] PIN stored successfully with biometric protection');
117
-
118
- // Verify the PIN was actually stored by checking if it can be retrieved
119
- const isStored = await biometricPinService.isPinStored();
120
- if (isStored) {
121
- console.log('✅ [PIN INPUT] PIN storage verified - proceeding with onboarding');
122
- onSubmit(pin);
123
- } else {
124
- console.error('❌ [PIN INPUT] PIN storage verification failed');
125
- setInfoStatus('storage-error');
126
- setIsStoringPin(false);
127
- }
128
- } else {
129
- // Biometric authentication was cancelled or failed
130
- console.warn('⚠️ [PIN INPUT] Biometric authentication failed or was cancelled');
131
- setInfoStatus('biometric-failed');
132
- setIsStoringPin(false);
133
- }
134
- } catch (error) {
135
- console.error('❌ [PIN INPUT] Error handling biometric PIN storage:', error);
136
- setInfoStatus('storage-error');
137
- setIsStoringPin(false);
138
- }
139
- } else {
140
- // Biometric storage disabled, proceed normally
141
- console.log('🔐 [PIN INPUT] Biometric storage disabled, proceeding normally');
142
- onSubmit(pin);
143
- }
144
- }, [pin, validatePin, onSubmit, enableBiometricStorage]);
145
-
146
- const handlePinChange = useCallback((value: string) => {
147
- setPin(value);
148
- setError(null);
149
- setInfoStatus('default');
150
-
151
- // Debug logging to help troubleshoot
152
- console.log('🔍 [PIN INPUT] PIN changed:', {
153
- length: value.length,
154
- hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(value),
155
- hasNumber: /\d/.test(value),
156
- isValid: !validatePin(value)
157
- });
158
- }, [validatePin]);
159
-
160
- const handleContinueWithoutBiometric = useCallback(() => {
161
- console.warn('⚠️ [PIN INPUT] User chose to continue without biometric security');
162
- onSubmit(pin);
163
- }, [pin, onSubmit]);
164
-
165
- const handleRetry = useCallback(() => {
166
- setInfoStatus('default');
167
- handleSubmit();
168
- }, [handleSubmit]);
169
-
170
- const getInfoMessage = () => {
171
- switch (infoStatus) {
172
- case 'biometric-unavailable':
173
- return {
174
- title: 'Biometric Authentication Not Available',
175
- message: 'This device does not support Face ID or Touch ID. Please enable biometric authentication in your device settings to secure your PIN.',
176
- type: 'warning' as const,
177
- showActions: true,
178
- };
179
- case 'biometric-failed':
180
- return {
181
- title: 'Biometric Authentication Failed',
182
- message: 'Biometric authentication was cancelled or failed. Your PIN needs to be secured with biometric authentication to proceed.',
183
- type: 'warning' as const,
184
- showActions: true,
185
- };
186
- case 'storage-error':
187
- return {
188
- title: 'Storage Error',
189
- message: 'An error occurred while securing your PIN. Would you like to try again?',
190
- type: 'error' as const,
191
- showActions: true,
192
- };
193
- case 'authenticating':
194
- return {
195
- title: 'Authenticating...',
196
- message: 'Please complete biometric authentication to secure your PIN.',
197
- type: 'info' as const,
198
- showActions: false,
199
- };
200
- default:
201
- return {
202
- title: 'Secure PIN Storage',
203
- message: 'Your PIN will be securely stored locally on your device using biometric authentication (Face ID or Touch ID) for enhanced security.',
204
- type: 'info' as const,
205
- showActions: false,
206
- };
207
- }
208
- };
209
-
210
- const infoMessage = getInfoMessage();
211
-
212
- return (
213
- <View style={styles.container}>
214
- <View style={styles.inputSection}>
215
- <View style={styles.header}>
216
- {onBack && (
217
- <TouchableOpacity style={styles.backButton} onPress={onBack}>
218
- <Text style={{ fontSize: 24 }}>←</Text>
219
- </TouchableOpacity>
220
- )}
221
- <Text style={styles.title}>Create a PIN</Text>
222
- </View>
223
-
224
- <Text style={styles.subtitle}>
225
- A PIN so only you have access to your data
226
- </Text>
227
-
228
- {/* ✅ NEW: Background training progress indicator */}
229
- {showBackgroundProgress && isBackgroundTrainingStarted && (
230
- <View style={styles.backgroundProgressContainer}>
231
- <Text style={styles.backgroundProgressText}>
232
- {backgroundProgressText}
233
- </Text>
234
- <View style={styles.backgroundProgressIndicator}>
235
- <ActivityIndicator size="small" color="#007AFF" />
236
- </View>
237
- </View>
238
- )}
239
-
240
- <View style={styles.inputContainer}>
241
- <TextInput
242
- style={styles.input}
243
- value={pin}
244
- onChangeText={handlePinChange}
245
- secureTextEntry={!showPin}
246
- placeholder="e.g. MyPin123!"
247
- keyboardType="default"
248
- maxLength={20}
249
- autoCapitalize="none"
250
- autoCorrect={false}
251
- returnKeyType="done"
252
- onSubmitEditing={() => Keyboard.dismiss()}
253
- />
254
- <TouchableOpacity
255
- style={styles.visibilityButton}
256
- onPress={() => setShowPin(!showPin)}
257
- >
258
- <Text>{showPin ? '👁️' : '👁️‍🗨️'}</Text>
259
- </TouchableOpacity>
260
- </View>
261
- </View>
262
-
263
- {error && <Text style={styles.error}>{error}</Text>}
264
-
265
- <View style={styles.requirements}>
266
- <Text style={styles.requirementTitle}>PIN Requirements:</Text>
267
- <Text style={[styles.requirement, pin.length >= minLength && styles.requirementMet]}>
268
- • At least {minLength} characters
269
- </Text>
270
- {requireSpecialChar && (
271
- <Text
272
- style={[
273
- styles.requirement,
274
- /[!@#$%^&*(),.?":{}|<>]/.test(pin) && styles.requirementMet,
275
- ]}
276
- >
277
- • Include a special character
278
- </Text>
279
- )}
280
- {requireNumber && (
281
- <Text
282
- style={[
283
- styles.requirement,
284
- /\d/.test(pin) && styles.requirementMet,
285
- ]}
286
- >
287
- • Include a number
288
- </Text>
289
- )}
290
- </View>
291
-
292
- {/* Inline Information Section */}
293
- <View style={[
294
- styles.infoContainer,
295
- infoMessage.type === 'info' && styles.infoContainerInfo,
296
- infoMessage.type === 'warning' && styles.infoContainerWarning,
297
- infoMessage.type === 'error' && styles.infoContainerError,
298
- ]}>
299
- <Text style={[
300
- styles.infoTitle,
301
- infoMessage.type === 'info' && styles.infoTitleInfo,
302
- infoMessage.type === 'warning' && styles.infoTitleWarning,
303
- infoMessage.type === 'error' && styles.infoTitleError,
304
- ]}>
305
- {infoMessage.title}
306
- </Text>
307
- <Text style={[
308
- styles.infoMessage,
309
- infoMessage.type === 'info' && styles.infoMessageInfo,
310
- infoMessage.type === 'warning' && styles.infoMessageWarning,
311
- infoMessage.type === 'error' && styles.infoMessageError,
312
- ]}>
313
- {infoMessage.message}
314
- </Text>
315
-
316
- {infoMessage.showActions && (
317
- <View style={styles.infoActions}>
318
- <TouchableOpacity
319
- style={styles.infoActionButton}
320
- onPress={handleRetry}
321
- >
322
- <Text style={styles.infoActionButtonText}>Try Again</Text>
323
- </TouchableOpacity>
324
- <TouchableOpacity
325
- style={[styles.infoActionButton, styles.infoActionButtonSecondary]}
326
- onPress={handleContinueWithoutBiometric}
327
- >
328
- <Text style={[styles.infoActionButtonText, styles.infoActionButtonSecondaryText]}>
329
- Continue Without Biometric
330
- </Text>
331
- </TouchableOpacity>
332
- </View>
333
- )}
334
- </View>
335
-
336
- <View style={styles.footer}>
337
- {onBack && (
338
- <TouchableOpacity
339
- style={styles.cancelButton}
340
- onPress={onBack}
341
- >
342
- <Text style={styles.cancelButtonText}>Back</Text>
343
- </TouchableOpacity>
344
- )}
345
-
346
- <TouchableOpacity
347
- style={[styles.submitButton, (!pin || isStoringPin || validatePin(pin)) && styles.submitButtonDisabled]}
348
- onPress={handleSubmit}
349
- disabled={!pin || isStoringPin || !!validatePin(pin)}
350
- >
351
- <Text style={styles.submitButtonText}>
352
- {isStoringPin ? 'Authenticating...' : 'Continue'}
353
- </Text>
354
- </TouchableOpacity>
355
- </View>
356
- </View>
357
- );
358
- };
359
-
360
- const styles = StyleSheet.create({
361
- container: {
362
- flex: 1,
363
- padding: 16,
364
- width: width,
365
- backgroundColor: '#fff',
366
- justifyContent: 'flex-start',
367
- },
368
- inputSection: {
369
- width: '100%',
370
- marginTop: 0,
371
- paddingTop: 0,
372
- },
373
- header: {
374
- flexDirection: 'row',
375
- alignItems: 'center',
376
- justifyContent: 'space-between',
377
- marginBottom: 16,
378
- paddingVertical: 8,
379
- },
380
- backButton: {
381
- padding: 8,
382
- },
383
- title: {
384
- fontSize: 20,
385
- fontWeight: '600',
386
- color: '#333',
387
- textAlign: 'center',
388
- flex: 1,
389
- marginBottom: 8,
390
- },
391
- subtitle: {
392
- fontSize: 14,
393
- color: '#666',
394
- marginBottom: 24,
395
- textAlign: 'center',
396
- },
397
- inputContainer: {
398
- flexDirection: 'row',
399
- alignItems: 'center',
400
- borderWidth: 1,
401
- borderColor: '#CCCCCC',
402
- borderRadius: 8,
403
- marginBottom: 16,
404
- },
405
- input: {
406
- flex: 1,
407
- padding: 12,
408
- fontSize: 16,
409
- },
410
- visibilityButton: {
411
- padding: 12,
412
- },
413
- // Info Container Styles
414
- infoContainer: {
415
- padding: 16,
416
- borderRadius: 12,
417
- marginBottom: 16,
418
- borderWidth: 1,
419
- },
420
- infoContainerInfo: {
421
- backgroundColor: '#E3F2FD',
422
- borderColor: '#2196F3',
423
- },
424
- infoContainerWarning: {
425
- backgroundColor: '#FFF3E0',
426
- borderColor: '#FF9800',
427
- },
428
- infoContainerError: {
429
- backgroundColor: '#FFEBEE',
430
- borderColor: '#F44336',
431
- },
432
- infoTitle: {
433
- fontSize: 16,
434
- fontWeight: '600',
435
- marginBottom: 8,
436
- },
437
- infoTitleInfo: {
438
- color: '#1976D2',
439
- },
440
- infoTitleWarning: {
441
- color: '#F57C00',
442
- },
443
- infoTitleError: {
444
- color: '#D32F2F',
445
- },
446
- infoMessage: {
447
- fontSize: 14,
448
- lineHeight: 20,
449
- marginBottom: 12,
450
- },
451
- infoMessageInfo: {
452
- color: '#1565C0',
453
- },
454
- infoMessageWarning: {
455
- color: '#E65100',
456
- },
457
- infoMessageError: {
458
- color: '#C62828',
459
- },
460
- infoActions: {
461
- flexDirection: 'row',
462
- justifyContent: 'space-between',
463
- marginTop: 8,
464
- },
465
- infoActionButton: {
466
- flex: 1,
467
- paddingVertical: 10,
468
- paddingHorizontal: 16,
469
- borderRadius: 8,
470
- backgroundColor: '#007AFF',
471
- marginHorizontal: 4,
472
- alignItems: 'center',
473
- },
474
- infoActionButtonSecondary: {
475
- backgroundColor: 'transparent',
476
- borderWidth: 1,
477
- borderColor: '#007AFF',
478
- },
479
- infoActionButtonText: {
480
- color: '#fff',
481
- fontSize: 14,
482
- fontWeight: '600',
483
- },
484
- infoActionButtonSecondaryText: {
485
- color: '#007AFF',
486
- },
487
- error: {
488
- color: '#FF3B30',
489
- marginBottom: 16,
490
- },
491
- requirements: {
492
- marginBottom: 24,
493
- },
494
- requirementTitle: {
495
- fontSize: 14,
496
- fontWeight: '600',
497
- marginBottom: 8,
498
- color: '#333',
499
- },
500
- requirement: {
501
- fontSize: 14,
502
- color: '#666',
503
- marginBottom: 4,
504
- },
505
- requirementMet: {
506
- color: '#34C759',
507
- },
508
- footer: {
509
- flexDirection: 'row',
510
- alignItems: 'center',
511
- justifyContent: 'space-between',
512
- marginTop: 24,
513
- borderTopWidth: 1,
514
- borderTopColor: '#eee',
515
- paddingTop: 16,
516
- },
517
- cancelButton: {
518
- paddingVertical: 8,
519
- paddingHorizontal: 16,
520
- },
521
- cancelButtonText: {
522
- color: '#666',
523
- fontSize: 16,
524
- },
525
- submitButton: {
526
- paddingVertical: 16,
527
- paddingHorizontal: 32,
528
- borderRadius: 16,
529
- backgroundColor: '#000000',
530
- alignItems: 'center',
531
- },
532
- submitButtonDisabled: {
533
- opacity: 0.5,
534
- },
535
- submitButtonText: {
536
- color: '#fff',
537
- fontSize: 16,
538
- fontWeight: '600',
539
- },
540
- // ✅ NEW: Background training progress styles
541
- backgroundProgressContainer: {
542
- flexDirection: 'row',
543
- alignItems: 'center',
544
- justifyContent: 'center',
545
- marginBottom: 16,
546
- },
547
- backgroundProgressText: {
548
- fontSize: 14,
549
- color: '#666',
550
- marginRight: 10,
551
- },
552
- backgroundProgressIndicator: {
553
- padding: 5,
554
- },
555
- });
1
+ import React, { useState, useCallback } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TextInput,
7
+ TouchableOpacity,
8
+ Dimensions,
9
+ Keyboard,
10
+ ActivityIndicator,
11
+ } from 'react-native';
12
+ import { biometricPinService } from '../services/biometricPinService';
13
+
14
+ const { width } = Dimensions.get('window');
15
+
16
+ export interface PinInputProps {
17
+ onSubmit: (pin: string) => void;
18
+ minLength?: number;
19
+ requireSpecialChar?: boolean;
20
+ requireNumber?: boolean;
21
+ onBack?: () => void;
22
+ // New prop to enable biometric storage
23
+ enableBiometricStorage?: boolean;
24
+ // ✅ NEW: Background training optimization props
25
+ onBackgroundTrainingStart?: () => Promise<void>;
26
+ showBackgroundProgress?: boolean;
27
+ backgroundProgressText?: string;
28
+ }
29
+
30
+ type InfoStatus = 'default' | 'biometric-unavailable' | 'biometric-failed' | 'storage-error' | 'authenticating';
31
+
32
+ export const PinInput: React.FC<PinInputProps> = ({
33
+ onSubmit,
34
+ minLength = 8,
35
+ requireSpecialChar = true,
36
+ requireNumber = true,
37
+ onBack,
38
+ enableBiometricStorage = true,
39
+ // ✅ NEW: Background training optimization props
40
+ onBackgroundTrainingStart,
41
+ showBackgroundProgress = false,
42
+ backgroundProgressText = "Training is starting in the background...",
43
+ }) => {
44
+ const [pin, setPin] = useState('');
45
+ const [error, setError] = useState<string | null>(null);
46
+ const [showPin, setShowPin] = useState(false);
47
+ const [isStoringPin, setIsStoringPin] = useState(false);
48
+ const [infoStatus, setInfoStatus] = useState<InfoStatus>('default');
49
+ // ✅ NEW: Background training state
50
+ const [isBackgroundTrainingStarted, setIsBackgroundTrainingStarted] = useState(false);
51
+
52
+ // ✅ UPDATED: Make background training conditional, not automatic on mount
53
+ React.useEffect(() => {
54
+ // Only start background training if explicitly requested and not already started
55
+ // This prevents automatic training on component mount
56
+ if (onBackgroundTrainingStart && !isBackgroundTrainingStarted && showBackgroundProgress) {
57
+ console.log('🚀 [PIN INPUT] Background training explicitly requested...');
58
+ setIsBackgroundTrainingStarted(true);
59
+
60
+ onBackgroundTrainingStart().catch(error => {
61
+ console.error('❌ [PIN INPUT] Background training failed:', error);
62
+ setIsBackgroundTrainingStarted(false);
63
+ });
64
+ }
65
+ }, [onBackgroundTrainingStart, isBackgroundTrainingStarted, showBackgroundProgress]);
66
+
67
+ const validatePin = useCallback((value: string) => {
68
+ if (value.length < minLength) {
69
+ return `PIN must be at least ${minLength} characters`;
70
+ }
71
+ if (requireSpecialChar && !/[!@#$%^&*(),.?":{}|<>]/.test(value)) {
72
+ return 'PIN must include a special character';
73
+ }
74
+ if (requireNumber && !/\d/.test(value)) {
75
+ return 'PIN must include a number';
76
+ }
77
+ return null;
78
+ }, [minLength, requireSpecialChar, requireNumber]);
79
+
80
+ const handleSubmit = useCallback(async () => {
81
+ console.log('🔍 [PIN INPUT] handleSubmit called with PIN length:', pin.length);
82
+
83
+ const validationError = validatePin(pin);
84
+ if (validationError) {
85
+ console.log('❌ [PIN INPUT] Validation failed:', validationError);
86
+ setError(validationError);
87
+ return;
88
+ }
89
+
90
+ console.log('✅ [PIN INPUT] PIN validation passed');
91
+
92
+ // If biometric storage is enabled, store PIN securely
93
+ if (enableBiometricStorage) {
94
+ console.log('🔐 [PIN INPUT] Biometric storage enabled, starting biometric flow...');
95
+ setIsStoringPin(true);
96
+ setInfoStatus('authenticating');
97
+
98
+ try {
99
+ // Check if biometric is available
100
+ const biometricAvailable = await biometricPinService.isBiometricAvailable();
101
+ console.log('📱 [PIN INPUT] Biometric available:', biometricAvailable);
102
+
103
+ if (!biometricAvailable) {
104
+ console.error('❌ [PIN INPUT] Face ID not available on this device');
105
+ setInfoStatus('biometric-unavailable');
106
+ setIsStoringPin(false);
107
+ return;
108
+ }
109
+
110
+ // Immediately attempt to store PIN with biometric authentication
111
+ console.log('🔐 [PIN INPUT] Triggering biometric authentication for PIN storage...');
112
+
113
+ const stored = await biometricPinService.storePinWithBiometric(pin);
114
+
115
+ if (stored) {
116
+ console.log('✅ [PIN INPUT] PIN stored successfully with biometric protection');
117
+
118
+ // Verify the PIN was actually stored by checking if it can be retrieved
119
+ const isStored = await biometricPinService.isPinStored();
120
+ if (isStored) {
121
+ console.log('✅ [PIN INPUT] PIN storage verified - proceeding with onboarding');
122
+ onSubmit(pin);
123
+ } else {
124
+ console.error('❌ [PIN INPUT] PIN storage verification failed');
125
+ setInfoStatus('storage-error');
126
+ setIsStoringPin(false);
127
+ }
128
+ } else {
129
+ // Biometric authentication was cancelled or failed
130
+ console.warn('⚠️ [PIN INPUT] Biometric authentication failed or was cancelled');
131
+ setInfoStatus('biometric-failed');
132
+ setIsStoringPin(false);
133
+ }
134
+ } catch (error) {
135
+ console.error('❌ [PIN INPUT] Error handling biometric PIN storage:', error);
136
+ setInfoStatus('storage-error');
137
+ setIsStoringPin(false);
138
+ }
139
+ } else {
140
+ // Biometric storage disabled, proceed normally
141
+ console.log('🔐 [PIN INPUT] Biometric storage disabled, proceeding normally');
142
+ onSubmit(pin);
143
+ }
144
+ }, [pin, validatePin, onSubmit, enableBiometricStorage]);
145
+
146
+ const handlePinChange = useCallback((value: string) => {
147
+ setPin(value);
148
+ setError(null);
149
+ setInfoStatus('default');
150
+
151
+ // Debug logging to help troubleshoot
152
+ console.log('🔍 [PIN INPUT] PIN changed:', {
153
+ length: value.length,
154
+ hasSpecialChar: /[!@#$%^&*(),.?":{}|<>]/.test(value),
155
+ hasNumber: /\d/.test(value),
156
+ isValid: !validatePin(value)
157
+ });
158
+ }, [validatePin]);
159
+
160
+ const handleContinueWithoutBiometric = useCallback(() => {
161
+ console.warn('⚠️ [PIN INPUT] User chose to continue without biometric security');
162
+ onSubmit(pin);
163
+ }, [pin, onSubmit]);
164
+
165
+ const handleRetry = useCallback(() => {
166
+ setInfoStatus('default');
167
+ handleSubmit();
168
+ }, [handleSubmit]);
169
+
170
+ const getInfoMessage = () => {
171
+ switch (infoStatus) {
172
+ case 'biometric-unavailable':
173
+ return {
174
+ title: 'Biometric Authentication Not Available',
175
+ message: 'This device does not support Face ID or Touch ID. Please enable biometric authentication in your device settings to secure your PIN.',
176
+ type: 'warning' as const,
177
+ showActions: true,
178
+ };
179
+ case 'biometric-failed':
180
+ return {
181
+ title: 'Biometric Authentication Failed',
182
+ message: 'Biometric authentication was cancelled or failed. Your PIN needs to be secured with biometric authentication to proceed.',
183
+ type: 'warning' as const,
184
+ showActions: true,
185
+ };
186
+ case 'storage-error':
187
+ return {
188
+ title: 'Storage Error',
189
+ message: 'An error occurred while securing your PIN. Would you like to try again?',
190
+ type: 'error' as const,
191
+ showActions: true,
192
+ };
193
+ case 'authenticating':
194
+ return {
195
+ title: 'Authenticating...',
196
+ message: 'Please complete biometric authentication to secure your PIN.',
197
+ type: 'info' as const,
198
+ showActions: false,
199
+ };
200
+ default:
201
+ return {
202
+ title: 'Secure PIN Storage',
203
+ message: 'Your PIN will be securely stored locally on your device using biometric authentication (Face ID or Touch ID) for enhanced security.',
204
+ type: 'info' as const,
205
+ showActions: false,
206
+ };
207
+ }
208
+ };
209
+
210
+ const infoMessage = getInfoMessage();
211
+
212
+ return (
213
+ <View style={styles.container}>
214
+ <View style={styles.inputSection}>
215
+ <View style={styles.header}>
216
+ {onBack && (
217
+ <TouchableOpacity style={styles.backButton} onPress={onBack}>
218
+ <Text style={{ fontSize: 24 }}>←</Text>
219
+ </TouchableOpacity>
220
+ )}
221
+ <Text style={styles.title}>Create a PIN</Text>
222
+ </View>
223
+
224
+ <Text style={styles.subtitle}>
225
+ A PIN so only you have access to your data
226
+ </Text>
227
+
228
+ {/* ✅ NEW: Background training progress indicator */}
229
+ {showBackgroundProgress && isBackgroundTrainingStarted && (
230
+ <View style={styles.backgroundProgressContainer}>
231
+ <Text style={styles.backgroundProgressText}>
232
+ {backgroundProgressText}
233
+ </Text>
234
+ <View style={styles.backgroundProgressIndicator}>
235
+ <ActivityIndicator size="small" color="#007AFF" />
236
+ </View>
237
+ </View>
238
+ )}
239
+
240
+ <View style={styles.inputContainer}>
241
+ <TextInput
242
+ style={styles.input}
243
+ value={pin}
244
+ onChangeText={handlePinChange}
245
+ secureTextEntry={!showPin}
246
+ placeholder="e.g. MyPin123!"
247
+ keyboardType="default"
248
+ maxLength={20}
249
+ autoCapitalize="none"
250
+ autoCorrect={false}
251
+ returnKeyType="done"
252
+ onSubmitEditing={() => Keyboard.dismiss()}
253
+ />
254
+ <TouchableOpacity
255
+ style={styles.visibilityButton}
256
+ onPress={() => setShowPin(!showPin)}
257
+ >
258
+ <Text>{showPin ? '👁️' : '👁️‍🗨️'}</Text>
259
+ </TouchableOpacity>
260
+ </View>
261
+ </View>
262
+
263
+ {error && <Text style={styles.error}>{error}</Text>}
264
+
265
+ <View style={styles.requirements}>
266
+ <Text style={styles.requirementTitle}>PIN Requirements:</Text>
267
+ <Text style={[styles.requirement, pin.length >= minLength && styles.requirementMet]}>
268
+ • At least {minLength} characters
269
+ </Text>
270
+ {requireSpecialChar && (
271
+ <Text
272
+ style={[
273
+ styles.requirement,
274
+ /[!@#$%^&*(),.?":{}|<>]/.test(pin) && styles.requirementMet,
275
+ ]}
276
+ >
277
+ • Include a special character
278
+ </Text>
279
+ )}
280
+ {requireNumber && (
281
+ <Text
282
+ style={[
283
+ styles.requirement,
284
+ /\d/.test(pin) && styles.requirementMet,
285
+ ]}
286
+ >
287
+ • Include a number
288
+ </Text>
289
+ )}
290
+ </View>
291
+
292
+ {/* Inline Information Section */}
293
+ <View style={[
294
+ styles.infoContainer,
295
+ infoMessage.type === 'info' && styles.infoContainerInfo,
296
+ infoMessage.type === 'warning' && styles.infoContainerWarning,
297
+ infoMessage.type === 'error' && styles.infoContainerError,
298
+ ]}>
299
+ <Text style={[
300
+ styles.infoTitle,
301
+ infoMessage.type === 'info' && styles.infoTitleInfo,
302
+ infoMessage.type === 'warning' && styles.infoTitleWarning,
303
+ infoMessage.type === 'error' && styles.infoTitleError,
304
+ ]}>
305
+ {infoMessage.title}
306
+ </Text>
307
+ <Text style={[
308
+ styles.infoMessage,
309
+ infoMessage.type === 'info' && styles.infoMessageInfo,
310
+ infoMessage.type === 'warning' && styles.infoMessageWarning,
311
+ infoMessage.type === 'error' && styles.infoMessageError,
312
+ ]}>
313
+ {infoMessage.message}
314
+ </Text>
315
+
316
+ {infoMessage.showActions && (
317
+ <View style={styles.infoActions}>
318
+ <TouchableOpacity
319
+ style={styles.infoActionButton}
320
+ onPress={handleRetry}
321
+ >
322
+ <Text style={styles.infoActionButtonText}>Try Again</Text>
323
+ </TouchableOpacity>
324
+ <TouchableOpacity
325
+ style={[styles.infoActionButton, styles.infoActionButtonSecondary]}
326
+ onPress={handleContinueWithoutBiometric}
327
+ >
328
+ <Text style={[styles.infoActionButtonText, styles.infoActionButtonSecondaryText]}>
329
+ Continue Without Biometric
330
+ </Text>
331
+ </TouchableOpacity>
332
+ </View>
333
+ )}
334
+ </View>
335
+
336
+ <View style={styles.footer}>
337
+ {onBack && (
338
+ <TouchableOpacity
339
+ style={styles.cancelButton}
340
+ onPress={onBack}
341
+ >
342
+ <Text style={styles.cancelButtonText}>Back</Text>
343
+ </TouchableOpacity>
344
+ )}
345
+
346
+ <TouchableOpacity
347
+ style={[styles.submitButton, (!pin || isStoringPin || validatePin(pin)) && styles.submitButtonDisabled]}
348
+ onPress={handleSubmit}
349
+ disabled={!pin || isStoringPin || !!validatePin(pin)}
350
+ >
351
+ <Text style={styles.submitButtonText}>
352
+ {isStoringPin ? 'Authenticating...' : 'Continue'}
353
+ </Text>
354
+ </TouchableOpacity>
355
+ </View>
356
+ </View>
357
+ );
358
+ };
359
+
360
+ const styles = StyleSheet.create({
361
+ container: {
362
+ flex: 1,
363
+ padding: 16,
364
+ width: width,
365
+ backgroundColor: '#fff',
366
+ justifyContent: 'flex-start',
367
+ },
368
+ inputSection: {
369
+ width: '100%',
370
+ marginTop: 0,
371
+ paddingTop: 0,
372
+ },
373
+ header: {
374
+ flexDirection: 'row',
375
+ alignItems: 'center',
376
+ justifyContent: 'space-between',
377
+ marginBottom: 16,
378
+ paddingVertical: 8,
379
+ },
380
+ backButton: {
381
+ padding: 8,
382
+ },
383
+ title: {
384
+ fontSize: 20,
385
+ fontWeight: '600',
386
+ color: '#333',
387
+ textAlign: 'center',
388
+ flex: 1,
389
+ marginBottom: 8,
390
+ },
391
+ subtitle: {
392
+ fontSize: 14,
393
+ color: '#666',
394
+ marginBottom: 24,
395
+ textAlign: 'center',
396
+ },
397
+ inputContainer: {
398
+ flexDirection: 'row',
399
+ alignItems: 'center',
400
+ borderWidth: 1,
401
+ borderColor: '#CCCCCC',
402
+ borderRadius: 8,
403
+ marginBottom: 16,
404
+ },
405
+ input: {
406
+ flex: 1,
407
+ padding: 12,
408
+ fontSize: 16,
409
+ },
410
+ visibilityButton: {
411
+ padding: 12,
412
+ },
413
+ // Info Container Styles
414
+ infoContainer: {
415
+ padding: 16,
416
+ borderRadius: 12,
417
+ marginBottom: 16,
418
+ borderWidth: 1,
419
+ },
420
+ infoContainerInfo: {
421
+ backgroundColor: '#E3F2FD',
422
+ borderColor: '#2196F3',
423
+ },
424
+ infoContainerWarning: {
425
+ backgroundColor: '#FFF3E0',
426
+ borderColor: '#FF9800',
427
+ },
428
+ infoContainerError: {
429
+ backgroundColor: '#FFEBEE',
430
+ borderColor: '#F44336',
431
+ },
432
+ infoTitle: {
433
+ fontSize: 16,
434
+ fontWeight: '600',
435
+ marginBottom: 8,
436
+ },
437
+ infoTitleInfo: {
438
+ color: '#1976D2',
439
+ },
440
+ infoTitleWarning: {
441
+ color: '#F57C00',
442
+ },
443
+ infoTitleError: {
444
+ color: '#D32F2F',
445
+ },
446
+ infoMessage: {
447
+ fontSize: 14,
448
+ lineHeight: 20,
449
+ marginBottom: 12,
450
+ },
451
+ infoMessageInfo: {
452
+ color: '#1565C0',
453
+ },
454
+ infoMessageWarning: {
455
+ color: '#E65100',
456
+ },
457
+ infoMessageError: {
458
+ color: '#C62828',
459
+ },
460
+ infoActions: {
461
+ flexDirection: 'row',
462
+ justifyContent: 'space-between',
463
+ marginTop: 8,
464
+ },
465
+ infoActionButton: {
466
+ flex: 1,
467
+ paddingVertical: 10,
468
+ paddingHorizontal: 16,
469
+ borderRadius: 8,
470
+ backgroundColor: '#007AFF',
471
+ marginHorizontal: 4,
472
+ alignItems: 'center',
473
+ },
474
+ infoActionButtonSecondary: {
475
+ backgroundColor: 'transparent',
476
+ borderWidth: 1,
477
+ borderColor: '#007AFF',
478
+ },
479
+ infoActionButtonText: {
480
+ color: '#fff',
481
+ fontSize: 14,
482
+ fontWeight: '600',
483
+ },
484
+ infoActionButtonSecondaryText: {
485
+ color: '#007AFF',
486
+ },
487
+ error: {
488
+ color: '#FF3B30',
489
+ marginBottom: 16,
490
+ },
491
+ requirements: {
492
+ marginBottom: 24,
493
+ },
494
+ requirementTitle: {
495
+ fontSize: 14,
496
+ fontWeight: '600',
497
+ marginBottom: 8,
498
+ color: '#333',
499
+ },
500
+ requirement: {
501
+ fontSize: 14,
502
+ color: '#666',
503
+ marginBottom: 4,
504
+ },
505
+ requirementMet: {
506
+ color: '#34C759',
507
+ },
508
+ footer: {
509
+ flexDirection: 'row',
510
+ alignItems: 'center',
511
+ justifyContent: 'space-between',
512
+ marginTop: 24,
513
+ borderTopWidth: 1,
514
+ borderTopColor: '#eee',
515
+ paddingTop: 16,
516
+ },
517
+ cancelButton: {
518
+ paddingVertical: 8,
519
+ paddingHorizontal: 16,
520
+ },
521
+ cancelButtonText: {
522
+ color: '#666',
523
+ fontSize: 16,
524
+ },
525
+ submitButton: {
526
+ paddingVertical: 16,
527
+ paddingHorizontal: 32,
528
+ borderRadius: 16,
529
+ backgroundColor: '#000000',
530
+ alignItems: 'center',
531
+ },
532
+ submitButtonDisabled: {
533
+ opacity: 0.5,
534
+ },
535
+ submitButtonText: {
536
+ color: '#fff',
537
+ fontSize: 16,
538
+ fontWeight: '600',
539
+ },
540
+ // ✅ NEW: Background training progress styles
541
+ backgroundProgressContainer: {
542
+ flexDirection: 'row',
543
+ alignItems: 'center',
544
+ justifyContent: 'center',
545
+ marginBottom: 16,
546
+ },
547
+ backgroundProgressText: {
548
+ fontSize: 14,
549
+ color: '#666',
550
+ marginRight: 10,
551
+ },
552
+ backgroundProgressIndicator: {
553
+ padding: 5,
554
+ },
555
+ });