@sanctum-key/react-native-sdk 1.0.48 → 1.0.50

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 (58) hide show
  1. package/build/package.json +1 -1
  2. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  3. package/build/src/components/KYCElements/CountrySelection.js +33 -108
  4. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  5. package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/CountrySelectionTemplate.js +50 -164
  7. package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  8. package/build/src/components/KYCElements/DocumentTypeSelection.d.ts.map +1 -1
  9. package/build/src/components/KYCElements/DocumentTypeSelection.js +32 -109
  10. package/build/src/components/KYCElements/DocumentTypeSelection.js.map +1 -1
  11. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  12. package/build/src/components/KYCElements/IDCardCapture.js +58 -25
  13. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  14. package/build/src/components/KYCElements/InitializationStep.d.ts.map +1 -1
  15. package/build/src/components/KYCElements/InitializationStep.js +13 -1
  16. package/build/src/components/KYCElements/InitializationStep.js.map +1 -1
  17. package/build/src/components/KYCElements/LocationCaptureTemplate.d.ts.map +1 -1
  18. package/build/src/components/KYCElements/LocationCaptureTemplate.js +46 -157
  19. package/build/src/components/KYCElements/LocationCaptureTemplate.js.map +1 -1
  20. package/build/src/components/KYCElements/SelfieCapture.d.ts.map +1 -1
  21. package/build/src/components/KYCElements/SelfieCapture.js +28 -24
  22. package/build/src/components/KYCElements/SelfieCapture.js.map +1 -1
  23. package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
  24. package/build/src/components/KYCElements/VerificationProgressTemplate.js +31 -40
  25. package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  26. package/build/src/components/SelfieCapture.d.ts.map +1 -1
  27. package/build/src/components/SelfieCapture.js +33 -90
  28. package/build/src/components/SelfieCapture.js.map +1 -1
  29. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  30. package/build/src/components/TemplateKYCExample.js +69 -120
  31. package/build/src/components/TemplateKYCExample.js.map +1 -1
  32. package/build/src/components/TemplateKYCFlowRefactored.d.ts +0 -2
  33. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  34. package/build/src/components/TemplateKYCFlowRefactored.js +95 -210
  35. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  36. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  37. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  38. package/build/src/types/KYC.types.d.ts.map +1 -1
  39. package/build/src/types/KYC.types.js.map +1 -1
  40. package/build/src/utils/logger.d.ts +12 -4
  41. package/build/src/utils/logger.d.ts.map +1 -1
  42. package/build/src/utils/logger.js +156 -28
  43. package/build/src/utils/logger.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/components/KYCElements/CountrySelection.tsx +36 -109
  46. package/src/components/KYCElements/CountrySelectionTemplate.tsx +67 -266
  47. package/src/components/KYCElements/DocumentTypeSelection.tsx +34 -110
  48. package/src/components/KYCElements/IDCardCapture.tsx +93 -39
  49. package/src/components/KYCElements/InitializationStep.tsx +17 -2
  50. package/src/components/KYCElements/LocationCaptureTemplate.tsx +53 -170
  51. package/src/components/KYCElements/SelfieCapture.tsx +50 -58
  52. package/src/components/KYCElements/VerificationProgressTemplate.tsx +38 -45
  53. package/src/components/SelfieCapture.tsx +44 -100
  54. package/src/components/TemplateKYCExample.tsx +94 -145
  55. package/src/components/TemplateKYCFlowRefactored.tsx +105 -239
  56. package/src/hooks/useTemplateKYCFlow.tsx +1 -0
  57. package/src/types/KYC.types.ts +0 -1
  58. package/src/utils/logger.ts +182 -29
@@ -8,6 +8,7 @@ import * as Location from 'expo-location';
8
8
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
9
9
  import { useI18n } from '../../hooks/useI18n';
10
10
  import { showAlert } from '../../utils/platformAlert';
11
+ import { sdkLogger } from '../../utils/logger'; // 🚨 IMPORT SDK LOGGER
11
12
 
12
13
  interface LocationCaptureTemplateProps {
13
14
  component: TemplateComponent;
@@ -27,17 +28,21 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
27
28
  const { t } = useI18n();
28
29
  const [isRequesting, setIsRequesting] = useState(false);
29
30
  const [currentLocation, setCurrentLocation] = useState<{ latitude: number; longitude: number; accuracy: number } | null>(value || null);
30
- const { actions } = useTemplateKYCFlowContext();
31
+ const { actions, state } = useTemplateKYCFlowContext();
31
32
  const config = component.config as LocationConfig;
32
33
 
34
+ // 🚨 LOG: Track when the user reaches this step
35
+ useEffect(() => {
36
+ if (state.session?.session_id) {
37
+ sdkLogger.info("User started location capture step");
38
+ }
39
+ }, [state.session?.session_id]);
40
+
33
41
  const requestLocationPermission = async (): Promise<boolean> => {
34
42
  try {
35
- // On web, use browser's geolocation API directly
36
43
  if (Platform.OS === 'web') {
37
44
  return await requestWebGeolocationPermission();
38
45
  }
39
-
40
- // On native, use expo-location
41
46
  const { status } = await Location.requestForegroundPermissionsAsync();
42
47
  return status === 'granted';
43
48
  } catch (err) {
@@ -48,39 +53,32 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
48
53
 
49
54
  const requestWebGeolocationPermission = async (): Promise<boolean> => {
50
55
  if (!navigator.geolocation) {
51
- console.error('Geolocation is not supported by this browser');
52
56
  return false;
53
57
  }
54
-
55
58
  try {
56
- // Check if permission API is available
57
59
  if ('permissions' in navigator) {
58
60
  const result = await navigator.permissions.query({ name: 'geolocation' as PermissionName });
59
61
  return result.state === 'granted' || result.state === 'prompt';
60
62
  }
61
-
62
- // Fallback: assume we can request permission
63
63
  return true;
64
64
  } catch (err) {
65
- console.warn('Could not check geolocation permission:', err);
66
- // Assume we can try to request
67
65
  return true;
68
66
  }
69
67
  };
70
68
 
71
69
  const getCurrentLocation = async () => {
72
70
  try {
71
+ sdkLogger.info("User initiated location request"); // 🚨 LOG
73
72
  setIsRequesting(true);
74
73
 
75
- // Web platform: use browser geolocation API
76
74
  if (Platform.OS === 'web') {
77
75
  await getWebGeolocation();
78
76
  return;
79
77
  }
80
78
 
81
- // Native platform: use expo-location
82
79
  const hasLocationPermission = await requestLocationPermission();
83
80
  if (!hasLocationPermission) {
81
+ sdkLogger.warn("Location permission denied by user"); // 🚨 LOG
84
82
  showAlert(
85
83
  t('kyc.locationCapture.permissionDeniedTitle'),
86
84
  t('kyc.locationCapture.permissionDeniedMessage')
@@ -89,9 +87,9 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
89
87
  return;
90
88
  }
91
89
 
92
- // Vérifier si la localisation est activée
93
90
  const isLocationEnabled = await Location.hasServicesEnabledAsync();
94
91
  if (!isLocationEnabled) {
92
+ sdkLogger.warn("Location services are disabled on device"); // 🚨 LOG
95
93
  showAlert(
96
94
  t('kyc.locationCapture.locationDisabledTitle'),
97
95
  t('kyc.locationCapture.locationDisabledMessage')
@@ -100,7 +98,6 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
100
98
  return;
101
99
  }
102
100
 
103
- // Récupérer la localisation actuelle
104
101
  const location = await Location.getCurrentPositionAsync({
105
102
  accuracy: config.accuracy === 'high' ? Location.Accuracy.High : Location.Accuracy.Balanced,
106
103
  timeInterval: 5000,
@@ -113,12 +110,13 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
113
110
  accuracy: location.coords.accuracy || 50,
114
111
  };
115
112
 
116
- // Mettre à jour l'état local et passer la valeur au parent
113
+ // 🚨 LOG: Success (coordinates omitted for privacy)
114
+ sdkLogger.info("Location successfully captured", { accuracy: locationData.accuracy, platform: 'native' });
115
+
117
116
  setCurrentLocation(locationData);
118
117
  onValueChange(locationData);
119
-
120
- } catch (error) {
121
- console.error('Erreur lors de la récupération de la localisation:', error);
118
+ } catch (error: any) {
119
+ sdkLogger.error("Failed to capture native location", { error: error?.message }); // 🚨 LOG
122
120
  showAlert(t('kyc.locationCapture.errorTitle'), t('kyc.locationCapture.errorMessage'));
123
121
  } finally {
124
122
  setIsRequesting(false);
@@ -128,6 +126,7 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
128
126
  const getWebGeolocation = async () => {
129
127
  return new Promise<void>((resolve, reject) => {
130
128
  if (!navigator.geolocation) {
129
+ sdkLogger.error("Web Geolocation not supported by browser"); // 🚨 LOG
131
130
  showAlert(
132
131
  t('kyc.locationCapture.errorTitle'),
133
132
  'Geolocation is not supported by your browser. Please use a modern browser or enable location services.'
@@ -149,19 +148,21 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
149
148
  longitude: position.coords.longitude,
150
149
  accuracy: position.coords.accuracy,
151
150
  };
151
+
152
+ // 🚨 LOG: Success (coordinates omitted for privacy)
153
+ sdkLogger.info("Location successfully captured", { accuracy: locationData.accuracy, platform: 'web' });
152
154
 
153
155
  setCurrentLocation(locationData);
154
156
  onValueChange(locationData);
155
157
  resolve();
156
158
  },
157
159
  (error) => {
158
- console.error('Geolocation error:', error);
159
-
160
+ sdkLogger.error("Web Geolocation error", { errorCode: error.code, errorMessage: error.message }); // 🚨 LOG
161
+
160
162
  let errorMessage = t('kyc.locationCapture.errorMessage');
161
-
162
163
  switch (error.code) {
163
164
  case error.PERMISSION_DENIED:
164
- errorMessage = Platform.OS === 'web'
165
+ errorMessage = Platform.OS === 'web'
165
166
  ? 'Location permission denied. Please enable location access in your browser settings and try again.'
166
167
  : t('kyc.locationCapture.permissionDeniedMessage');
167
168
  break;
@@ -172,7 +173,6 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
172
173
  errorMessage = 'Location request timed out. Please try again.';
173
174
  break;
174
175
  }
175
-
176
176
  showAlert(t('kyc.locationCapture.errorTitle'), errorMessage);
177
177
  reject(error);
178
178
  },
@@ -181,25 +181,20 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
181
181
  });
182
182
  };
183
183
 
184
- // Passer automatiquement à l'étape suivante quand la localisation est obtenue
185
184
  useEffect(() => {
186
185
  if (currentLocation && !isRequesting) {
187
- // Délai court pour permettre à l'utilisateur de voir la localisation obtenue
188
186
  const timer = setTimeout(() => {
189
187
  actions.nextComponent();
190
188
  }, 1500);
191
-
192
189
  return () => clearTimeout(timer);
193
190
  }
194
191
  return undefined;
195
192
  }, [currentLocation, isRequesting, actions]);
196
193
 
197
-
198
-
199
194
  return (
200
195
  <View style={styles.container}>
201
196
  <View style={{ flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
202
- <View style={{ position: 'relative', width: 70, }}>
197
+ <View style={{ position: 'relative', width: 70 }}>
203
198
  <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', width: 60, height: 60, backgroundColor: '#2DBE60', borderRadius: 100 }}>
204
199
  <ShieldIcon />
205
200
  </View>
@@ -207,13 +202,15 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
207
202
  <GpsIcon />
208
203
  </View>
209
204
  </View>
205
+
210
206
  <View style={{ flexDirection: 'column', alignItems: 'center', justifyContent: 'center', marginTop: 36 }}>
211
207
  <Text style={{ fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8 }}>{t('kyc.locationCapture.title')}</Text>
212
208
  <Text style={{ fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 }}>
213
209
  {t('kyc.locationCapture.description')}
214
210
  </Text>
215
211
  </View>
216
- <View style={{ flexDirection: 'column', marginTop: 36, backgroundColor: "#F9FAFB", padding: 12, borderRadius: 12, width: '100%', }}>
212
+
213
+ <View style={{ flexDirection: 'column', marginTop: 36, backgroundColor: "#F9FAFB", padding: 12, borderRadius: 12, width: '100%' }}>
217
214
  <Text style={{ fontSize: 16, color: 'black', fontWeight: 'bold', textAlign: 'left', lineHeight: 22 }}>
218
215
  {t('kyc.locationCapture.howThisHelps')}
219
216
  </Text>
@@ -231,148 +228,34 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
231
228
  style={{ paddingVertical: 20, marginTop: 36 }}
232
229
  />
233
230
  )}
234
-
235
- <View style={{
236
- height: 12,
237
- }} />
231
+ <View style={{ height: 12 }} />
238
232
  </View>
239
233
  </View>
240
234
  );
241
235
  };
242
236
 
243
237
  const styles = StyleSheet.create({
244
- container: {
245
- padding: 16,
246
- backgroundColor: 'white',
247
- marginHorizontal: 12,
248
- marginTop: 12,
249
- borderRadius: 12,
250
- shadowColor: '#000',
251
- shadowOffset: { width: 0, height: 2 },
252
- shadowOpacity: 0.15,
253
- maxWidth: 760,
254
- },
255
- title: {
256
- fontSize: 24,
257
- fontWeight: 'bold',
258
- color: '#333',
259
- marginBottom: 8,
260
- textAlign: 'center',
261
- },
262
- description: {
263
- fontSize: 16,
264
- color: '#666',
265
- textAlign: 'center',
266
- marginBottom: 24,
267
- lineHeight: 22,
268
- },
269
- locationContainer: {
270
- flex: 1,
271
- justifyContent: 'center',
272
- alignItems: 'center',
273
- padding: 24,
274
- },
275
- iconContainer: {
276
- marginBottom: 24,
277
- },
278
- locationIcon: {
279
- fontSize: 80,
280
- },
281
- locationRequest: {
282
- alignItems: 'center',
283
- width: '100%',
284
- },
285
- locationRequestText: {
286
- fontSize: 16,
287
- color: '#666',
288
- textAlign: 'center',
289
- marginBottom: 24,
290
- },
291
- locationButton: {
292
- paddingHorizontal: 32,
293
- paddingVertical: 16,
294
- borderRadius: 8,
295
- minWidth: 200,
296
- alignItems: 'center',
297
- },
298
- locationButtonDisabled: {
299
- opacity: 0.6,
300
- },
301
- locationButtonText: {
302
- color: 'white',
303
- fontWeight: '600',
304
- fontSize: 16,
305
- },
306
- buttonContainer: {
307
- flexDirection: 'row',
308
- justifyContent: 'space-around',
309
- alignItems: 'center',
310
- marginTop: 24,
311
- width: '100%',
312
- },
313
- primaryButton: {
314
- paddingHorizontal: 32,
315
- paddingVertical: 16,
316
- borderRadius: 8,
317
- minWidth: 120,
318
- alignItems: 'center',
319
- },
320
- primaryButtonText: {
321
- color: 'white',
322
- fontWeight: '600',
323
- fontSize: 16,
324
- },
325
- secondaryButton: {
326
- backgroundColor: '#e5e5e5',
327
- paddingHorizontal: 32,
328
- paddingVertical: 16,
329
- borderRadius: 8,
330
- minWidth: 120,
331
- alignItems: 'center',
332
- },
333
- secondaryButtonText: {
334
- color: '#333',
335
- fontWeight: '600',
336
- fontSize: 16,
337
- },
338
- requiredContainer: {
339
- backgroundColor: '#fff3cd',
340
- padding: 16,
341
- borderRadius: 8,
342
- marginTop: 16,
343
- },
344
- requiredText: {
345
- fontSize: 16,
346
- fontWeight: '600',
347
- color: '#856404',
348
- marginBottom: 8,
349
- },
350
- requiredSubtext: {
351
- fontSize: 14,
352
- color: '#856404',
353
- lineHeight: 20,
354
- },
355
- accuracyContainer: {
356
- backgroundColor: '#f0f9f0',
357
- padding: 16,
358
- borderRadius: 8,
359
- marginTop: 16,
360
- },
361
- accuracyTitle: {
362
- fontSize: 16,
363
- fontWeight: '600',
364
- color: '#2DBD60',
365
- marginBottom: 8,
366
- },
367
- accuracyText: {
368
- fontSize: 14,
369
- color: '#666',
370
- lineHeight: 20,
371
- },
372
- errorText: {
373
- color: '#dc2626',
374
- fontSize: 14,
375
- marginTop: 8,
376
- textAlign: 'center',
377
- },
378
- });
238
+ container: { padding: 16, backgroundColor: 'white', marginHorizontal: 12, marginTop: 12, borderRadius: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, maxWidth: 760 },
239
+ title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
240
+ description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
241
+ locationContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24 },
242
+ iconContainer: { marginBottom: 24 },
243
+ locationIcon: { fontSize: 80 },
244
+ locationRequest: { alignItems: 'center', width: '100%' },
245
+ locationRequestText: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24 },
246
+ locationButton: { paddingHorizontal: 32, paddingVertical: 16, borderRadius: 8, minWidth: 200, alignItems: 'center' },
247
+ locationButtonDisabled: { opacity: 0.6 },
248
+ locationButtonText: { color: 'white', fontWeight: '600', fontSize: 16 },
249
+ buttonContainer: { flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', marginTop: 24, width: '100%' },
250
+ primaryButton: { paddingHorizontal: 32, paddingVertical: 16, borderRadius: 8, minWidth: 120, alignItems: 'center' },
251
+ primaryButtonText: { color: 'white', fontWeight: '600', fontSize: 16 },
252
+ secondaryButton: { backgroundColor: '#e5e5e5', paddingHorizontal: 32, paddingVertical: 16, borderRadius: 8, minWidth: 120, alignItems: 'center' },
253
+ secondaryButtonText: { color: '#333', fontWeight: '600', fontSize: 16 },
254
+ requiredContainer: { backgroundColor: '#fff3cd', padding: 16, borderRadius: 8, marginTop: 16 },
255
+ requiredText: { fontSize: 16, fontWeight: '600', color: '#856404', marginBottom: 8 },
256
+ requiredSubtext: { fontSize: 14, color: '#856404', lineHeight: 20 },
257
+ accuracyContainer: { backgroundColor: '#f0f9f0', padding: 16, borderRadius: 8, marginTop: 16 },
258
+ accuracyTitle: { fontSize: 16, fontWeight: '600', color: '#2DBD60', marginBottom: 8 },
259
+ accuracyText: { fontSize: 14, color: '#666', lineHeight: 20 },
260
+ errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
261
+ });
@@ -1,9 +1,11 @@
1
- import React, { useState } from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { View, Text, TouchableOpacity, StyleSheet, Image, ActivityIndicator, Platform } from 'react-native';
3
3
  import { showAlert } from '../../utils/platformAlert';
4
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
- import SelfieOverlay from '../OverLay/SelfieOverlay';
5
+ import SelfieOverlay from '../OverLay/SelfieOverlay';
6
6
  import { KYCElement, ISilentCaptureResult } from '../../types/KYC.types';
7
+ import { sdkLogger } from '../../utils/logger';
8
+ import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
7
9
 
8
10
  interface SelfieCaptureProps {
9
11
  element: KYCElement;
@@ -20,11 +22,16 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
20
22
  }) => {
21
23
  const [capturedImage, setCapturedImage] = useState<string | null>(value || null);
22
24
  const [showCamera, setShowCamera] = useState(false);
23
-
24
- // 🚨 AUTO-CAPTURE STATES
25
- const [isProcessingCapture, setIsProcessingCapture] = useState(false);
25
+ const [isProcessingCapture, setIsProcessingCapture] = useState(false);
26
26
  const [processingImagePath, setProcessingImagePath] = useState<string | null>(null);
27
27
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
28
+ const { state } = useTemplateKYCFlowContext();
29
+
30
+ useEffect(() => {
31
+ if (state.session?.session_id) {
32
+ sdkLogger.info("User started selfie verification step");
33
+ }
34
+ }, [state.session?.session_id]);
28
35
 
29
36
  const cameraConfig = element.cameraConfig || {
30
37
  aspectRatio: 1,
@@ -33,25 +40,25 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
33
40
  cameraType: 'front',
34
41
  allowRetake: true,
35
42
  maxRetakes: 3,
36
- overlay: {
37
- showGuide: true,
38
- guideText: 'Placez votre visage dans l\'ovale',
39
- }
43
+ overlay: { showGuide: true, guideText: 'Placez votre visage dans l\'ovale' }
40
44
  };
41
45
 
42
46
  // --- THE AUTO CAPTURE LOGIC ---
43
47
  const autoCapture = async (capturePath: string) => {
44
48
  if (isProcessingCapture) return;
45
49
 
50
+ sdkLogger.info("Valid selfie frame detected, processing image...");
51
+
46
52
  // 1. Freeze UI and show the captured selfie
47
- setIsProcessingCapture(true);
53
+ setIsProcessingCapture(true);
48
54
  setProcessingImagePath(capturePath);
49
55
 
50
56
  try {
51
57
  setCapturedImage(capturePath);
52
-
53
58
  // Save data to parent component
54
- await onValueChange(capturePath);
59
+ await onValueChange(capturePath);
60
+
61
+ sdkLogger.info("Selfie successfully captured and saved");
55
62
 
56
63
  // 2. Short delay for UX success feeling
57
64
  setTimeout(() => {
@@ -60,8 +67,8 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
60
67
  setIsProcessingCapture(false);
61
68
  setProcessingImagePath(null);
62
69
  }, 800);
63
-
64
70
  } catch (e: any) {
71
+ sdkLogger.error("Failed to process and save selfie", { error: e?.message }); // 🚨 LOG
65
72
  showAlert('Erreur', e?.message || 'Impossible de sauvegarder le selfie');
66
73
  setSilentCaptureResult({ success: false, isAnalyzing: false });
67
74
  setIsProcessingCapture(false);
@@ -75,28 +82,25 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
75
82
 
76
83
  if (result.success && result.path) {
77
84
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
78
-
79
85
  try {
80
86
  // Since camera returned success (clear frame), trigger auto-capture immediately
81
87
  await autoCapture(result.path);
82
88
  } catch (error: any) {
83
- setSilentCaptureResult(prev => ({
84
- ...prev,
85
- isAnalyzing: false,
86
- success: false,
87
- error: error?.message || 'Visage non détecté'
88
- }));
89
+ sdkLogger.warn("Selfie auto-capture evaluation failed", { error: error?.message }); // 🚨 LOG
90
+ setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: error?.message || 'Visage non détecté' }));
89
91
  }
90
92
  }
91
93
  };
92
94
 
93
95
  const handleError = (event: { message: string }) => {
96
+ sdkLogger.error("Selfie camera hardware/permission error", { error: event.message }); // 🚨 LOG
94
97
  showAlert('Erreur', event.message);
95
98
  setShowCamera(false);
96
99
  setIsProcessingCapture(false);
97
100
  };
98
101
 
99
102
  const retakePicture = () => {
103
+ sdkLogger.info("User chose to retake selfie");
100
104
  setIsProcessingCapture(false);
101
105
  setProcessingImagePath(null);
102
106
  setSilentCaptureResult({ success: false, isAnalyzing: false });
@@ -107,13 +111,12 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
107
111
 
108
112
  // --- LIVE CAMERA RENDER ---
109
113
  if (showCamera) {
110
- const isBusy = isProcessingCapture;
111
-
114
+ const isBusy = isProcessingCapture;
112
115
  return (
113
116
  <View style={styles.cameraContainerFullScreen}>
114
117
  <EnhancedCameraView
115
118
  showCamera={true}
116
- isProcessing={isBusy}
119
+ isProcessing={isBusy}
117
120
  cameraType={cameraConfig.cameraType as 'front' | 'back'}
118
121
  style={styles.camera}
119
122
  onError={handleError}
@@ -123,43 +126,39 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
123
126
  <>
124
127
  {/* Subtle scanning indicator */}
125
128
  {!isBusy && silentCaptureResult.isAnalyzing && (
126
- <View style={styles.topAnalyzingPillContainer}>
127
- <View style={styles.topAnalyzingPill}>
128
- <ActivityIndicator size="small" color="white" />
129
- <Text style={styles.analyzingPillText}>Analyse du visage...</Text>
130
- </View>
131
- </View>
129
+ <View style={styles.topAnalyzingPillContainer}>
130
+ <View style={styles.topAnalyzingPill}>
131
+ <ActivityIndicator size="small" color="white" />
132
+ <Text style={styles.analyzingPillText}>Analyse du visage...</Text>
133
+ </View>
134
+ </View>
132
135
  )}
133
136
 
134
137
  {/* Success Overlay with Frozen Perfect Selfie */}
135
138
  {isBusy && (
136
139
  <View style={StyleSheet.absoluteFillObject}>
137
140
  {processingImagePath && (
138
- <Image
139
- source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }}
140
- style={StyleSheet.absoluteFillObject}
141
- resizeMode="cover"
141
+ <Image
142
+ source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }}
143
+ style={StyleSheet.absoluteFillObject}
144
+ resizeMode="cover"
142
145
  />
143
146
  )}
144
147
  <View style={styles.processingOverlay}>
145
148
  <ActivityIndicator size="large" color="#2DBD60" />
146
- <Text style={styles.processingText}>
147
- Parfait!{"\n"}Traitement du selfie...
148
- </Text>
149
+ <Text style={styles.processingText}> Parfait!{"\n"}Traitement du selfie... </Text>
149
150
  </View>
150
151
  </View>
151
152
  )}
152
153
 
153
154
  {!isBusy && (
154
- <SelfieOverlay
155
- xMin={0} yMin={0} xMax={100} yMax={100}
156
- orientation="center"
157
- isSuccess={isBusy}
158
- instructionsTile={element.title || "Selfie"}
159
- instructionsSubtitle={cameraConfig.overlay?.guideText || "Placez votre visage dans l'ovale"}
160
- cornerOpacity={1}
161
- instructions=""
162
- language="en"
155
+ <SelfieOverlay
156
+ xMin={0} yMin={0} xMax={100} yMax={100}
157
+ orientation="center"
158
+ isSuccess={isBusy}
159
+ instructionsTile={element.title || "Selfie"}
160
+ instructionsSubtitle={cameraConfig.overlay?.guideText || "Placez votre visage dans l'ovale"}
161
+ cornerOpacity={1} instructions="" language="en"
163
162
  />
164
163
  )}
165
164
  </>
@@ -176,12 +175,11 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
176
175
  );
177
176
  }
178
177
 
179
- // --- PREVIEW RENDER ---
180
178
  return (
181
179
  <View style={styles.container}>
182
180
  <Text style={styles.title}>{element.title}</Text>
183
181
  <Text style={styles.description}>{element.description}</Text>
184
-
182
+
185
183
  {capturedImage ? (
186
184
  <>
187
185
  <View style={styles.imageContainer}>
@@ -202,8 +200,11 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
202
200
  ) : (
203
201
  <View style={styles.cameraPlaceholderWrapper}>
204
202
  <TouchableOpacity
205
- style={styles.cameraPlaceholder}
206
- onPress={() => setShowCamera(true)}
203
+ style={styles.cameraPlaceholder}
204
+ onPress={() => {
205
+ sdkLogger.info("User opened selfie camera"); // 🚨 LOG
206
+ setShowCamera(true);
207
+ }}
207
208
  >
208
209
  <Text style={styles.cameraPlaceholderText}>🤳</Text>
209
210
  <Text style={styles.cameraPlaceholderLabel}>
@@ -219,37 +220,28 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
219
220
  };
220
221
 
221
222
  const styles = StyleSheet.create({
222
- // 🚨 UI FIX: The camera MUST be full screen so your SelfieOverlay math matches the device screen
223
223
  cameraContainerFullScreen: { ...StyleSheet.absoluteFillObject, backgroundColor: 'black', zIndex: 999 },
224
224
  camera: { flex: 1 },
225
-
226
225
  container: { flex: 1, padding: 16, height: 500 },
227
226
  title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
228
227
  description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
229
-
230
228
  cameraPlaceholderWrapper: { flex: 1, borderRadius: 12, overflow: 'hidden', marginBottom: 24 },
231
229
  imageContainer: { flex: 1, borderRadius: 12, overflow: 'hidden', marginBottom: 24, backgroundColor: '#000' },
232
230
  capturedImage: { width: '100%', height: '100%', resizeMode: 'cover' },
233
-
234
231
  buttonContainer: { flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', paddingHorizontal: 16, marginBottom: 24 },
235
232
  primaryButton: { backgroundColor: '#2DBD60', paddingHorizontal: 32, paddingVertical: 16, borderRadius: 12 },
236
233
  primaryButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16 },
237
234
  secondaryButton: { backgroundColor: '#e5e5e5', paddingHorizontal: 32, paddingVertical: 16, borderRadius: 12 },
238
235
  secondaryButtonText: { color: '#333', fontWeight: 'bold', fontSize: 16 },
239
-
240
236
  errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
241
-
242
237
  cameraPlaceholder: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5', borderRadius: 12, borderWidth: 2, borderColor: '#e0e0e0', borderStyle: 'dashed' },
243
238
  cameraPlaceholderText: { fontSize: 50, marginBottom: 12 },
244
239
  cameraPlaceholderLabel: { fontSize: 16, color: '#666', textAlign: 'center', fontWeight: '500' },
245
-
246
240
  topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
247
241
  topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
248
242
  analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' },
249
-
250
243
  processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.7)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
251
244
  processingText: { color: '#FFF', fontSize: 20, fontWeight: 'bold', marginTop: 16, textAlign: 'center', lineHeight: 28 },
252
-
253
245
  floatingErrorBanner: { position: 'absolute', top: 100, left: '10%', right: '10%', backgroundColor: 'rgba(220, 38, 38, 0.95)', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 8, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 5, elevation: 8, zIndex: 100 },
254
246
  floatingErrorText: { color: 'white', fontSize: 14, fontWeight: '700', textAlign: 'center' },
255
247
  });