@transfergratis/react-native-sdk 0.1.23 → 0.1.25

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/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +12 -5
  2. package/android/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar +0 -0
  3. package/android/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +0 -0
  4. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  5. package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
  6. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +61 -59
  7. package/android/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-transfergratis-react-native-sdk.jar +0 -0
  8. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +12 -5
  9. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  10. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  11. package/android/build/outputs/aar/transfergratis-react-native-sdk-debug.aar +0 -0
  12. package/android/build/outputs/logs/manifest-merger-debug-report.txt +26 -34
  13. package/android/src/main/AndroidManifest.xml +13 -5
  14. package/build/components/EnhancedCameraView.d.ts.map +1 -1
  15. package/build/components/EnhancedCameraView.js +26 -3
  16. package/build/components/EnhancedCameraView.js.map +1 -1
  17. package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
  18. package/build/components/EnhancedCameraView.web.js +21 -0
  19. package/build/components/EnhancedCameraView.web.js.map +1 -1
  20. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts +12 -0
  21. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts.map +1 -0
  22. package/build/components/KYCElements/AdditionalDocumentsTemplate.js +283 -0
  23. package/build/components/KYCElements/AdditionalDocumentsTemplate.js.map +1 -0
  24. package/build/components/KYCElements/CameraCapture.d.ts.map +1 -1
  25. package/build/components/KYCElements/CameraCapture.js +4 -3
  26. package/build/components/KYCElements/CameraCapture.js.map +1 -1
  27. package/build/components/KYCElements/CountrySelectionTemplate.d.ts +5 -2
  28. package/build/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  29. package/build/components/KYCElements/CountrySelectionTemplate.js +360 -101
  30. package/build/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  31. package/build/components/KYCElements/EmailVerificationTemplate.d.ts +12 -0
  32. package/build/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -0
  33. package/build/components/KYCElements/EmailVerificationTemplate.js +193 -0
  34. package/build/components/KYCElements/EmailVerificationTemplate.js.map +1 -0
  35. package/build/components/KYCElements/FileUpload.d.ts.map +1 -1
  36. package/build/components/KYCElements/FileUpload.js +5 -4
  37. package/build/components/KYCElements/FileUpload.js.map +1 -1
  38. package/build/components/KYCElements/FileUploadTemplate.d.ts.map +1 -1
  39. package/build/components/KYCElements/FileUploadTemplate.js +5 -4
  40. package/build/components/KYCElements/FileUploadTemplate.js.map +1 -1
  41. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  42. package/build/components/KYCElements/IDCardCapture.js +356 -227
  43. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  44. package/build/components/KYCElements/LocationCaptureTemplate.d.ts.map +1 -1
  45. package/build/components/KYCElements/LocationCaptureTemplate.js +78 -37
  46. package/build/components/KYCElements/LocationCaptureTemplate.js.map +1 -1
  47. package/build/components/KYCElements/OrientationVideoCapture.d.ts +2 -0
  48. package/build/components/KYCElements/OrientationVideoCapture.d.ts.map +1 -1
  49. package/build/components/KYCElements/OrientationVideoCapture.js +5 -4
  50. package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
  51. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts +2 -0
  52. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts.map +1 -1
  53. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +5 -4
  54. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
  55. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts +2 -0
  56. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts.map +1 -1
  57. package/build/components/KYCElements/OrientationVideoCaptureFinal.js +5 -4
  58. package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
  59. package/build/components/KYCElements/PersonalInformationTemplate.d.ts +12 -0
  60. package/build/components/KYCElements/PersonalInformationTemplate.d.ts.map +1 -0
  61. package/build/components/KYCElements/PersonalInformationTemplate.js +120 -0
  62. package/build/components/KYCElements/PersonalInformationTemplate.js.map +1 -0
  63. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts +12 -0
  64. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -0
  65. package/build/components/KYCElements/PhoneVerificationTemplate.js +185 -0
  66. package/build/components/KYCElements/PhoneVerificationTemplate.js.map +1 -0
  67. package/build/components/KYCElements/SelfieCapture.d.ts.map +1 -1
  68. package/build/components/KYCElements/SelfieCapture.js +4 -3
  69. package/build/components/KYCElements/SelfieCapture.js.map +1 -1
  70. package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  71. package/build/components/KYCElements/SelfieCaptureTemplate.js +189 -42
  72. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  73. package/build/components/KYCElements/WelcomeTemplate.d.ts +12 -0
  74. package/build/components/KYCElements/WelcomeTemplate.d.ts.map +1 -0
  75. package/build/components/KYCElements/WelcomeTemplate.js +243 -0
  76. package/build/components/KYCElements/WelcomeTemplate.js.map +1 -0
  77. package/build/components/TemplateKYCExample.d.ts +8 -2
  78. package/build/components/TemplateKYCExample.d.ts.map +1 -1
  79. package/build/components/TemplateKYCExample.js +10 -97
  80. package/build/components/TemplateKYCExample.js.map +1 -1
  81. package/build/components/TemplateKYCFlowRefactored.d.ts +6 -1
  82. package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  83. package/build/components/TemplateKYCFlowRefactored.js +108 -11
  84. package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
  85. package/build/components/example/DynamicTemplateExample.d.ts +10 -0
  86. package/build/components/example/DynamicTemplateExample.d.ts.map +1 -0
  87. package/build/components/example/DynamicTemplateExample.js +241 -0
  88. package/build/components/example/DynamicTemplateExample.js.map +1 -0
  89. package/build/config/KYCConfig.d.ts +14 -0
  90. package/build/config/KYCConfig.d.ts.map +1 -0
  91. package/build/config/KYCConfig.js +26 -0
  92. package/build/config/KYCConfig.js.map +1 -0
  93. package/build/config/allowedDomains.d.ts +30 -0
  94. package/build/config/allowedDomains.d.ts.map +1 -0
  95. package/build/config/allowedDomains.js +112 -0
  96. package/build/config/allowedDomains.js.map +1 -0
  97. package/build/hooks/useOrientationVideo.d.ts +2 -1
  98. package/build/hooks/useOrientationVideo.d.ts.map +1 -1
  99. package/build/hooks/useOrientationVideo.js +3 -3
  100. package/build/hooks/useOrientationVideo.js.map +1 -1
  101. package/build/hooks/useTemplateKYCFlow.d.ts +6 -1
  102. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  103. package/build/hooks/useTemplateKYCFlow.js +317 -34
  104. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  105. package/build/hooks/useTemplateLoader.d.ts +14 -0
  106. package/build/hooks/useTemplateLoader.d.ts.map +1 -0
  107. package/build/hooks/useTemplateLoader.js +85 -0
  108. package/build/hooks/useTemplateLoader.js.map +1 -0
  109. package/build/i18n/en/index.d.ts +49 -0
  110. package/build/i18n/en/index.d.ts.map +1 -1
  111. package/build/i18n/en/index.js +50 -1
  112. package/build/i18n/en/index.js.map +1 -1
  113. package/build/i18n/fr/index.d.ts +35 -0
  114. package/build/i18n/fr/index.d.ts.map +1 -1
  115. package/build/i18n/fr/index.js +36 -1
  116. package/build/i18n/fr/index.js.map +1 -1
  117. package/build/index.d.ts +6 -0
  118. package/build/index.d.ts.map +1 -1
  119. package/build/index.js +10 -0
  120. package/build/index.js.map +1 -1
  121. package/build/modules/api/CardAuthentification.d.ts +24 -3
  122. package/build/modules/api/CardAuthentification.d.ts.map +1 -1
  123. package/build/modules/api/CardAuthentification.js +69 -10
  124. package/build/modules/api/CardAuthentification.js.map +1 -1
  125. package/build/modules/api/KYCService.d.ts +7 -7
  126. package/build/modules/api/KYCService.d.ts.map +1 -1
  127. package/build/modules/api/KYCService.js +108 -39
  128. package/build/modules/api/KYCService.js.map +1 -1
  129. package/build/modules/api/SelfieVerification.d.ts +3 -1
  130. package/build/modules/api/SelfieVerification.d.ts.map +1 -1
  131. package/build/modules/api/SelfieVerification.js +17 -1
  132. package/build/modules/api/SelfieVerification.js.map +1 -1
  133. package/build/modules/api/TemplateService.d.ts +44 -0
  134. package/build/modules/api/TemplateService.d.ts.map +1 -0
  135. package/build/modules/api/TemplateService.js +145 -0
  136. package/build/modules/api/TemplateService.js.map +1 -0
  137. package/build/types/KYC.types.d.ts +265 -4
  138. package/build/types/KYC.types.d.ts.map +1 -1
  139. package/build/types/KYC.types.js +15 -0
  140. package/build/types/KYC.types.js.map +1 -1
  141. package/build/types/env.types.d.ts +13 -0
  142. package/build/types/env.types.d.ts.map +1 -0
  143. package/build/types/env.types.js +2 -0
  144. package/build/types/env.types.js.map +1 -0
  145. package/build/utils/cropByObb.d.ts +1 -0
  146. package/build/utils/cropByObb.d.ts.map +1 -1
  147. package/build/utils/cropByObb.js +70 -0
  148. package/build/utils/cropByObb.js.map +1 -1
  149. package/build/utils/deviceDetection.d.ts +6 -0
  150. package/build/utils/deviceDetection.d.ts.map +1 -0
  151. package/build/utils/deviceDetection.js +12 -0
  152. package/build/utils/deviceDetection.js.map +1 -0
  153. package/build/utils/platformAlert.d.ts +20 -0
  154. package/build/utils/platformAlert.d.ts.map +1 -0
  155. package/build/utils/platformAlert.js +67 -0
  156. package/build/utils/platformAlert.js.map +1 -0
  157. package/build/utils/template-transformer.d.ts +10 -0
  158. package/build/utils/template-transformer.d.ts.map +1 -0
  159. package/build/utils/template-transformer.js +365 -0
  160. package/build/utils/template-transformer.js.map +1 -0
  161. package/build/web/WebKYCEntry.d.ts.map +1 -1
  162. package/build/web/WebKYCEntry.js +158 -32
  163. package/build/web/WebKYCEntry.js.map +1 -1
  164. package/package.json +1 -1
  165. package/plugin/build/withVisionCamera.js +3 -4
  166. package/plugin/src/withVisionCamera.js +3 -4
  167. package/plugin/src/withVisionCamera.ts +3 -4
  168. package/src/components/EnhancedCameraView.tsx +31 -2
  169. package/src/components/EnhancedCameraView.web.tsx +24 -0
  170. package/src/components/KYCElements/AdditionalDocumentsTemplate.tsx +346 -0
  171. package/src/components/KYCElements/CameraCapture.tsx +4 -3
  172. package/src/components/KYCElements/CountrySelectionTemplate.tsx +410 -113
  173. package/src/components/KYCElements/EmailVerificationTemplate.tsx +264 -0
  174. package/src/components/KYCElements/FileUpload.tsx +5 -4
  175. package/src/components/KYCElements/FileUploadTemplate.tsx +5 -4
  176. package/src/components/KYCElements/IDCardCapture.tsx +397 -254
  177. package/src/components/KYCElements/LocationCaptureTemplate.tsx +95 -44
  178. package/src/components/KYCElements/OrientationVideoCapture.tsx +6 -3
  179. package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +6 -3
  180. package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +6 -3
  181. package/src/components/KYCElements/PersonalInformationTemplate.tsx +158 -0
  182. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +253 -0
  183. package/src/components/KYCElements/SelfieCapture.tsx +4 -3
  184. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +201 -44
  185. package/src/components/KYCElements/WelcomeTemplate.tsx +289 -0
  186. package/src/components/TemplateKYCExample.tsx +37 -108
  187. package/src/components/TemplateKYCFlowRefactored.tsx +148 -12
  188. package/src/components/example/DynamicTemplateExample.tsx +289 -0
  189. package/src/config/KYCConfig.ts +34 -0
  190. package/src/config/allowedDomains.ts +133 -0
  191. package/src/hooks/useOrientationVideo.ts +5 -4
  192. package/src/hooks/useTemplateKYCFlow.tsx +347 -32
  193. package/src/hooks/useTemplateLoader.ts +102 -0
  194. package/src/i18n/en/index.ts +53 -2
  195. package/src/i18n/fr/index.ts +37 -1
  196. package/src/index.ts +14 -0
  197. package/src/modules/api/CardAuthentification.ts +76 -11
  198. package/src/modules/api/KYCService.ts +129 -45
  199. package/src/modules/api/SelfieVerification.ts +25 -3
  200. package/src/modules/api/TemplateService.ts +167 -0
  201. package/src/types/KYC.types.ts +331 -3
  202. package/src/types/env.types.ts +13 -0
  203. package/src/utils/cropByObb.ts +83 -3
  204. package/src/utils/deviceDetection.ts +11 -0
  205. package/src/utils/platformAlert.ts +86 -0
  206. package/src/utils/template-transformer.ts +445 -0
  207. package/src/web/WebKYCEntry.tsx +199 -50
@@ -1,6 +1,8 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { View, Text, TouchableOpacity, StyleSheet, Image, Alert, ScrollView, Dimensions } from 'react-native';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, Modal, TouchableOpacity } from 'react-native';
3
+ import { showAlert } from '../../utils/platformAlert';
3
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
+ import { GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
4
6
  import IdCardOverlay from '../OverLay/IdCard';
5
7
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
6
8
  import { useI18n } from '../../hooks/useI18n';
@@ -9,8 +11,9 @@ import { removeDuplicates } from '../../utils/remove-duplicate';
9
11
  import { backVerification, checkTemplateType, frontVerification } from '../../modules/api/CardAuthentification';
10
12
  import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
11
13
  import pathToBase64 from '../../utils/pathToBase64';
12
- import { truncateFields } from '../../modules/api/KYCService';
13
- import { cropByObb, cropImageWithBBox } from '../../utils/cropByObb';
14
+ import kycService, { truncateFields } from '../../modules/api/KYCService';
15
+ import { cropByObb, cropImageWithBBox, cropImageWithBBoxWithTolerance } from '../../utils/cropByObb';
16
+ import { isMobileWeb } from '../../utils/deviceDetection';
14
17
  import { logger } from '../../utils/logger';
15
18
  export const IDCardCapture = ({ component, value = {}, onValueChange, error, language = 'en', }) => {
16
19
  const { t, locale } = useI18n();
@@ -20,18 +23,18 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
20
23
  const [currentSide, setCurrentSide] = useState('front');
21
24
  // Stocker les bbox par côté pour pouvoir restaurer les images croppées
22
25
  const [bboxBySide, setBboxBySide] = useState({});
23
- const [selectedDocumentType, setSelectedDocumentType] = useState({
24
- type: 'identity_card',
25
- region: 'root'
26
- });
27
- const [showDocumentSelection, setShowDocumentSelection] = useState({
28
- documentSelection: true,
29
- regionSelection: false
30
- });
31
26
  const [silentCaptureResult, setSilentCaptureResult] = useState({ success: false, isAnalyzing: false });
27
+ const [showQRModal, setShowQRModal] = useState(false);
28
+ // Mapping des types de documents backend vers SDK
29
+ const documentTypeMapping = {
30
+ 'nationalId': 'national_id',
31
+ 'passport': 'passport',
32
+ 'driversLicense': 'drivers_licence',
33
+ 'residencePermit': 'permanent_residence',
34
+ 'healthInsuranceCard': 'health_insurance_card',
35
+ };
32
36
  // const [imageNaturalSize, setImageNaturalSize] = useState<{ width: number; height: number } | null>(null);
33
- const { actions, state, } = useTemplateKYCFlowContext();
34
- const config = component.config;
37
+ const { actions, state, env } = useTemplateKYCFlowContext();
35
38
  const getLocalizedText = (text) => {
36
39
  // console.log("text", text, JSON.stringify(component, null, 2));
37
40
  if (text && typeof text[currentSide] === 'object' && text[currentSide][locale]) {
@@ -39,27 +42,54 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
39
42
  }
40
43
  return "";
41
44
  };
42
- const countryData = useMemo(() => {
43
- const geCountryID = Object.keys(state.componentData).find((c) => c === "6");
44
- if (geCountryID) {
45
- const countryMapping = state.componentData[geCountryID];
46
- return countryMapping;
45
+ // Récupérer les données depuis le composant country_selection
46
+ const countrySelectionData = useMemo(() => {
47
+ const countrySelectionComponent = state.template.components.find(c => c.type === 'country_selection');
48
+ if (countrySelectionComponent) {
49
+ return state.componentData[countrySelectionComponent.id];
47
50
  }
48
51
  return null;
49
- }, [state.componentData]);
50
- const availableDocumentTypes = useMemo(() => {
51
- const newconfig = [];
52
- if (countryData) {
53
- const countryMapping = Object.keys(countryData?.regionMapping);
54
- if (countryMapping && Array.isArray(countryMapping)) {
55
- countryMapping.forEach((c) => {
56
- newconfig.push(c);
52
+ }, [state.template.components, state.componentData]);
53
+ // Extraire selectedDocumentType depuis countrySelectionData
54
+ const selectedDocumentType = useMemo(() => {
55
+ if (!countrySelectionData?.documentType)
56
+ return null;
57
+ const backendDocType = countrySelectionData.documentType;
58
+ const mappedType = documentTypeMapping[backendDocType] || backendDocType;
59
+ const region = countrySelectionData.region || 'root';
60
+ return { type: mappedType, region };
61
+ }, [countrySelectionData, documentTypeMapping]);
62
+ // Récupérer countryData pour compatibilité avec le code existant
63
+ const countryData = useMemo(() => {
64
+ return countrySelectionData;
65
+ }, [countrySelectionData]);
66
+ // Synchroniser capturedImages avec value quand les données sont chargées (ex: reprise de session)
67
+ useEffect(() => {
68
+ if (value && Object.keys(value).length > 0) {
69
+ // Vérifier si les données ont changé
70
+ const valueChanged = JSON.stringify(value) !== JSON.stringify(capturedImages);
71
+ if (valueChanged) {
72
+ logger.log("Updating capturedImages from value:", Object.keys(value));
73
+ logger.log("Value data sample:", truncateFields(value));
74
+ const updatedImages = value;
75
+ setCapturedImages(updatedImages);
76
+ // Si on a des images chargées, mettre à jour silentCaptureResult pour l'affichage
77
+ Object.keys(updatedImages).forEach((side) => {
78
+ const imageData = updatedImages[side];
79
+ if (imageData?.dir) {
80
+ setSilentCaptureResult(prev => ({
81
+ ...prev,
82
+ path: imageData.dir,
83
+ success: true,
84
+ isAnalyzing: false,
85
+ mrz: imageData.mrz || '',
86
+ templatePath: imageData.templatePath || '',
87
+ }));
88
+ }
57
89
  });
58
90
  }
59
91
  }
60
- logger.log("newconfig", truncateFields(newconfig));
61
- return newconfig;
62
- }, []);
92
+ }, [value]);
63
93
  useEffect(() => {
64
94
  logger.log("cropImageUri", JSON.stringify(truncateFields({ box: silentCaptureResult }), null, 2));
65
95
  if (capturedImages[currentSide]?.dir && silentCaptureResult?.bbox) {
@@ -68,27 +98,32 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
68
98
  });
69
99
  }
70
100
  }, [capturedImages[currentSide]?.dir, silentCaptureResult?.bbox]);
71
- const cameraConfig = {
72
- aspectRatio: 4 / 3,
73
- quality: 0.8,
74
- flashMode: 'auto',
75
- cameraType: 'back',
76
- allowRetake: true,
77
- maxRetakes: 3,
78
- overlay: {
79
- showGuide: true,
80
- guideText: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions),
81
- bbox: selectedDocumentType && config.bbox_configs[selectedDocumentType.type] ? config.bbox_configs[selectedDocumentType.type] : {
82
- xMin: 20,
83
- yMin: 140,
84
- xMax: 370,
85
- yMax: 340,
86
- borderColor: '#2DBD60',
87
- borderWidth: 3,
88
- cornerRadius: 8
101
+ const cameraConfig = useMemo(() => {
102
+ const instructions = selectedDocumentType
103
+ ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
104
+ : getLocalizedText(component.instructions);
105
+ return {
106
+ aspectRatio: 4 / 3,
107
+ quality: 0.8,
108
+ flashMode: 'auto',
109
+ cameraType: 'back',
110
+ allowRetake: true,
111
+ maxRetakes: 3,
112
+ overlay: {
113
+ showGuide: true,
114
+ guideText: instructions,
115
+ bbox: {
116
+ xMin: 20,
117
+ yMin: 140,
118
+ xMax: 370,
119
+ yMax: 340,
120
+ borderColor: '#2DBD60',
121
+ borderWidth: 3,
122
+ cornerRadius: 8
123
+ }
89
124
  }
90
- }
91
- };
125
+ };
126
+ }, [selectedDocumentType, locale, component.instructions]);
92
127
  const retakePicture = (currentSide) => {
93
128
  setShowCamera(true);
94
129
  actions.showCustomStepper(false);
@@ -97,26 +132,14 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
97
132
  setCropImageUri('');
98
133
  onValueChange({ ...value, [currentSide]: { dir: '', file: '', mrz: '' } });
99
134
  };
100
- const hasRegions = useCallback((documentType) => {
101
- const regionMapping = countryData?.regionMapping[documentType];
102
- if (regionMapping && Object.keys(regionMapping).length > 1) {
103
- return {
104
- hasRegions: true,
105
- regionMapping: Object.keys(regionMapping)
106
- };
107
- }
108
- return {
109
- hasRegions: false,
110
- regionMapping: []
111
- };
112
- }, [countryData]);
113
135
  const getCurrentSideVerification = (currentSide) => {
114
- const regionMapping = countryData?.regionMapping[selectedDocumentType?.type];
136
+ if (!selectedDocumentType || !countryData?.regionMapping) {
137
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
138
+ }
139
+ const regionMapping = countryData.regionMapping[selectedDocumentType.type];
115
140
  const authMethod = [];
116
141
  const mrzTypes = [];
117
- const key = selectedDocumentType?.region?.trim()?.length > 0 ? selectedDocumentType?.region?.trim() : 'root';
118
- // console.log("regionMapping", JSON.stringify(regionMapping, null, 2), selectedDocumentType?.region);
119
- // const key = selectedDocumentType?.region as keyof typeof regionMapping;
142
+ const key = selectedDocumentType.region?.trim()?.length > 0 ? selectedDocumentType.region.trim() : 'root';
120
143
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
121
144
  regionMapping[key].forEach((item) => {
122
145
  if (item[currentSide]) {
@@ -127,11 +150,11 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
127
150
  }
128
151
  });
129
152
  }
130
- logger.log("regionMapping", JSON.stringify(truncateFields({ regionMapping, selectedDocumentType: selectedDocumentType?.region, key }), null, 2));
153
+ logger.log("regionMapping", JSON.stringify(truncateFields({ regionMapping, selectedDocumentType: selectedDocumentType.region, key }), null, 2));
131
154
  return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping: regionMapping, key: key };
132
155
  };
133
156
  const getCorrespondingMrzType = (templatePath, mapping, selectedDocumentType = "root") => {
134
- if (!mapping[selectedDocumentType])
157
+ if (!mapping || !mapping[selectedDocumentType])
135
158
  return null;
136
159
  // Extraire le nom du fichier depuis le template_path
137
160
  const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
@@ -151,11 +174,20 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
151
174
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
152
175
  let templatePath = silentCaptureResult.templatePath || '';
153
176
  let templateBbox;
177
+ if (!selectedDocumentType) {
178
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
179
+ return;
180
+ }
154
181
  const regionMappings = getCurrentSideVerification(currentSide);
155
182
  // logger.log("regionMappings", JSON.stringify(truncateFields({regionMappings, templatePath}), null, 2));
156
183
  if (templatePath.length === 0) {
157
184
  try {
158
- const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type, docRegion: countryData?.code || "", postfix: currentSide });
185
+ logger.log("checkTemplateType - BEFORE call", {
186
+ selectedDocumentTypeType: selectedDocumentType?.type,
187
+ countrySelectionDataDocumentType: countrySelectionData?.documentType,
188
+ docTypeToSend: selectedDocumentType?.type
189
+ });
190
+ const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type, docRegion: countryData?.code || "", postfix: currentSide }, env);
159
191
  if (templateType.template_path) {
160
192
  templatePath = templateType.template_path;
161
193
  logger.log("templatePath", templatePath);
@@ -174,57 +206,87 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
174
206
  catch (e) {
175
207
  logger.log("error checking template type", truncateFields(e));
176
208
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du template' }));
209
+ return; // Return early if checkTemplateType fails
177
210
  }
178
211
  }
179
- logger.log("templatePath before front verification", templatePath);
212
+ logger.log("templatePath before verification", templatePath, "currentSide", currentSide);
180
213
  if (currentSide === 'front') {
181
- frontVerification({
182
- path: result.path,
183
- regionMapping: regionMappings,
184
- selectedDocumentType: selectedDocumentType?.type || '',
185
- code: countryData?.code || '',
186
- currentSide: currentSide,
187
- templatePath: templatePath,
188
- mrzType: getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '',
189
- }).then((mrz) => {
190
- logger.log("front verification result", truncateFields(mrz));
191
- const bbox = mrz?.bbox || templateBbox;
192
- setSilentCaptureResult((prev) => ({
193
- ...prev,
214
+ logger.log("frontVerification - BEFORE call", {
215
+ selectedDocumentTypeType: selectedDocumentType?.type,
216
+ countrySelectionDataDocumentType: countrySelectionData?.documentType,
217
+ docTypeToSend: selectedDocumentType?.type
218
+ });
219
+ logger.log("Calling frontVerification", { templatePath, selectedDocumentType: selectedDocumentType?.type, regionMappings });
220
+ console.log("About to call frontVerification", typeof frontVerification);
221
+ try {
222
+ const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
223
+ console.log("mrzType calculated", mrzType);
224
+ const verificationParams = {
194
225
  path: result.path,
226
+ regionMapping: {
227
+ authMethod: regionMappings.authMethod,
228
+ mrzTypes: regionMappings.mrzTypes,
229
+ },
230
+ selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type] || '',
231
+ code: countryData?.code || '',
232
+ currentSide: currentSide,
195
233
  templatePath: templatePath,
196
- bbox: bbox, success: true,
197
- mrz: JSON.stringify(mrz), isAnalyzing: false,
198
- country: countryData?.code,
199
- documentType: selectedDocumentType?.type,
200
- }));
201
- // Stocker le bbox pour ce côté
202
- if (bbox && typeof bbox === 'object') {
203
- setBboxBySide((prev) => ({ ...prev, [currentSide]: bbox }));
204
- }
205
- }).catch((e) => {
206
- logger.log("error front verification", truncateFields(e));
207
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
208
- // Alert.alert('Erreur', e?.message || 'Erreur de détection du MRZ');
209
- });
234
+ mrzType: mrzType,
235
+ };
236
+ console.log("frontVerification params", verificationParams);
237
+ console.log("About to call frontVerification function");
238
+ const promise = frontVerification(verificationParams, env);
239
+ console.log("frontVerification promise created", promise);
240
+ promise.then((mrz) => {
241
+ logger.log("front verification result", truncateFields(mrz));
242
+ const bbox = mrz?.bbox || templateBbox;
243
+ setSilentCaptureResult((prev) => ({
244
+ ...prev,
245
+ path: result.path,
246
+ templatePath: templatePath,
247
+ bbox: bbox, success: true,
248
+ mrz: JSON.stringify(mrz), isAnalyzing: false,
249
+ country: countryData?.code,
250
+ documentType: selectedDocumentType.type,
251
+ }));
252
+ // Stocker le bbox pour ce côté
253
+ if (bbox && typeof bbox === 'object') {
254
+ setBboxBySide((prev) => ({ ...prev, [currentSide]: bbox }));
255
+ }
256
+ }).catch((e) => {
257
+ console.log("error front verification", e);
258
+ logger.log("error front verification", truncateFields(e));
259
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
260
+ // showAlert('Erreur', e?.message || 'Erreur de détection du MRZ');
261
+ });
262
+ }
263
+ catch (error) {
264
+ console.log("Error setting up frontVerification call", error);
265
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: error?.message || 'Erreur lors de la configuration de la vérification' }));
266
+ }
210
267
  }
211
268
  else {
269
+ const backRegionMappings = getCurrentSideVerification(currentSide);
270
+ logger.log("Calling backVerification", { templatePath, selectedDocumentType: selectedDocumentType.type, backRegionMappings });
212
271
  backVerification({
213
272
  path: result.path,
214
- regionMapping: getCurrentSideVerification(currentSide),
215
- selectedDocumentType: selectedDocumentType?.type || '',
273
+ regionMapping: {
274
+ authMethod: backRegionMappings.authMethod,
275
+ mrzTypes: backRegionMappings.mrzTypes,
276
+ },
277
+ selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type] || '',
216
278
  code: countryData?.code || '',
217
279
  currentSide: currentSide,
218
280
  templatePath: templatePath,
219
- mrzType: getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '',
220
- }).then((mrz) => {
281
+ mrzType: getCorrespondingMrzType(templatePath, backRegionMappings.regionMapping, backRegionMappings.key || '') || '',
282
+ }, env).then((mrz) => {
221
283
  logger.log("back verification result", truncateFields(mrz));
222
284
  const bbox = mrz?.bbox || templateBbox;
223
285
  setSilentCaptureResult((prev) => ({
224
286
  ...prev, path: result.path, bbox: bbox,
225
287
  success: true, mrz: JSON.stringify(mrz), isAnalyzing: false,
226
288
  country: countryData?.code,
227
- documentType: selectedDocumentType?.type,
289
+ documentType: selectedDocumentType.type,
228
290
  }));
229
291
  // Stocker le bbox pour ce côté
230
292
  if (bbox && typeof bbox === 'object') {
@@ -233,109 +295,115 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
233
295
  }).catch((e) => {
234
296
  logger.log("error back verification", truncateFields(e));
235
297
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
236
- // Alert.alert('Erreur', e?.message || 'Erreur de détection du MRZ');
298
+ // showAlert('Erreur', e?.message || 'Erreur de détection du MRZ');
237
299
  });
238
300
  }
239
301
  }
240
302
  };
241
303
  // Handle capture
242
304
  const handleCapture = async (result) => {
305
+ console.log("handleCapture", JSON.stringify(truncateFields({ result, silentCaptureResult }), null, 2));
243
306
  if (silentCaptureResult.path) {
244
- const base64 = await pathToBase64(silentCaptureResult.path);
307
+ // Créer une image rognée avec tolérance de 10% pour l'envoi
308
+ let imagePathForUpload = silentCaptureResult.path;
309
+ if (silentCaptureResult.bbox) {
310
+ try {
311
+ logger.log("Début du rognage avec tolérance, URI original:", silentCaptureResult.path);
312
+ imagePathForUpload = await cropImageWithBBoxWithTolerance(silentCaptureResult.path, silentCaptureResult.bbox, 0.05);
313
+ logger.log("Image rognée avec tolérance créée pour l'envoi, URI final:", imagePathForUpload);
314
+ }
315
+ catch (error) {
316
+ logger.log("Erreur lors du rognage avec tolérance, utilisation de l'image originale:", truncateFields(error));
317
+ // En cas d'erreur, on utilise l'image originale
318
+ imagePathForUpload = silentCaptureResult.path;
319
+ }
320
+ }
321
+ const base64 = await pathToBase64(imagePathForUpload);
245
322
  logger.log("silentCaptureResult captured", JSON.stringify(truncateFields(silentCaptureResult), null, 2));
323
+ // Utiliser l'image originale pour l'affichage (dir) mais l'image rognée avec tolérance pour l'envoi (file)
246
324
  const newImages = { ...capturedImages, [currentSide]: { dir: silentCaptureResult.path, file: base64, mrz: silentCaptureResult.mrz || "", templatePath: silentCaptureResult.templatePath } };
247
325
  setCapturedImages(newImages);
248
326
  if (silentCaptureResult.country && silentCaptureResult.documentType) {
249
327
  onValueChange({
250
328
  ...newImages,
251
329
  country: silentCaptureResult.country,
252
- documentType: silentCaptureResult.documentType,
330
+ documentType: GovernmentDocumentTypeBackend[silentCaptureResult.documentType] || '',
253
331
  });
254
332
  }
255
333
  setShowCamera(false);
256
334
  actions.showCustomStepper(true);
257
335
  }
258
336
  else {
259
- Alert.alert('Erreur', result.error || 'Impossible de prendre la photo');
337
+ showAlert('Erreur', result.error || 'Impossible de prendre la photo');
260
338
  }
261
339
  };
262
340
  const handleError = (event) => {
263
- Alert.alert('Erreur', event.message);
341
+ showAlert('Erreur', event.message);
264
342
  setShowCamera(false);
265
343
  };
266
344
  useEffect(() => {
267
345
  actions.showCustomStepper(!showCamera);
268
346
  }, [showCamera]);
269
- const handleDocumentTypeSelection = (docType) => {
270
- setSelectedDocumentType({ type: docType, region: hasRegions(docType) ? '' : 'root' });
271
- setSilentCaptureResult((prev) => ({ ...prev, templatePath: '', bbox: undefined, success: false, isAnalyzing: false, error: '', path: '', mrz: '', country: '', documentType: '' }));
272
- setCapturedImages((prev) => ({ front: { dir: '', file: '', mrz: '', templatePath: '' }, back: { dir: '', file: '', mrz: '', templatePath: '' } }));
273
- };
274
- const handleRegionSelection = (region) => {
275
- setSelectedDocumentType((prev) => ({ ...prev, region: region }));
347
+ // Cross-device polling logic
348
+ useEffect(() => {
349
+ if (!showQRModal || !state.session.session_id)
350
+ return;
351
+ const pollInterval = setInterval(async () => {
352
+ try {
353
+ const result = await kycService.getVerificationResult(state.session.session_id);
354
+ const sessionData = result[state.session.session_id]?.data;
355
+ if (sessionData) {
356
+ // Check if verification is completed or if we have ID card data
357
+ // Since the requirement is "verification restarts", we might look for overall completion
358
+ // or we could check if user_data is populated.
359
+ // For now, let's assume if status is not PENDING/INITIALIZED it might be done.
360
+ // Or simplier: if the mobile flow completes, the session status updates.
361
+ logger.log('Polling result:', truncateFields(sessionData));
362
+ if (sessionData.verification_status === 'completed' || sessionData.verification_status === 'approved' || sessionData.verification_status === 'review') {
363
+ clearInterval(pollInterval);
364
+ setShowQRModal(false);
365
+ actions.submitVerification(); // Or handleComplete
366
+ }
367
+ }
368
+ }
369
+ catch (error) {
370
+ console.error('Polling error:', error);
371
+ }
372
+ }, 5000);
373
+ return () => clearInterval(pollInterval);
374
+ }, [showQRModal, state.session.session_id, actions]);
375
+ const getQrCodeUrl = () => {
376
+ // Only available on web platform
377
+ if (Platform.OS !== 'web')
378
+ return '';
379
+ if (typeof window === 'undefined' || !window.location || !window.location.href)
380
+ return '';
381
+ try {
382
+ const currentUrl = new URL(window.location.href);
383
+ if (!currentUrl.searchParams.has('kyc_id') && state.session.session_id) {
384
+ currentUrl.searchParams.set('kyc_id', state.session.session_id);
385
+ }
386
+ // Ajouter l'étape actuelle pour permettre à l'utilisateur de continuer au bon endroit
387
+ if (!currentUrl.searchParams.has('step')) {
388
+ currentUrl.searchParams.set('step', String(state.currentComponentIndex));
389
+ }
390
+ return `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(currentUrl.toString())}`;
391
+ }
392
+ catch (error) {
393
+ console.warn('Error generating QR code URL:', error);
394
+ return '';
395
+ }
276
396
  };
277
- // Interface de sélection du type de document
278
- if (showDocumentSelection.documentSelection || showDocumentSelection.regionSelection) {
397
+ // Vérifier si les données sont disponibles, sinon afficher un message d'erreur
398
+ if (!countrySelectionData || !selectedDocumentType) {
279
399
  return (<View style={styles.root}>
280
400
  <View style={styles.container}>
281
-
282
401
  <Text style={styles.title}>{getLocalizedText(component.labels)}</Text>
283
- <Text style={styles.description}>{getLocalizedText(component.instructions)}</Text>
284
-
285
- <ScrollView style={styles.documentTypesContainer} showsVerticalScrollIndicator={false}>
286
- {showDocumentSelection.documentSelection ? availableDocumentTypes.map((docType) => {
287
- const docInfo = getDocumentTypeInfo(docType);
288
- return (<TouchableOpacity key={docType} style={[styles.documentTypeButton, { backgroundColor: selectedDocumentType?.type === docType ? '#2DBD6030' : '#F8F9FA', borderColor: selectedDocumentType?.type === docType ? '#2DBD60' : '#E9ECEF', borderWidth: 2 }]} onPress={() => handleDocumentTypeSelection(docType)}>
289
- <View style={[styles.documentTypeIconContainer, { backgroundColor: 'gray', }]}>
290
- <Text style={styles.documentTypeIcon}>{docInfo.icon}</Text>
291
- </View>
292
- <Text style={styles.documentTypeName}>
293
- {docInfo.name[language] || docInfo.name.en}
294
- </Text>
295
- <Text style={styles.documentTypeArrow}>→</Text>
296
- </TouchableOpacity>);
297
- }) : null}
298
- {showDocumentSelection.regionSelection ?
299
- hasRegions(selectedDocumentType?.type || 'identity_card').regionMapping.map((region) => {
300
- return (<TouchableOpacity key={region} onPress={() => handleRegionSelection(region)} style={[styles.documentTypeButton, { backgroundColor: selectedDocumentType?.region === region ? '#2DBD6030' : '#F8F9FA', borderColor: selectedDocumentType?.region === region ? '#2DBD60' : '#E9ECEF', borderWidth: 2 }]}>
301
- <Text>{region}</Text>
302
- </TouchableOpacity>);
303
- }) : null}
304
- </ScrollView>
305
- <Button title={t('common.next')} onPress={() => {
306
- if (!selectedDocumentType) {
307
- Alert.alert(t('common.error'), t('validation.required'));
308
- return;
309
- }
310
- if (showDocumentSelection.regionSelection) {
311
- logger.log("showDocumentSelection", JSON.stringify(truncateFields(showDocumentSelection), null, 2));
312
- setShowDocumentSelection({
313
- documentSelection: false,
314
- regionSelection: false
315
- });
316
- setShowCamera(true);
317
- actions.showCustomStepper(false);
318
- return;
319
- }
320
- else {
321
- if (hasRegions(selectedDocumentType?.type)?.hasRegions) {
322
- setShowDocumentSelection({
323
- documentSelection: false,
324
- regionSelection: true
325
- });
326
- }
327
- else {
328
- setShowDocumentSelection({
329
- documentSelection: false,
330
- regionSelection: false
331
- });
332
- setShowCamera(true);
333
- actions.showCustomStepper(false);
334
- }
335
- }
336
- }} variant={selectedDocumentType ? 'primary' : "neutral"} size="large" fullWidth/>
337
-
338
-
402
+ <Text style={styles.description}>
403
+ {state.currentLanguage === "en"
404
+ ? "Please complete the country and document selection first."
405
+ : "Veuillez d'abord compléter la sélection du pays et du document."}
406
+ </Text>
339
407
  {error && (<Text style={styles.errorText}>{error}</Text>)}
340
408
  </View>
341
409
  </View>);
@@ -347,10 +415,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
347
415
  if (currentSide === 'back') {
348
416
  setCurrentSide('front');
349
417
  setShowCamera(false);
350
- setShowDocumentSelection({
351
- documentSelection: false,
352
- regionSelection: false
353
- });
354
418
  // Si une image front existe, on la restaure pour l'afficher
355
419
  if (capturedImages['front']?.dir) {
356
420
  // Restaurer l'état de capture du front pour afficher l'image
@@ -375,14 +439,11 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
375
439
  }
376
440
  }
377
441
  else {
378
- setShowDocumentSelection({
379
- documentSelection: true,
380
- regionSelection: false
381
- });
382
- actions.showCustomStepper(true);
442
+ // Retour au composant précédent (country_selection)
443
+ actions.previousComponent();
383
444
  }
384
445
  },
385
- selectedDocumentType: locale === 'en' ? getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.en : getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.fr || '',
446
+ selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
386
447
  step: state.currentComponentIndex + 1,
387
448
  totalSteps: state.template.components.length,
388
449
  side: currentSide,
@@ -422,7 +483,13 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
422
483
  borderRadius: 12,
423
484
  resizeMode: 'cover',
424
485
  }}/>) : null}
425
- {silentCaptureResult.path ? (<Image source={{ uri: silentCaptureResult.path }} style={{
486
+ {!cropImageUri && silentCaptureResult.path ? (<Image source={{ uri: silentCaptureResult.path }} style={{
487
+ width: '100%',
488
+ height: 200,
489
+ borderRadius: 12,
490
+ resizeMode: 'cover',
491
+ }}/>) : null}
492
+ {!cropImageUri && !silentCaptureResult.path && capturedImages[currentSide]?.dir ? (<Image source={{ uri: capturedImages[currentSide].dir }} style={{
426
493
  width: '100%',
427
494
  height: 200,
428
495
  borderRadius: 12,
@@ -430,22 +497,33 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
430
497
  }}/>) : null}
431
498
 
432
499
  </View>
433
- {/* retake button */}
434
- <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => {
435
- retakePicture(currentSide);
436
- }} variant="outline" size="medium" fullWidth/>
437
- <Button title={t('common.next')} onPress={() => {
438
- if (currentSide === 'back' || selectedDocumentType?.type === 'passport') {
439
- actions.nextComponent();
440
- return;
441
- }
442
- else {
500
+ {/* Capture button si aucune image n'a été capturée */}
501
+ {!capturedImages[currentSide]?.dir && (<Button title={state.currentLanguage === "en" ? "Take Photo" : "Prendre une photo"} onPress={() => {
443
502
  setShowCamera(true);
444
- setCurrentSide('back');
445
- setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', mrz: '', templatePath: '' }));
446
- setCropImageUri('');
447
- }
448
- }} variant="primary" size="large" fullWidth/>
503
+ actions.showCustomStepper(false);
504
+ }} variant="primary" size="large" fullWidth/>)}
505
+ {/* retake button si une image a été capturée */}
506
+ {capturedImages[currentSide]?.dir && (<>
507
+ <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => {
508
+ retakePicture(currentSide);
509
+ }} variant="outline" size="medium" fullWidth/>
510
+ <Button title={t('common.next')} onPress={() => {
511
+ if (!selectedDocumentType) {
512
+ showAlert('Error', 'Document type not selected');
513
+ return;
514
+ }
515
+ if (currentSide === 'back' || selectedDocumentType.type === 'passport') {
516
+ actions.nextComponent();
517
+ return;
518
+ }
519
+ else {
520
+ setShowCamera(true);
521
+ setCurrentSide('back');
522
+ setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', mrz: '', templatePath: '' }));
523
+ setCropImageUri('');
524
+ }
525
+ }} variant="primary" size="large" fullWidth/>
526
+ </>)}
449
527
  </View>
450
528
  </View>
451
529
  </ScrollView>
@@ -453,6 +531,32 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
453
531
 
454
532
 
455
533
  {error && (<Text style={styles.errorText}>{error}</Text>)}
534
+
535
+ {/* Cross-Device / Continue on Phone Button (Web Only) */}
536
+ {Platform.OS === 'web' && !isMobileWeb() && !capturedImages[currentSide]?.dir && (<Button title={t('kyc.idCardCapture.continueOnPhone')} onPress={() => { setShowQRModal(true); }}/>)}
537
+
538
+ {/* QR Code Modal - Web Only */}
539
+ {Platform.OS === 'web' && (<Modal visible={showQRModal} transparent={true} animationType="fade" onRequestClose={() => setShowQRModal(false)}>
540
+ <View style={styles.modalOverlay}>
541
+ <View style={styles.modalContent}>
542
+ <Text style={styles.modalTitle}>
543
+ {t('kyc.idCardCapture.continueOnMobile')}
544
+ </Text>
545
+ <Text style={styles.modalDescription}>
546
+ {t('kyc.idCardCapture.scanQrCode')}
547
+ </Text>
548
+
549
+ {showQRModal && getQrCodeUrl() ? (<Image source={{ uri: getQrCodeUrl() }} style={styles.qrCodeImage}/>) : null}
550
+
551
+ <TouchableOpacity style={styles.closeButton} onPress={() => setShowQRModal(false)}>
552
+ <Text style={styles.closeButtonText}>
553
+ {t('common.close')}
554
+ </Text>
555
+ </TouchableOpacity>
556
+ </View>
557
+ </View>
558
+ </Modal>)}
559
+
456
560
  </View>
457
561
  </View>);
458
562
  };
@@ -606,42 +710,67 @@ const styles = StyleSheet.create({
606
710
  marginTop: 8,
607
711
  textAlign: 'center',
608
712
  },
609
- documentTypesContainer: {
610
- // flex: 1,
611
- // marginBottom: 16,
612
- // paddingHorizontal: 16,
613
- maxHeight: Dimensions.get('window').height - 400,
614
- },
615
- documentTypeButton: {
616
- flexDirection: 'row',
713
+ crossDeviceButton: {
714
+ marginTop: 16,
715
+ padding: 12,
617
716
  alignItems: 'center',
618
- backgroundColor: '#F8F9FA',
619
- padding: 16,
620
- marginBottom: 12,
621
- borderRadius: 12,
622
717
  borderWidth: 1,
623
- borderColor: '#E9ECEF',
718
+ borderColor: '#2DBD60',
719
+ borderRadius: 8,
720
+ backgroundColor: '#f0f9f0',
624
721
  },
625
- documentTypeIconContainer: {
626
- width: 40, height: 40,
627
- borderRadius: 10,
628
- alignItems: 'center',
722
+ crossDeviceText: {
723
+ color: '#2DBD60',
724
+ fontSize: 16,
725
+ fontWeight: '600',
726
+ },
727
+ modalOverlay: {
728
+ flex: 1,
729
+ backgroundColor: 'rgba(0,0,0,0.5)',
629
730
  justifyContent: 'center',
630
- marginRight: 16,
731
+ alignItems: 'center',
631
732
  },
632
- documentTypeIcon: {
633
- fontSize: 24,
733
+ modalContent: {
734
+ backgroundColor: 'white',
735
+ borderRadius: 16,
736
+ padding: 24,
737
+ alignItems: 'center',
738
+ width: '90%',
739
+ maxWidth: 340,
740
+ shadowColor: '#000',
741
+ shadowOffset: { width: 0, height: 2 },
742
+ shadowOpacity: 0.25,
743
+ shadowRadius: 4,
744
+ elevation: 5,
634
745
  },
635
- documentTypeName: {
636
- flex: 1,
637
- fontSize: 16,
638
- fontWeight: '600',
746
+ modalTitle: {
747
+ fontSize: 20,
748
+ fontWeight: 'bold',
749
+ marginBottom: 12,
639
750
  color: '#333',
640
751
  },
641
- documentTypeArrow: {
642
- fontSize: 18,
752
+ modalDescription: {
753
+ fontSize: 16,
643
754
  color: '#666',
644
- fontWeight: 'bold',
755
+ textAlign: 'center',
756
+ marginBottom: 20,
757
+ lineHeight: 22,
758
+ },
759
+ qrCodeImage: {
760
+ width: 200,
761
+ height: 200,
762
+ marginBottom: 20,
763
+ },
764
+ closeButton: {
765
+ paddingVertical: 10,
766
+ paddingHorizontal: 20,
767
+ backgroundColor: '#f5f5f5',
768
+ borderRadius: 8,
769
+ },
770
+ closeButtonText: {
771
+ fontSize: 16,
772
+ color: '#666',
773
+ fontWeight: '600',
645
774
  },
646
775
  });
647
776
  //# sourceMappingURL=IDCardCapture.js.map