@transfergratis/react-native-sdk 0.1.22 → 0.1.24

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 (142) hide show
  1. package/android/src/main/AndroidManifest.xml +9 -4
  2. package/build/components/EnhancedCameraView.d.ts.map +1 -1
  3. package/build/components/EnhancedCameraView.js +26 -3
  4. package/build/components/EnhancedCameraView.js.map +1 -1
  5. package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
  6. package/build/components/EnhancedCameraView.web.js +21 -0
  7. package/build/components/EnhancedCameraView.web.js.map +1 -1
  8. package/build/components/KYCElements/CameraCapture.d.ts.map +1 -1
  9. package/build/components/KYCElements/CameraCapture.js +4 -3
  10. package/build/components/KYCElements/CameraCapture.js.map +1 -1
  11. package/build/components/KYCElements/CountrySelectionTemplate.d.ts +5 -2
  12. package/build/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  13. package/build/components/KYCElements/CountrySelectionTemplate.js +360 -101
  14. package/build/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  15. package/build/components/KYCElements/FileUpload.d.ts.map +1 -1
  16. package/build/components/KYCElements/FileUpload.js +5 -4
  17. package/build/components/KYCElements/FileUpload.js.map +1 -1
  18. package/build/components/KYCElements/FileUploadTemplate.d.ts.map +1 -1
  19. package/build/components/KYCElements/FileUploadTemplate.js +5 -4
  20. package/build/components/KYCElements/FileUploadTemplate.js.map +1 -1
  21. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  22. package/build/components/KYCElements/IDCardCapture.js +193 -237
  23. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  24. package/build/components/KYCElements/LocationCaptureTemplate.d.ts.map +1 -1
  25. package/build/components/KYCElements/LocationCaptureTemplate.js +78 -37
  26. package/build/components/KYCElements/LocationCaptureTemplate.js.map +1 -1
  27. package/build/components/KYCElements/OrientationVideoCapture.js +3 -2
  28. package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
  29. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +3 -2
  30. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
  31. package/build/components/KYCElements/OrientationVideoCaptureFinal.js +3 -2
  32. package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
  33. package/build/components/KYCElements/SelfieCapture.d.ts.map +1 -1
  34. package/build/components/KYCElements/SelfieCapture.js +4 -3
  35. package/build/components/KYCElements/SelfieCapture.js.map +1 -1
  36. package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  37. package/build/components/KYCElements/SelfieCaptureTemplate.js +182 -39
  38. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  39. package/build/components/KYCElements/WelcomeTemplate.d.ts +12 -0
  40. package/build/components/KYCElements/WelcomeTemplate.d.ts.map +1 -0
  41. package/build/components/KYCElements/WelcomeTemplate.js +243 -0
  42. package/build/components/KYCElements/WelcomeTemplate.js.map +1 -0
  43. package/build/components/TemplateKYCExample.d.ts +4 -2
  44. package/build/components/TemplateKYCExample.d.ts.map +1 -1
  45. package/build/components/TemplateKYCExample.js +5 -68
  46. package/build/components/TemplateKYCExample.js.map +1 -1
  47. package/build/components/TemplateKYCFlowRefactored.d.ts +2 -1
  48. package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  49. package/build/components/TemplateKYCFlowRefactored.js +95 -9
  50. package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
  51. package/build/components/example/DynamicTemplateExample.d.ts +10 -0
  52. package/build/components/example/DynamicTemplateExample.d.ts.map +1 -0
  53. package/build/components/example/DynamicTemplateExample.js +241 -0
  54. package/build/components/example/DynamicTemplateExample.js.map +1 -0
  55. package/build/config/allowedDomains.d.ts +30 -0
  56. package/build/config/allowedDomains.d.ts.map +1 -0
  57. package/build/config/allowedDomains.js +127 -0
  58. package/build/config/allowedDomains.js.map +1 -0
  59. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  60. package/build/hooks/useTemplateKYCFlow.js +68 -43
  61. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  62. package/build/hooks/useTemplateLoader.d.ts +14 -0
  63. package/build/hooks/useTemplateLoader.d.ts.map +1 -0
  64. package/build/hooks/useTemplateLoader.js +85 -0
  65. package/build/hooks/useTemplateLoader.js.map +1 -0
  66. package/build/i18n/en/index.d.ts +9 -0
  67. package/build/i18n/en/index.d.ts.map +1 -1
  68. package/build/i18n/en/index.js +9 -0
  69. package/build/i18n/en/index.js.map +1 -1
  70. package/build/i18n/fr/index.d.ts +9 -0
  71. package/build/i18n/fr/index.d.ts.map +1 -1
  72. package/build/i18n/fr/index.js +9 -0
  73. package/build/i18n/fr/index.js.map +1 -1
  74. package/build/index.d.ts +5 -0
  75. package/build/index.d.ts.map +1 -1
  76. package/build/index.js +8 -0
  77. package/build/index.js.map +1 -1
  78. package/build/modules/api/CardAuthentification.js +1 -0
  79. package/build/modules/api/CardAuthentification.js.map +1 -1
  80. package/build/modules/api/KYCService.d.ts +4 -1
  81. package/build/modules/api/KYCService.d.ts.map +1 -1
  82. package/build/modules/api/KYCService.js +17 -5
  83. package/build/modules/api/KYCService.js.map +1 -1
  84. package/build/modules/api/TemplateService.d.ts +45 -0
  85. package/build/modules/api/TemplateService.d.ts.map +1 -0
  86. package/build/modules/api/TemplateService.js +145 -0
  87. package/build/modules/api/TemplateService.js.map +1 -0
  88. package/build/modules/api/types.d.ts +1 -0
  89. package/build/modules/api/types.d.ts.map +1 -1
  90. package/build/modules/api/types.js.map +1 -1
  91. package/build/types/KYC.types.d.ts +144 -4
  92. package/build/types/KYC.types.d.ts.map +1 -1
  93. package/build/types/KYC.types.js +15 -0
  94. package/build/types/KYC.types.js.map +1 -1
  95. package/build/utils/cropByObb.d.ts +1 -0
  96. package/build/utils/cropByObb.d.ts.map +1 -1
  97. package/build/utils/cropByObb.js +70 -0
  98. package/build/utils/cropByObb.js.map +1 -1
  99. package/build/utils/platformAlert.d.ts +20 -0
  100. package/build/utils/platformAlert.d.ts.map +1 -0
  101. package/build/utils/platformAlert.js +67 -0
  102. package/build/utils/platformAlert.js.map +1 -0
  103. package/build/utils/template-transformer.d.ts +10 -0
  104. package/build/utils/template-transformer.d.ts.map +1 -0
  105. package/build/utils/template-transformer.js +353 -0
  106. package/build/utils/template-transformer.js.map +1 -0
  107. package/build/web/WebKYCEntry.d.ts.map +1 -1
  108. package/build/web/WebKYCEntry.js +102 -20
  109. package/build/web/WebKYCEntry.js.map +1 -1
  110. package/package.json +1 -1
  111. package/src/components/EnhancedCameraView.tsx +31 -2
  112. package/src/components/EnhancedCameraView.web.tsx +24 -0
  113. package/src/components/KYCElements/CameraCapture.tsx +4 -3
  114. package/src/components/KYCElements/CountrySelectionTemplate.tsx +410 -113
  115. package/src/components/KYCElements/FileUpload.tsx +5 -4
  116. package/src/components/KYCElements/FileUploadTemplate.tsx +5 -4
  117. package/src/components/KYCElements/IDCardCapture.tsx +196 -254
  118. package/src/components/KYCElements/LocationCaptureTemplate.tsx +95 -44
  119. package/src/components/KYCElements/OrientationVideoCapture.tsx +2 -2
  120. package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +2 -2
  121. package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +2 -2
  122. package/src/components/KYCElements/SelfieCapture.tsx +4 -3
  123. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +195 -41
  124. package/src/components/KYCElements/WelcomeTemplate.tsx +289 -0
  125. package/src/components/TemplateKYCExample.tsx +16 -71
  126. package/src/components/TemplateKYCFlowRefactored.tsx +121 -11
  127. package/src/components/example/DynamicTemplateExample.tsx +289 -0
  128. package/src/config/allowedDomains.ts +152 -0
  129. package/src/hooks/useTemplateKYCFlow.tsx +71 -46
  130. package/src/hooks/useTemplateLoader.ts +102 -0
  131. package/src/i18n/en/index.ts +10 -0
  132. package/src/i18n/fr/index.ts +9 -0
  133. package/src/index.ts +11 -0
  134. package/src/modules/api/CardAuthentification.ts +1 -1
  135. package/src/modules/api/KYCService.ts +18 -8
  136. package/src/modules/api/TemplateService.ts +167 -0
  137. package/src/modules/api/types.ts +1 -0
  138. package/src/types/KYC.types.ts +188 -3
  139. package/src/utils/cropByObb.ts +83 -3
  140. package/src/utils/platformAlert.ts +85 -0
  141. package/src/utils/template-transformer.ts +433 -0
  142. package/src/web/WebKYCEntry.tsx +122 -24
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect } from 'react';
2
- import { View, Text, StyleSheet, Alert } from 'react-native';
2
+ import { View, Text, StyleSheet, Platform } from 'react-native';
3
3
  import { LocationConfig, TemplateComponent } from '../../types/KYC.types';
4
4
  import ShieldIcon from '../Svgs/Schield';
5
5
  import GpsIcon from '../Svgs/GpsIcon';
@@ -7,6 +7,7 @@ import { Button } from '../ui/Button';
7
7
  import * as Location from 'expo-location';
8
8
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
9
9
  import { useI18n } from '../../hooks/useI18n';
10
+ import { showAlert } from '../../utils/platformAlert';
10
11
 
11
12
  interface LocationCaptureTemplateProps {
12
13
  component: TemplateComponent;
@@ -31,6 +32,12 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
31
32
 
32
33
  const requestLocationPermission = async (): Promise<boolean> => {
33
34
  try {
35
+ // On web, use browser's geolocation API directly
36
+ if (Platform.OS === 'web') {
37
+ return await requestWebGeolocationPermission();
38
+ }
39
+
40
+ // On native, use expo-location
34
41
  const { status } = await Location.requestForegroundPermissionsAsync();
35
42
  return status === 'granted';
36
43
  } catch (err) {
@@ -39,13 +46,42 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
39
46
  }
40
47
  };
41
48
 
49
+ const requestWebGeolocationPermission = async (): Promise<boolean> => {
50
+ if (!navigator.geolocation) {
51
+ console.error('Geolocation is not supported by this browser');
52
+ return false;
53
+ }
54
+
55
+ try {
56
+ // Check if permission API is available
57
+ if ('permissions' in navigator) {
58
+ const result = await navigator.permissions.query({ name: 'geolocation' as PermissionName });
59
+ return result.state === 'granted' || result.state === 'prompt';
60
+ }
61
+
62
+ // Fallback: assume we can request permission
63
+ return true;
64
+ } catch (err) {
65
+ console.warn('Could not check geolocation permission:', err);
66
+ // Assume we can try to request
67
+ return true;
68
+ }
69
+ };
70
+
42
71
  const getCurrentLocation = async () => {
43
72
  try {
44
73
  setIsRequesting(true);
45
74
 
75
+ // Web platform: use browser geolocation API
76
+ if (Platform.OS === 'web') {
77
+ await getWebGeolocation();
78
+ return;
79
+ }
80
+
81
+ // Native platform: use expo-location
46
82
  const hasLocationPermission = await requestLocationPermission();
47
83
  if (!hasLocationPermission) {
48
- Alert.alert(
84
+ showAlert(
49
85
  t('kyc.locationCapture.permissionDeniedTitle'),
50
86
  t('kyc.locationCapture.permissionDeniedMessage')
51
87
  );
@@ -56,7 +92,7 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
56
92
  // Vérifier si la localisation est activée
57
93
  const isLocationEnabled = await Location.hasServicesEnabledAsync();
58
94
  if (!isLocationEnabled) {
59
- Alert.alert(
95
+ showAlert(
60
96
  t('kyc.locationCapture.locationDisabledTitle'),
61
97
  t('kyc.locationCapture.locationDisabledMessage')
62
98
  );
@@ -83,15 +119,66 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
83
119
 
84
120
  } catch (error) {
85
121
  console.error('Erreur lors de la récupération de la localisation:', error);
86
- Alert.alert(t('kyc.locationCapture.errorTitle'), t('kyc.locationCapture.errorMessage'));
122
+ showAlert(t('kyc.locationCapture.errorTitle'), t('kyc.locationCapture.errorMessage'));
87
123
  } finally {
88
124
  setIsRequesting(false);
89
125
  }
90
126
  };
91
127
 
92
- const resetLocation = () => {
93
- setCurrentLocation(null);
94
- onValueChange(null);
128
+ const getWebGeolocation = async () => {
129
+ return new Promise<void>((resolve, reject) => {
130
+ if (!navigator.geolocation) {
131
+ showAlert(
132
+ t('kyc.locationCapture.errorTitle'),
133
+ 'Geolocation is not supported by your browser. Please use a modern browser or enable location services.'
134
+ );
135
+ reject(new Error('Geolocation not supported'));
136
+ return;
137
+ }
138
+
139
+ const options: PositionOptions = {
140
+ enableHighAccuracy: config.accuracy === 'high',
141
+ timeout: 10000,
142
+ maximumAge: 0,
143
+ };
144
+
145
+ navigator.geolocation.getCurrentPosition(
146
+ (position) => {
147
+ const locationData = {
148
+ latitude: position.coords.latitude,
149
+ longitude: position.coords.longitude,
150
+ accuracy: position.coords.accuracy,
151
+ };
152
+
153
+ setCurrentLocation(locationData);
154
+ onValueChange(locationData);
155
+ resolve();
156
+ },
157
+ (error) => {
158
+ console.error('Geolocation error:', error);
159
+
160
+ let errorMessage = t('kyc.locationCapture.errorMessage');
161
+
162
+ switch (error.code) {
163
+ case error.PERMISSION_DENIED:
164
+ errorMessage = Platform.OS === 'web'
165
+ ? 'Location permission denied. Please enable location access in your browser settings and try again.'
166
+ : t('kyc.locationCapture.permissionDeniedMessage');
167
+ break;
168
+ case error.POSITION_UNAVAILABLE:
169
+ errorMessage = 'Location information is unavailable. Please check your device settings.';
170
+ break;
171
+ case error.TIMEOUT:
172
+ errorMessage = 'Location request timed out. Please try again.';
173
+ break;
174
+ }
175
+
176
+ showAlert(t('kyc.locationCapture.errorTitle'), errorMessage);
177
+ reject(error);
178
+ },
179
+ options
180
+ );
181
+ });
95
182
  };
96
183
 
97
184
  // Passer automatiquement à l'étape suivante quand la localisation est obtenue
@@ -135,26 +222,7 @@ export const LocationCaptureTemplate: React.FC<LocationCaptureTemplateProps> = (
135
222
  </Text>
136
223
  </View>
137
224
 
138
- {currentLocation ? (
139
- <View style={styles.locationInfo}>
140
- <Text style={styles.locationTitle}>{t('kyc.locationCapture.obtained')}</Text>
141
- <Text style={styles.locationDetails}>
142
- {t('kyc.locationCapture.latitudeLabel')}: {currentLocation.latitude.toFixed(6)}
143
- </Text>
144
- <Text style={styles.locationDetails}>
145
- {t('kyc.locationCapture.longitudeLabel')}: {currentLocation.longitude.toFixed(6)}
146
- </Text>
147
- <Text style={styles.locationDetails}>
148
- {t('kyc.locationCapture.accuracyLabel')}: {currentLocation.accuracy.toFixed(1)}m
149
- </Text>
150
- <Button
151
- title={t('kyc.locationCapture.resetButton')}
152
- fullWidth
153
- onPress={resetLocation}
154
- style={{ paddingVertical: 20, marginTop: 24, backgroundColor: '#6B7280' }}
155
- />
156
- </View>
157
- ) : (
225
+ {!currentLocation && (
158
226
  <Button
159
227
  title={isRequesting ? t('kyc.locationCapture.fetching') : t('kyc.locationCapture.enableButton')}
160
228
  fullWidth
@@ -184,23 +252,6 @@ const styles = StyleSheet.create({
184
252
  shadowOpacity: 0.15,
185
253
  maxWidth: 760,
186
254
  },
187
- locationInfo: {
188
- alignItems: 'center',
189
- width: '100%',
190
- marginTop: 24,
191
- },
192
- locationTitle: {
193
- fontSize: 20,
194
- fontWeight: '600',
195
- color: '#2DBD60',
196
- marginBottom: 16,
197
- },
198
- locationDetails: {
199
- fontSize: 14,
200
- color: '#666',
201
- marginBottom: 4,
202
- fontFamily: 'monospace',
203
- },
204
255
  title: {
205
256
  fontSize: 24,
206
257
  fontWeight: 'bold',
@@ -4,10 +4,10 @@ import {
4
4
  Text,
5
5
  StyleSheet,
6
6
  TouchableOpacity,
7
- Alert,
8
7
  ActivityIndicator,
9
8
  Animated,
10
9
  } from 'react-native';
10
+ import { showAlert } from '../../utils/platformAlert';
11
11
  import Svg, { Rect, Path, Mask, Defs } from 'react-native-svg';
12
12
  import { Camera, useCameraDevice } from 'react-native-vision-camera';
13
13
  import { OrientationVideoConfig, OrientationVideoState } from '../../types/KYC.types';
@@ -204,7 +204,7 @@ export const OrientationVideoCapture: React.FC<OrientationVideoCaptureProps> = (
204
204
 
205
205
  const retake = () => {
206
206
  if (state.retakeCount >= finalConfig.maxRetakes) {
207
- Alert.alert('Max Retakes Reached', 'You have reached the maximum number of retakes.');
207
+ showAlert('Max Retakes Reached', 'You have reached the maximum number of retakes.');
208
208
  return;
209
209
  }
210
210
 
@@ -4,11 +4,11 @@ import {
4
4
  Text,
5
5
  StyleSheet,
6
6
  TouchableOpacity,
7
- Alert,
8
7
  ActivityIndicator,
9
8
  Animated,
10
9
  Dimensions,
11
10
  } from 'react-native';
11
+ import { showAlert } from '../../utils/platformAlert';
12
12
  import Svg, { Rect, Path, Mask, Defs } from 'react-native-svg';
13
13
  import { EnhancedCameraView } from '../EnhancedCameraView';
14
14
  import { OrientationVideoConfig, OrientationVideoState } from '../../types/KYC.types';
@@ -183,7 +183,7 @@ export const OrientationVideoCaptureEnhanced: React.FC<OrientationVideoCaptureEn
183
183
 
184
184
  const retake = () => {
185
185
  if (state.retakeCount >= finalConfig.maxRetakes) {
186
- Alert.alert('Max Retakes Reached', 'You have reached the maximum number of retakes.');
186
+ showAlert('Max Retakes Reached', 'You have reached the maximum number of retakes.');
187
187
  return;
188
188
  }
189
189
 
@@ -4,10 +4,10 @@ import {
4
4
  Text,
5
5
  StyleSheet,
6
6
  TouchableOpacity,
7
- Alert,
8
7
  ActivityIndicator,
9
8
  Animated,
10
9
  } from 'react-native';
10
+ import { showAlert } from '../../utils/platformAlert';
11
11
  import Svg, { Rect, Path, Mask, Defs } from 'react-native-svg';
12
12
  import { EnhancedCameraView } from '../EnhancedCameraView';
13
13
  import { OrientationVideoConfig, OrientationVideoState } from '../../types/KYC.types';
@@ -157,7 +157,7 @@ export const OrientationVideoCaptureFinal: React.FC<OrientationVideoCaptureFinal
157
157
 
158
158
  const retake = () => {
159
159
  if (state.retakeCount >= finalConfig.maxRetakes) {
160
- Alert.alert('Max Retakes Reached', 'You have reached the maximum number of retakes.');
160
+ showAlert('Max Retakes Reached', 'You have reached the maximum number of retakes.');
161
161
  return;
162
162
  }
163
163
 
@@ -1,5 +1,6 @@
1
1
  import React, { useState } from 'react';
2
- import { View, Text, TouchableOpacity, StyleSheet, Image, Alert } from 'react-native';
2
+ import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native';
3
+ import { showAlert } from '../../utils/platformAlert';
3
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
4
5
 
5
6
  import { KYCElement } from '../../types/KYC.types';
@@ -39,12 +40,12 @@ export const SelfieCapture: React.FC<SelfieCaptureProps> = ({
39
40
  onValueChange(result.path);
40
41
  setShowCamera(false);
41
42
  } else {
42
- Alert.alert('Erreur', result.error || 'Impossible de prendre le selfie');
43
+ showAlert('Erreur', result.error || 'Impossible de prendre le selfie');
43
44
  }
44
45
  };
45
46
 
46
47
  const handleError = (event: { message: string }) => {
47
- Alert.alert('Erreur', event.message);
48
+ showAlert('Erreur', event.message);
48
49
  setShowCamera(false);
49
50
  };
50
51
 
@@ -1,6 +1,7 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
- import { View, Text, StyleSheet, Alert, ScrollView } from 'react-native';
3
- import { TemplateComponent, SelfieConfig, LocalizedText, ISilentCaptureResult, OrientationType, GovernmentDocumentType } from '../../types/KYC.types';
2
+ import { View, Text, StyleSheet, ScrollView, Image, TouchableOpacity } from 'react-native';
3
+ import { showAlert } from '../../utils/platformAlert';
4
+ import { TemplateComponent, LocalizedText, ISilentCaptureResult, OrientationType, GovernmentDocumentType } from '../../types/KYC.types';
4
5
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
6
  import { Button } from '../ui/Button';
6
7
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
@@ -30,14 +31,15 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
30
31
  language,
31
32
  }) => {
32
33
  const { t } = useI18n();
33
- const config = component.config as SelfieConfig;
34
- const orientations: OrientationType[] = (config.orientations || ['center']) as OrientationType[];
34
+ // const config = component.config as SelfieConfig;
35
+ const orientations: OrientationType[] = (['center','left','right']) as OrientationType[];
35
36
  const { actions, state, } = useTemplateKYCFlowContext();
36
37
 
37
38
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
38
39
 
39
-
40
40
  const [showCamera, setShowCamera] = useState(false);
41
+ const [showPreview, setShowPreview] = useState(false);
42
+ const [previewImagePath, setPreviewImagePath] = useState<string | null>(null);
41
43
  const [currentOrientation, setCurrentOrientation] = useState<OrientationType>(orientations[0]);
42
44
  const [capturedImages, setCapturedImages] = useState<Record<string, IImagePayload>>(value || {});
43
45
 
@@ -78,7 +80,7 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
78
80
  case 'left':
79
81
  return state.currentLanguage === "en" ? "Left Profil Selfie" : "Selfie profil gauche";
80
82
  case 'right':
81
- return state.currentLanguage === "en" ? "Rigth Profil Selfiee" : "Selfie profil droit";
83
+ return state.currentLanguage === "en" ? "Right Profile Selfie" : "Selfie profil droit";
82
84
  default:
83
85
  return getLocalizedText(component.labels as LocalizedText);
84
86
  }
@@ -162,39 +164,80 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
162
164
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Le selfie n\'est pas correct' }));
163
165
  }
164
166
  }).catch((e: any) => {
165
-
166
167
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du selfie' }));
167
168
  });
168
169
  }
169
170
  }
170
171
 
172
+ // Capture manuelle - affiche le preview
171
173
  const handleCapture = async (result: { success: boolean; path?: string; error?: string }) => {
172
- console.log("handleCapturessss", silentCaptureResult);
174
+ console.log("handleCapture called", result, silentCaptureResult);
173
175
 
176
+ // Priorité 1: Si la capture silencieuse a réussi, utiliser ce path
174
177
  if (silentCaptureResult.success && silentCaptureResult.path) {
175
- const base64 = await pathToBase64(silentCaptureResult.path);
176
- // Keep backward-compatible structure for UI validation and add *_base64 fields for backend payload
178
+ setPreviewImagePath(silentCaptureResult.path);
179
+ setShowCamera(false);
180
+ setShowPreview(true);
181
+ return;
182
+ }
183
+
184
+ // Priorité 2: Capture manuelle normale
185
+ if (result.success && result.path) {
186
+ setPreviewImagePath(result.path);
187
+ setShowCamera(false);
188
+ setShowPreview(true);
189
+ }
190
+ };
191
+
192
+ // Confirmer la capture depuis le preview
193
+ const handleConfirmCapture = async () => {
194
+ if (!previewImagePath) return;
195
+
196
+ try {
197
+ const base64 = await pathToBase64(previewImagePath);
177
198
  const newImages: Record<string, any> = {
178
199
  ...capturedImages,
179
- [currentOrientation]: { dir: silentCaptureResult.path, file: base64 } as IImagePayload,
200
+ [currentOrientation]: { dir: previewImagePath, file: base64 } as IImagePayload,
180
201
  };
181
202
  setCapturedImages(newImages);
182
203
  onValueChange(newImages);
183
- setShowCamera(false);
184
-
204
+ setShowPreview(false);
205
+ setPreviewImagePath(null);
206
+ setSilentCaptureResult({ success: false, isAnalyzing: false });
185
207
 
186
208
  // Passer à l'orientation suivante si disponible
187
209
  const currentIndex = orientations.indexOf(currentOrientation as OrientationType);
188
210
  if (currentIndex < orientations.length - 1) {
189
- setCurrentOrientation(orientations[currentIndex + 1]);
211
+ const nextOrientation = orientations[currentIndex + 1];
212
+ setCurrentOrientation(nextOrientation);
213
+ // Rouvrir automatiquement la caméra pour la prochaine orientation
214
+ setShowCamera(true);
190
215
  }
216
+ // Si toutes les orientations sont complétées, on reste sur la vue principale
217
+ } catch (e) {
218
+ console.error("Error confirming capture", e);
219
+ showAlert('Erreur', 'Erreur lors de la sauvegarde de l\'image');
191
220
  }
192
221
  };
222
+
223
+ // Reprendre la photo depuis le preview
224
+ const handleRetakeFromPreview = () => {
225
+ setShowPreview(false);
226
+ setPreviewImagePath(null);
227
+ setSilentCaptureResult({ success: false, isAnalyzing: false });
228
+ setShowCamera(true);
229
+ };
193
230
  const idCardData = useMemo(() => {
194
231
  const idCardID = Object.keys(state.componentData).find((c: string) => c === "1");
195
232
  if (idCardID) {
196
233
  const _idCardData = state.componentData[idCardID];
197
- return _idCardData as unknown as { country: string; documentType: GovernmentDocumentType };
234
+ const documentType = _idCardData?.documentType;
235
+ // Map national_id to identity_card for selfie capture
236
+ const mappedDocumentType = documentType === 'national_id' ? 'identity_card' : documentType;
237
+ return {
238
+ country: _idCardData?.country,
239
+ documentType: mappedDocumentType as GovernmentDocumentType
240
+ };
198
241
  }
199
242
  return null;
200
243
  }, [state.componentData]);
@@ -202,7 +245,7 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
202
245
 
203
246
 
204
247
  const handleError = (event: { message: string }) => {
205
- Alert.alert('Erreur', event.message);
248
+ showAlert('Erreur', event.message);
206
249
  setShowCamera(false);
207
250
  };
208
251
 
@@ -216,7 +259,60 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
216
259
  };
217
260
  console.log("Current Orientation", currentOrientation);
218
261
 
262
+ // Vue Preview
263
+ if (showPreview && previewImagePath) {
264
+ return (
265
+ <View style={styles.containerCamera}>
266
+ <View style={styles.previewContainer}>
267
+ <View style={styles.previewHeader}>
268
+ <TouchableOpacity onPress={handleRetakeFromPreview} style={styles.backButton}>
269
+ <Text style={styles.backButtonText}>←</Text>
270
+ </TouchableOpacity>
271
+ <Text style={styles.previewTitle}>
272
+ {state.currentLanguage === "en" ? "Preview" : "Aperçu"}
273
+ </Text>
274
+ <View style={{ width: 40 }} />
275
+ </View>
276
+
277
+ <View style={styles.previewImageContainer}>
278
+ <Image
279
+ source={{ uri: previewImagePath }}
280
+ style={styles.previewImage}
281
+ resizeMode="contain"
282
+ />
283
+ </View>
284
+
285
+ <Text style={styles.previewInstructions}>
286
+ {state.currentLanguage === "en"
287
+ ? "Is your face clearly visible?"
288
+ : "Votre visage est-il clairement visible ?"}
289
+ </Text>
290
+
291
+ <View style={styles.previewButtonsContainer}>
292
+ <TouchableOpacity
293
+ style={styles.retakeButton}
294
+ onPress={handleRetakeFromPreview}
295
+ >
296
+ <Text style={styles.retakeButtonText}>
297
+ {state.currentLanguage === "en" ? "Retake" : "Reprendre"}
298
+ </Text>
299
+ </TouchableOpacity>
300
+
301
+ <TouchableOpacity
302
+ style={styles.confirmButton}
303
+ onPress={handleConfirmCapture}
304
+ >
305
+ <Text style={styles.confirmButtonText}>
306
+ {state.currentLanguage === "en" ? "Use Photo" : "Utiliser"}
307
+ </Text>
308
+ </TouchableOpacity>
309
+ </View>
310
+ </View>
311
+ </View>
312
+ );
313
+ }
219
314
 
315
+ // Vue Camera
220
316
  if (showCamera) {
221
317
  return (
222
318
  <View style={styles.containerCamera}>
@@ -322,18 +418,13 @@ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
322
418
  <View style={{ height: 10 }} />
323
419
  {isAllOrientationsCompleted() ? <>
324
420
  <Button title={t('common.continue')} fullWidth style={{ paddingVertical: 20, paddingTop: 12 }} onPress={async () => {
325
- if (idCardData?.country && idCardData?.documentType) {
326
- const value = {
327
- ...capturedImages,
328
- country: idCardData?.country || '',
329
- documentType: idCardData?.documentType as GovernmentDocumentType,
330
- }
331
- console.log("value", JSON.stringify(truncateFields(value), null, 2));
332
- onValueChange(value);
333
- } else {
334
- Alert.alert('Erreur', 'Veuillez sélectionner un pays et un type de document');
335
- return;
421
+ const value = {
422
+ ...capturedImages,
423
+ ...(idCardData?.country ? { country: idCardData.country } : {}),
424
+ ...(idCardData?.documentType ? { documentType: idCardData.documentType } : {}),
336
425
  }
426
+ console.log("value", JSON.stringify(truncateFields(value), null, 2));
427
+ onValueChange(value);
337
428
  actions.nextComponent();
338
429
  }} />
339
430
  </> : (
@@ -370,23 +461,17 @@ const styles = StyleSheet.create({
370
461
  borderRadius: 20,
371
462
  paddingVertical: 20,
372
463
  paddingHorizontal: 16,
373
- // shadow
374
464
  shadowColor: '#000',
375
465
  shadowOffset: { width: 0, height: 2 },
376
466
  shadowOpacity: 0.35,
377
467
  shadowRadius: 4.84,
378
468
  elevation: 10,
379
-
380
-
381
- // padding: 16,
382
469
  },
383
470
  containerCamera: {
384
471
  flex: 1,
385
- // maxWidth: 760,
386
472
  width: '100%',
387
473
  height: '100%',
388
- // borderRadius: 12,
389
- backgroundColor: '#f5f5f5',
474
+ backgroundColor: '#000',
390
475
  },
391
476
  title: {
392
477
  fontSize: 24,
@@ -404,7 +489,6 @@ const styles = StyleSheet.create({
404
489
  },
405
490
  camera: {
406
491
  flex: 1,
407
- // borderRadius: 12,
408
492
  overflow: 'hidden',
409
493
  },
410
494
  attemptsText: {
@@ -414,7 +498,6 @@ const styles = StyleSheet.create({
414
498
  marginBottom: 10,
415
499
  },
416
500
  orientationsContainer: {
417
- // flex: 1,
418
501
  },
419
502
  orientationContainer: {
420
503
  backgroundColor: 'white',
@@ -434,7 +517,6 @@ const styles = StyleSheet.create({
434
517
  fontSize: 18,
435
518
  fontWeight: '600',
436
519
  color: '#333',
437
- // marginBottom: 12,
438
520
  },
439
521
  capturedImageContainer: {
440
522
  alignItems: 'center',
@@ -464,14 +546,86 @@ const styles = StyleSheet.create({
464
546
  fontWeight: '600',
465
547
  color: 'white',
466
548
  },
549
+ // Preview styles
550
+ previewContainer: {
551
+ flex: 1,
552
+ backgroundColor: '#000',
553
+ justifyContent: 'space-between',
554
+ },
555
+ previewHeader: {
556
+ flexDirection: 'row',
557
+ alignItems: 'center',
558
+ justifyContent: 'space-between',
559
+ paddingHorizontal: 16,
560
+ paddingTop: 50,
561
+ paddingBottom: 16,
562
+ },
563
+ backButton: {
564
+ width: 40,
565
+ height: 40,
566
+ borderRadius: 20,
567
+ backgroundColor: 'rgba(255,255,255,0.2)',
568
+ justifyContent: 'center',
569
+ alignItems: 'center',
570
+ },
571
+ backButtonText: {
572
+ color: 'white',
573
+ fontSize: 24,
574
+ fontWeight: 'bold',
575
+ },
576
+ previewTitle: {
577
+ color: 'white',
578
+ fontSize: 18,
579
+ fontWeight: '600',
580
+ },
581
+ previewImageContainer: {
582
+ flex: 1,
583
+ justifyContent: 'center',
584
+ alignItems: 'center',
585
+ paddingHorizontal: 20,
586
+ },
587
+ previewImage: {
588
+ width: '100%',
589
+ height: '80%',
590
+ borderRadius: 16,
591
+ },
592
+ previewInstructions: {
593
+ color: 'white',
594
+ fontSize: 16,
595
+ textAlign: 'center',
596
+ paddingHorizontal: 20,
597
+ marginBottom: 20,
598
+ },
599
+ previewButtonsContainer: {
600
+ flexDirection: 'row',
601
+ justifyContent: 'space-between',
602
+ paddingHorizontal: 20,
603
+ paddingBottom: 40,
604
+ gap: 16,
605
+ },
467
606
  retakeButton: {
468
- backgroundColor: '#FF6B6B',
469
- paddingVertical: 12,
607
+ flex: 1,
608
+ backgroundColor: 'rgba(255,255,255,0.2)',
609
+ paddingVertical: 16,
470
610
  paddingHorizontal: 20,
471
- borderRadius: 8,
611
+ borderRadius: 12,
612
+ alignItems: 'center',
472
613
  },
473
614
  retakeButtonText: {
474
- fontSize: 14,
615
+ fontSize: 16,
616
+ fontWeight: '600',
617
+ color: 'white',
618
+ },
619
+ confirmButton: {
620
+ flex: 1,
621
+ backgroundColor: '#2DBD60',
622
+ paddingVertical: 16,
623
+ paddingHorizontal: 20,
624
+ borderRadius: 12,
625
+ alignItems: 'center',
626
+ },
627
+ confirmButtonText: {
628
+ fontSize: 16,
475
629
  fontWeight: '600',
476
630
  color: 'white',
477
631
  },