@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,7 +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';
4
- import { TemplateComponent, IDCardConfig, LocalizedText, GovernmentDocumentType, ISilentCaptureResult, IBbox } from '../../types/KYC.types';
5
+ import { TemplateComponent, LocalizedText, GovernmentDocumentType, ISilentCaptureResult, IBbox, GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
5
6
  import IdCardOverlay from '../OverLay/IdCard';
6
7
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
7
8
  import { useI18n } from '../../hooks/useI18n';
@@ -10,8 +11,9 @@ import { removeDuplicates } from '../../utils/remove-duplicate';
10
11
  import { backVerification, checkTemplateType, frontVerification } from '../../modules/api/CardAuthentification';
11
12
  import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
12
13
  import pathToBase64 from '../../utils/pathToBase64';
13
- import { truncateFields } from '../../modules/api/KYCService';
14
- 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';
15
17
  import { logger } from '../../utils/logger';
16
18
 
17
19
 
@@ -45,24 +47,20 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
45
47
  const [currentSide, setCurrentSide] = useState<string>('front');
46
48
  // Stocker les bbox par côté pour pouvoir restaurer les images croppées
47
49
  const [bboxBySide, setBboxBySide] = useState<Record<string, IBbox>>({});
48
- const [selectedDocumentType, setSelectedDocumentType] = useState<{
49
- type: GovernmentDocumentType;
50
- region: string;
51
- }>({
52
- type: 'identity_card',
53
- region: 'root'
54
- });
55
- const [showDocumentSelection, setShowDocumentSelection] = useState(
56
- {
57
- documentSelection: true,
58
- regionSelection: false
59
- }
60
- );
61
50
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
51
+ const [showQRModal, setShowQRModal] = useState(false);
52
+
53
+ // Mapping des types de documents backend vers SDK
54
+ const documentTypeMapping: Record<string, GovernmentDocumentType> = {
55
+ 'nationalId': 'national_id',
56
+ 'passport': 'passport',
57
+ 'driversLicense': 'drivers_licence',
58
+ 'residencePermit': 'permanent_residence',
59
+ 'healthInsuranceCard': 'health_insurance_card',
60
+ };
62
61
  // const [imageNaturalSize, setImageNaturalSize] = useState<{ width: number; height: number } | null>(null);
63
62
 
64
- const { actions, state, } = useTemplateKYCFlowContext();
65
- const config = component.config as IDCardConfig;
63
+ const { actions, state, env } = useTemplateKYCFlowContext();
66
64
 
67
65
 
68
66
 
@@ -74,32 +72,60 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
74
72
  return "";
75
73
  };
76
74
 
77
- const countryData = useMemo(() => {
78
- const geCountryID = Object.keys(state.componentData).find((c: string) => c === "6");
79
-
80
- if (geCountryID) {
81
- const countryMapping = state.componentData[geCountryID];
82
- return countryMapping;
75
+ // Récupérer les données depuis le composant country_selection
76
+ const countrySelectionData = useMemo(() => {
77
+ const countrySelectionComponent = state.template.components.find(c => c.type === 'country_selection');
78
+ if (countrySelectionComponent) {
79
+ return state.componentData[countrySelectionComponent.id];
83
80
  }
84
81
  return null;
85
- }, [state.componentData]);
82
+ }, [state.template.components, state.componentData]);
86
83
 
84
+ // Extraire selectedDocumentType depuis countrySelectionData
85
+ const selectedDocumentType = useMemo<{ type: GovernmentDocumentType; region: string } | null>(() => {
86
+ if (!countrySelectionData?.documentType) return null;
87
+
88
+ const backendDocType = countrySelectionData.documentType;
89
+ const mappedType = documentTypeMapping[backendDocType] || backendDocType as GovernmentDocumentType;
90
+ const region = countrySelectionData.region || 'root';
91
+
92
+ return { type: mappedType, region };
93
+ }, [countrySelectionData, documentTypeMapping]);
94
+
95
+ // Récupérer countryData pour compatibilité avec le code existant
96
+ const countryData = useMemo(() => {
97
+ return countrySelectionData;
98
+ }, [countrySelectionData]);
87
99
 
88
- const availableDocumentTypes = useMemo(() => {
89
- const newconfig: GovernmentDocumentType[] = [];
90
100
 
91
- if (countryData) {
92
- const countryMapping = Object.keys(countryData?.regionMapping);
93
- if (countryMapping && Array.isArray(countryMapping)) {
94
- countryMapping.forEach((c) => {
95
- newconfig.push(c as GovernmentDocumentType);
101
+ // Synchroniser capturedImages avec value quand les données sont chargées (ex: reprise de session)
102
+ useEffect(() => {
103
+ if (value && Object.keys(value).length > 0) {
104
+ // Vérifier si les données ont changé
105
+ const valueChanged = JSON.stringify(value) !== JSON.stringify(capturedImages);
106
+ if (valueChanged) {
107
+ logger.log("Updating capturedImages from value:", Object.keys(value));
108
+ logger.log("Value data sample:", truncateFields(value));
109
+ const updatedImages = value as Record<string, IIDCardPayload>;
110
+ setCapturedImages(updatedImages);
111
+
112
+ // Si on a des images chargées, mettre à jour silentCaptureResult pour l'affichage
113
+ Object.keys(updatedImages).forEach((side) => {
114
+ const imageData = updatedImages[side];
115
+ if (imageData?.dir) {
116
+ setSilentCaptureResult(prev => ({
117
+ ...prev,
118
+ path: imageData.dir,
119
+ success: true,
120
+ isAnalyzing: false,
121
+ mrz: imageData.mrz || '',
122
+ templatePath: imageData.templatePath || '',
123
+ }));
124
+ }
96
125
  });
97
126
  }
98
127
  }
99
- logger.log("newconfig", truncateFields(newconfig));
100
- return newconfig;
101
- }, []);
102
-
128
+ }, [value]);
103
129
 
104
130
  useEffect(() => {
105
131
  logger.log("cropImageUri", JSON.stringify(truncateFields({ box: silentCaptureResult }), null, 2));
@@ -112,28 +138,33 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
112
138
 
113
139
 
114
140
 
115
- const cameraConfig = {
116
- aspectRatio: 4 / 3,
117
- quality: 0.8,
118
- flashMode: 'auto' as const,
119
- cameraType: 'back' as const,
120
- allowRetake: true,
121
- maxRetakes: 3,
122
- overlay: {
123
- showGuide: true,
124
- guideText: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions as Record<string, LocalizedText>),
125
- bbox: selectedDocumentType && config.bbox_configs[selectedDocumentType.type] ? config.bbox_configs[selectedDocumentType.type] : {
126
- xMin: 20,
127
- yMin: 140,
128
- xMax: 370,
129
- yMax: 340,
130
- borderColor: '#2DBD60',
131
- borderWidth: 3,
132
- cornerRadius: 8
133
- }
141
+ const cameraConfig = useMemo(() => {
142
+ const instructions = selectedDocumentType
143
+ ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
144
+ : getLocalizedText(component.instructions as Record<string, LocalizedText>);
134
145
 
135
- }
136
- };
146
+ return {
147
+ aspectRatio: 4 / 3,
148
+ quality: 0.8,
149
+ flashMode: 'auto' as const,
150
+ cameraType: 'back' as const,
151
+ allowRetake: true,
152
+ maxRetakes: 3,
153
+ overlay: {
154
+ showGuide: true,
155
+ guideText: instructions,
156
+ bbox: {
157
+ xMin: 20,
158
+ yMin: 140,
159
+ xMax: 370,
160
+ yMax: 340,
161
+ borderColor: '#2DBD60',
162
+ borderWidth: 3,
163
+ cornerRadius: 8
164
+ }
165
+ }
166
+ };
167
+ }, [selectedDocumentType, locale, component.instructions]);
137
168
 
138
169
  const retakePicture = (currentSide: string) => {
139
170
  setShowCamera(true);
@@ -144,29 +175,18 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
144
175
  onValueChange({ ...value, [currentSide]: { dir: '', file: '', mrz: '' } });
145
176
  };
146
177
 
147
- const hasRegions = useCallback((documentType: GovernmentDocumentType): { hasRegions: boolean, regionMapping: string[] } => {
148
- const regionMapping = countryData?.regionMapping[documentType as GovernmentDocumentType];
149
- if (regionMapping && Object.keys(regionMapping).length > 1) {
150
- return {
151
- hasRegions: true,
152
- regionMapping: Object.keys(regionMapping)
153
- };
154
- }
155
- return {
156
- hasRegions: false,
157
- regionMapping: []
158
- };
159
- }, [countryData]);
160
178
 
161
179
 
162
180
  const getCurrentSideVerification = (currentSide: string) => {
163
- const regionMapping = countryData?.regionMapping[selectedDocumentType?.type as GovernmentDocumentType];
181
+ if (!selectedDocumentType || !countryData?.regionMapping) {
182
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
183
+ }
184
+
185
+ const regionMapping = countryData.regionMapping[selectedDocumentType.type as GovernmentDocumentType];
164
186
  const authMethod: string[] = [];
165
187
  const mrzTypes: string[] = [];
166
188
 
167
- const key = selectedDocumentType?.region?.trim()?.length > 0 ? selectedDocumentType?.region?.trim() : 'root';
168
- // console.log("regionMapping", JSON.stringify(regionMapping, null, 2), selectedDocumentType?.region);
169
- // const key = selectedDocumentType?.region as keyof typeof regionMapping;
189
+ const key = selectedDocumentType.region?.trim()?.length > 0 ? selectedDocumentType.region.trim() : 'root';
170
190
 
171
191
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
172
192
  regionMapping[key].forEach((item: any) => {
@@ -179,13 +199,13 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
179
199
  });
180
200
  }
181
201
 
182
- logger.log("regionMapping", JSON.stringify(truncateFields({ regionMapping, selectedDocumentType: selectedDocumentType?.region, key }), null, 2));
202
+ logger.log("regionMapping", JSON.stringify(truncateFields({ regionMapping, selectedDocumentType: selectedDocumentType.region, key }), null, 2));
183
203
 
184
204
  return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping: regionMapping, key: key };
185
205
  }
186
206
 
187
207
  const getCorrespondingMrzType = (templatePath: string, mapping: any, selectedDocumentType: string = "root") => {
188
- if (!mapping[selectedDocumentType]) return null;
208
+ if (!mapping || !mapping[selectedDocumentType]) return null;
189
209
 
190
210
  // Extraire le nom du fichier depuis le template_path
191
211
  const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
@@ -210,11 +230,21 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
210
230
  let templatePath = silentCaptureResult.templatePath || '';
211
231
  let templateBbox: IBbox | undefined;
212
232
 
233
+ if (!selectedDocumentType) {
234
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
235
+ return;
236
+ }
237
+
213
238
  const regionMappings = getCurrentSideVerification(currentSide)
214
239
  // logger.log("regionMappings", JSON.stringify(truncateFields({regionMappings, templatePath}), null, 2));
215
240
  if (templatePath.length === 0) {
216
241
  try {
217
- const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide });
242
+ logger.log("checkTemplateType - BEFORE call", {
243
+ selectedDocumentTypeType: selectedDocumentType?.type,
244
+ countrySelectionDataDocumentType: countrySelectionData?.documentType,
245
+ docTypeToSend: selectedDocumentType?.type
246
+ });
247
+ const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide }, env);
218
248
 
219
249
  if (templateType.template_path) {
220
250
  templatePath = templateType.template_path;
@@ -232,21 +262,38 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
232
262
  } catch (e: any) {
233
263
  logger.log("error checking template type", truncateFields(e));
234
264
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du template' }));
265
+ return; // Return early if checkTemplateType fails
235
266
  }
236
267
  }
237
- logger.log("templatePath before front verification", templatePath);
268
+ logger.log("templatePath before verification", templatePath, "currentSide", currentSide);
238
269
  if (currentSide === 'front') {
239
-
240
- frontVerification(
241
- {
270
+ logger.log("frontVerification - BEFORE call", {
271
+ selectedDocumentTypeType: selectedDocumentType?.type,
272
+ countrySelectionDataDocumentType: countrySelectionData?.documentType,
273
+ docTypeToSend: selectedDocumentType?.type
274
+ });
275
+ logger.log("Calling frontVerification", { templatePath, selectedDocumentType: selectedDocumentType?.type, regionMappings });
276
+ console.log("About to call frontVerification", typeof frontVerification);
277
+ try {
278
+ const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
279
+ console.log("mrzType calculated", mrzType);
280
+ const verificationParams = {
242
281
  path: result.path,
243
- regionMapping: regionMappings,
244
- selectedDocumentType: selectedDocumentType?.type || '',
282
+ regionMapping: {
283
+ authMethod: regionMappings.authMethod,
284
+ mrzTypes: regionMappings.mrzTypes,
285
+ },
286
+ selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type as keyof typeof GovernmentDocumentTypeShorted] || '',
245
287
  code: countryData?.code || '',
246
288
  currentSide: currentSide,
247
289
  templatePath: templatePath,
248
- mrzType: getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '',
249
- }).then((mrz) => {
290
+ mrzType: mrzType,
291
+ };
292
+ console.log("frontVerification params", verificationParams);
293
+ console.log("About to call frontVerification function");
294
+ const promise = frontVerification(verificationParams, env);
295
+ console.log("frontVerification promise created", promise);
296
+ promise.then((mrz) => {
250
297
  logger.log("front verification result", truncateFields(mrz));
251
298
  const bbox = (mrz as any)?.bbox || templateBbox;
252
299
  setSilentCaptureResult((prev) => ({
@@ -256,7 +303,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
256
303
  bbox: bbox, success: true,
257
304
  mrz: JSON.stringify(mrz), isAnalyzing: false,
258
305
  country: countryData?.code,
259
- documentType: selectedDocumentType?.type,
306
+ documentType: selectedDocumentType.type,
260
307
  }),
261
308
  );
262
309
  // Stocker le bbox pour ce côté
@@ -265,27 +312,37 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
265
312
  }
266
313
 
267
314
  }).catch((e: any) => {
315
+ console.log("error front verification", e);
268
316
  logger.log("error front verification", truncateFields(e));
269
317
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
270
- // Alert.alert('Erreur', e?.message || 'Erreur de détection du MRZ');
271
- })
318
+ // showAlert('Erreur', e?.message || 'Erreur de détection du MRZ');
319
+ });
320
+ } catch (error: any) {
321
+ console.log("Error setting up frontVerification call", error);
322
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: error?.message || 'Erreur lors de la configuration de la vérification' }));
323
+ }
272
324
  } else {
325
+ const backRegionMappings = getCurrentSideVerification(currentSide);
326
+ logger.log("Calling backVerification", { templatePath, selectedDocumentType: selectedDocumentType.type, backRegionMappings });
273
327
  backVerification({
274
328
  path: result.path,
275
- regionMapping: getCurrentSideVerification(currentSide),
276
- selectedDocumentType: selectedDocumentType?.type || '',
329
+ regionMapping: {
330
+ authMethod: backRegionMappings.authMethod,
331
+ mrzTypes: backRegionMappings.mrzTypes,
332
+ },
333
+ selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
277
334
  code: countryData?.code || '',
278
335
  currentSide: currentSide,
279
336
  templatePath: templatePath,
280
- mrzType: getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '',
281
- }).then((mrz) => {
337
+ mrzType: getCorrespondingMrzType(templatePath, backRegionMappings.regionMapping, backRegionMappings.key || '') || '',
338
+ }, env).then((mrz) => {
282
339
  logger.log("back verification result", truncateFields(mrz));
283
340
  const bbox = (mrz as any)?.bbox || templateBbox;
284
341
  setSilentCaptureResult((prev) => ({
285
342
  ...prev, path: result.path, bbox: bbox,
286
343
  success: true, mrz: JSON.stringify(mrz), isAnalyzing: false,
287
344
  country: countryData?.code,
288
- documentType: selectedDocumentType?.type,
345
+ documentType: selectedDocumentType.type,
289
346
 
290
347
  }));
291
348
  // Stocker le bbox pour ce côté
@@ -295,7 +352,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
295
352
  }).catch((e: any) => {
296
353
  logger.log("error back verification", truncateFields(e));
297
354
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
298
- // Alert.alert('Erreur', e?.message || 'Erreur de détection du MRZ');
355
+ // showAlert('Erreur', e?.message || 'Erreur de détection du MRZ');
299
356
  })
300
357
  }
301
358
 
@@ -304,28 +361,45 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
304
361
 
305
362
  // Handle capture
306
363
  const handleCapture = async (result: { success: boolean; path?: string; error?: string }) => {
364
+ console.log("handleCapture", JSON.stringify(truncateFields({ result, silentCaptureResult }), null, 2));
365
+
307
366
  if (silentCaptureResult.path) {
308
- const base64 = await pathToBase64(silentCaptureResult.path);
367
+ // Créer une image rognée avec tolérance de 10% pour l'envoi
368
+ let imagePathForUpload = silentCaptureResult.path;
369
+ if (silentCaptureResult.bbox) {
370
+ try {
371
+ logger.log("Début du rognage avec tolérance, URI original:", silentCaptureResult.path);
372
+ imagePathForUpload = await cropImageWithBBoxWithTolerance(silentCaptureResult.path, silentCaptureResult.bbox, 0.05);
373
+ logger.log("Image rognée avec tolérance créée pour l'envoi, URI final:", imagePathForUpload);
374
+ } catch (error) {
375
+ logger.log("Erreur lors du rognage avec tolérance, utilisation de l'image originale:", truncateFields(error));
376
+ // En cas d'erreur, on utilise l'image originale
377
+ imagePathForUpload = silentCaptureResult.path;
378
+ }
379
+ }
380
+
381
+ const base64 = await pathToBase64(imagePathForUpload);
309
382
 
310
383
  logger.log("silentCaptureResult captured", JSON.stringify(truncateFields(silentCaptureResult), null, 2));
384
+ // Utiliser l'image originale pour l'affichage (dir) mais l'image rognée avec tolérance pour l'envoi (file)
311
385
  const newImages = { ...capturedImages, [currentSide]: { dir: silentCaptureResult.path, file: base64, mrz: silentCaptureResult.mrz || "", templatePath: silentCaptureResult.templatePath } };
312
386
  setCapturedImages(newImages);
313
387
  if (silentCaptureResult.country && silentCaptureResult.documentType) {
314
388
  onValueChange({
315
389
  ...newImages,
316
390
  country: silentCaptureResult.country,
317
- documentType: silentCaptureResult.documentType,
391
+ documentType: GovernmentDocumentTypeBackend[silentCaptureResult.documentType as keyof typeof GovernmentDocumentTypeBackend] || '',
318
392
  });
319
393
  }
320
394
  setShowCamera(false);
321
395
  actions.showCustomStepper(true);
322
396
  } else {
323
- Alert.alert('Erreur', result.error || 'Impossible de prendre la photo');
397
+ showAlert('Erreur', result.error || 'Impossible de prendre la photo');
324
398
  }
325
399
  };
326
400
 
327
401
  const handleError = (event: { message: string }) => {
328
- Alert.alert('Erreur', event.message);
402
+ showAlert('Erreur', event.message);
329
403
  setShowCamera(false);
330
404
  };
331
405
 
@@ -337,98 +411,75 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
337
411
  actions.showCustomStepper(!showCamera);
338
412
  }, [showCamera]);
339
413
 
414
+ // Cross-device polling logic
415
+ useEffect(() => {
416
+ if (!showQRModal || !state.session.session_id) return;
417
+
418
+ const pollInterval = setInterval(async () => {
419
+ try {
420
+ const result = await kycService.getVerificationResult(state.session.session_id);
421
+ const sessionData = result[state.session.session_id]?.data;
422
+
423
+ if (sessionData) {
424
+ // Check if verification is completed or if we have ID card data
425
+ // Since the requirement is "verification restarts", we might look for overall completion
426
+ // or we could check if user_data is populated.
427
+ // For now, let's assume if status is not PENDING/INITIALIZED it might be done.
428
+ // Or simplier: if the mobile flow completes, the session status updates.
429
+ logger.log('Polling result:', truncateFields(sessionData));
430
+
431
+ if (sessionData.verification_status === 'completed' || sessionData.verification_status === 'approved' || sessionData.verification_status === 'review') {
432
+ clearInterval(pollInterval);
433
+ setShowQRModal(false);
434
+ actions.submitVerification(); // Or handleComplete
435
+ }
436
+ }
437
+ } catch (error) {
438
+ console.error('Polling error:', error);
439
+ }
440
+ }, 5000);
441
+
442
+ return () => clearInterval(pollInterval);
443
+ }, [showQRModal, state.session.session_id, actions]);
444
+
445
+ const getQrCodeUrl = (): string => {
446
+ // Only available on web platform
447
+ if (Platform.OS !== 'web') return '';
448
+ if (typeof window === 'undefined' || !window.location || !window.location.href) return '';
449
+
450
+ try {
451
+ const currentUrl = new URL(window.location.href);
452
+ if (!currentUrl.searchParams.has('kyc_id') && state.session.session_id) {
453
+ currentUrl.searchParams.set('kyc_id', state.session.session_id);
454
+ }
455
+ // Ajouter l'étape actuelle pour permettre à l'utilisateur de continuer au bon endroit
456
+ if (!currentUrl.searchParams.has('step')) {
457
+ currentUrl.searchParams.set('step', String(state.currentComponentIndex));
458
+ }
459
+ return `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(currentUrl.toString())}`;
460
+ } catch (error) {
461
+ console.warn('Error generating QR code URL:', error);
462
+ return '';
463
+ }
464
+ };
465
+
340
466
 
341
467
 
342
468
 
343
469
 
344
470
 
345
- const handleDocumentTypeSelection = (docType: GovernmentDocumentType) => {
346
- setSelectedDocumentType({ type: docType, region: hasRegions(docType) ? '' : 'root' });
347
- setSilentCaptureResult((prev) => ({ ...prev, templatePath: '', bbox: undefined, success: false, isAnalyzing: false, error: '', path: '', mrz: '', country: '', documentType: '' }));
348
- setCapturedImages((prev) => ({ front: { dir: '', file: '', mrz: '', templatePath: '' }, back: { dir: '', file: '', mrz: '', templatePath: '' } }));
349
- };
350
- const handleRegionSelection = (region: string) => {
351
- setSelectedDocumentType((prev) => ({ ...prev, region: region }));
352
- };
353
471
 
354
- // Interface de sélection du type de document
355
- if (showDocumentSelection.documentSelection || showDocumentSelection.regionSelection) {
472
+ // Vérifier si les données sont disponibles, sinon afficher un message d'erreur
473
+ if (!countrySelectionData || !selectedDocumentType) {
356
474
  return (
357
475
  <View style={styles.root}>
358
476
  <View style={styles.container}>
359
-
360
477
  <Text style={styles.title}>{getLocalizedText(component.labels)}</Text>
361
- <Text style={styles.description}>{getLocalizedText(component.instructions)}</Text>
362
-
363
- <ScrollView style={styles.documentTypesContainer} showsVerticalScrollIndicator={false}>
364
- {showDocumentSelection.documentSelection ? availableDocumentTypes.map((docType) => {
365
- const docInfo = getDocumentTypeInfo(docType);
366
- return (
367
- <TouchableOpacity
368
- key={docType}
369
- style={[styles.documentTypeButton, { backgroundColor: selectedDocumentType?.type === docType ? '#2DBD6030' : '#F8F9FA', borderColor: selectedDocumentType?.type === docType ? '#2DBD60' : '#E9ECEF', borderWidth: 2 }]}
370
- onPress={() => handleDocumentTypeSelection(docType)}
371
- >
372
- <View style={[styles.documentTypeIconContainer, { backgroundColor: 'gray', }]}>
373
- <Text style={styles.documentTypeIcon}>{docInfo.icon}</Text>
374
- </View>
375
- <Text style={styles.documentTypeName}>
376
- {docInfo.name[language as keyof typeof docInfo.name] || docInfo.name.en}
377
- </Text>
378
- <Text style={styles.documentTypeArrow}>→</Text>
379
- </TouchableOpacity>
380
- );
381
- }) : null}
382
- {showDocumentSelection.regionSelection ?
383
- hasRegions(selectedDocumentType?.type || 'identity_card').regionMapping.map((region) => {
384
- return (
385
- <TouchableOpacity key={region} onPress={() => handleRegionSelection(region)}
386
- style={[styles.documentTypeButton, { backgroundColor: selectedDocumentType?.region === region ? '#2DBD6030' : '#F8F9FA', borderColor: selectedDocumentType?.region === region ? '#2DBD60' : '#E9ECEF', borderWidth: 2 }]}
387
-
388
- >
389
- <Text>{region}</Text>
390
- </TouchableOpacity>
391
- );
392
- }) : null}
393
- </ScrollView>
394
- <Button title={t('common.next')} onPress={() => {
395
- if (!selectedDocumentType) {
396
- Alert.alert(t('common.error'), t('validation.required'));
397
- return;
398
- }
399
-
400
- if (showDocumentSelection.regionSelection) {
401
- logger.log("showDocumentSelection", JSON.stringify(truncateFields(showDocumentSelection), null, 2));
402
- setShowDocumentSelection({
403
- documentSelection: false,
404
- regionSelection: false
405
- });
406
- setShowCamera(true);
407
- actions.showCustomStepper(false);
408
- return;
409
- } else {
410
- if (hasRegions(selectedDocumentType?.type)?.hasRegions) {
411
- setShowDocumentSelection({
412
- documentSelection: false,
413
- regionSelection: true
414
- });
415
- } else {
416
- setShowDocumentSelection({
417
- documentSelection: false,
418
- regionSelection: false
419
- });
420
- setShowCamera(true);
421
- actions.showCustomStepper(false);
422
- }
423
- }
424
-
425
- }}
426
- variant={selectedDocumentType ? 'primary' : "neutral"}
427
- size="large"
428
- fullWidth
429
- />
430
-
431
-
478
+ <Text style={styles.description}>
479
+ {state.currentLanguage === "en"
480
+ ? "Please complete the country and document selection first."
481
+ : "Veuillez d'abord compléter la sélection du pays et du document."}
482
+ </Text>
432
483
  {error && (
433
484
  <Text style={styles.errorText}>{error}</Text>
434
485
  )}
@@ -470,10 +521,6 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
470
521
  if (currentSide === 'back') {
471
522
  setCurrentSide('front');
472
523
  setShowCamera(false);
473
- setShowDocumentSelection({
474
- documentSelection: false,
475
- regionSelection: false
476
- });
477
524
  // Si une image front existe, on la restaure pour l'afficher
478
525
  if (capturedImages['front']?.dir) {
479
526
  // Restaurer l'état de capture du front pour afficher l'image
@@ -496,14 +543,11 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
496
543
  setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '' }));
497
544
  }
498
545
  } else {
499
- setShowDocumentSelection({
500
- documentSelection: true,
501
- regionSelection: false
502
- });
503
- actions.showCustomStepper(true);
546
+ // Retour au composant précédent (country_selection)
547
+ actions.previousComponent();
504
548
  }
505
549
  },
506
- selectedDocumentType: locale === 'en' ? getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.en : getDocumentTypeInfo(selectedDocumentType?.type || "identity_card").name.fr || '',
550
+ selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
507
551
  step: state.currentComponentIndex + 1,
508
552
  totalSteps: state.template.components.length,
509
553
  side: currentSide,
@@ -557,41 +601,74 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
557
601
  }}
558
602
  />
559
603
  ) : null}
560
- {silentCaptureResult.path ? (<Image
561
- source={{ uri: silentCaptureResult.path }}
562
- style={{
563
- width: '100%',
564
- height: 200,
565
- borderRadius: 12,
566
- resizeMode: 'cover',
567
- }}
568
- />) : null}
604
+ {!cropImageUri && silentCaptureResult.path ? (
605
+ <Image
606
+ source={{ uri: silentCaptureResult.path }}
607
+ style={{
608
+ width: '100%',
609
+ height: 200,
610
+ borderRadius: 12,
611
+ resizeMode: 'cover',
612
+ }}
613
+ />
614
+ ) : null}
615
+ {!cropImageUri && !silentCaptureResult.path && capturedImages[currentSide]?.dir ? (
616
+ <Image
617
+ source={{ uri: capturedImages[currentSide].dir }}
618
+ style={{
619
+ width: '100%',
620
+ height: 200,
621
+ borderRadius: 12,
622
+ resizeMode: 'cover',
623
+ }}
624
+ />
625
+ ) : null}
569
626
 
570
627
  </View>
571
- {/* retake button */}
572
- <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => {
573
- retakePicture(currentSide);
574
- }}
575
- variant="outline"
576
- size="medium"
577
- fullWidth
578
-
579
- />
580
- <Button title={t('common.next')} onPress={() => {
581
- if (currentSide === 'back' || selectedDocumentType?.type === 'passport') {
582
- actions.nextComponent();
583
- return;
584
- } else {
585
- setShowCamera(true);
586
- setCurrentSide('back');
587
- setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', mrz: '', templatePath: '' }));
588
- setCropImageUri('');
589
- }
590
- }}
591
- variant="primary"
592
- size="large"
593
- fullWidth
594
- />
628
+ {/* Capture button si aucune image n'a été capturée */}
629
+ {!capturedImages[currentSide]?.dir && (
630
+ <Button
631
+ title={state.currentLanguage === "en" ? "Take Photo" : "Prendre une photo"}
632
+ onPress={() => {
633
+ setShowCamera(true);
634
+ actions.showCustomStepper(false);
635
+ }}
636
+ variant="primary"
637
+ size="large"
638
+ fullWidth
639
+ />
640
+ )}
641
+ {/* retake button si une image a été capturée */}
642
+ {capturedImages[currentSide]?.dir && (
643
+ <>
644
+ <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => {
645
+ retakePicture(currentSide);
646
+ }}
647
+ variant="outline"
648
+ size="medium"
649
+ fullWidth
650
+ />
651
+ <Button title={t('common.next')} onPress={() => {
652
+ if (!selectedDocumentType) {
653
+ showAlert('Error', 'Document type not selected');
654
+ return;
655
+ }
656
+ if (currentSide === 'back' || selectedDocumentType.type === 'passport') {
657
+ actions.nextComponent();
658
+ return;
659
+ } else {
660
+ setShowCamera(true);
661
+ setCurrentSide('back');
662
+ setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', mrz: '', templatePath: '' }));
663
+ setCropImageUri('');
664
+ }
665
+ }}
666
+ variant="primary"
667
+ size="large"
668
+ fullWidth
669
+ />
670
+ </>
671
+ )}
595
672
  </View>
596
673
  </View>
597
674
  </ScrollView>
@@ -601,6 +678,49 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
601
678
  {error && (
602
679
  <Text style={styles.errorText}>{error}</Text>
603
680
  )}
681
+
682
+ {/* Cross-Device / Continue on Phone Button (Web Only) */}
683
+ {Platform.OS === 'web' && !isMobileWeb() && !capturedImages[currentSide]?.dir && (
684
+ <Button title={t('kyc.idCardCapture.continueOnPhone')} onPress={() => { setShowQRModal(true) }} />
685
+ )}
686
+
687
+ {/* QR Code Modal - Web Only */}
688
+ {Platform.OS === 'web' && (
689
+ <Modal
690
+ visible={showQRModal}
691
+ transparent={true}
692
+ animationType="fade"
693
+ onRequestClose={() => setShowQRModal(false)}
694
+ >
695
+ <View style={styles.modalOverlay}>
696
+ <View style={styles.modalContent}>
697
+ <Text style={styles.modalTitle}>
698
+ {t('kyc.idCardCapture.continueOnMobile')}
699
+ </Text>
700
+ <Text style={styles.modalDescription}>
701
+ {t('kyc.idCardCapture.scanQrCode')}
702
+ </Text>
703
+
704
+ {showQRModal && getQrCodeUrl() ? (
705
+ <Image
706
+ source={{ uri: getQrCodeUrl() }}
707
+ style={styles.qrCodeImage}
708
+ />
709
+ ) : null}
710
+
711
+ <TouchableOpacity
712
+ style={styles.closeButton}
713
+ onPress={() => setShowQRModal(false)}
714
+ >
715
+ <Text style={styles.closeButtonText}>
716
+ {t('common.close')}
717
+ </Text>
718
+ </TouchableOpacity>
719
+ </View>
720
+ </View>
721
+ </Modal>
722
+ )}
723
+
604
724
  </View>
605
725
  </View>
606
726
  );
@@ -757,43 +877,66 @@ const styles = StyleSheet.create({
757
877
  marginTop: 8,
758
878
  textAlign: 'center',
759
879
  },
760
- documentTypesContainer: {
761
- // flex: 1,
762
- // marginBottom: 16,
763
- // paddingHorizontal: 16,
764
- maxHeight: Dimensions.get('window').height - 400,
765
- },
766
- documentTypeButton: {
767
- flexDirection: 'row',
880
+ crossDeviceButton: {
881
+ marginTop: 16,
882
+ padding: 12,
768
883
  alignItems: 'center',
769
- backgroundColor: '#F8F9FA',
770
- padding: 16,
771
- marginBottom: 12,
772
- borderRadius: 12,
773
884
  borderWidth: 1,
774
- borderColor: '#E9ECEF',
885
+ borderColor: '#2DBD60',
886
+ borderRadius: 8,
887
+ backgroundColor: '#f0f9f0',
775
888
  },
776
- documentTypeIconContainer: {
777
-
778
- width: 40, height: 40,
779
- borderRadius: 10,
780
- alignItems: 'center',
889
+ crossDeviceText: {
890
+ color: '#2DBD60',
891
+ fontSize: 16,
892
+ fontWeight: '600',
893
+ },
894
+ modalOverlay: {
895
+ flex: 1,
896
+ backgroundColor: 'rgba(0,0,0,0.5)',
781
897
  justifyContent: 'center',
782
- marginRight: 16,
898
+ alignItems: 'center',
783
899
  },
784
- documentTypeIcon: {
785
- fontSize: 24,
786
-
900
+ modalContent: {
901
+ backgroundColor: 'white',
902
+ borderRadius: 16,
903
+ padding: 24,
904
+ alignItems: 'center',
905
+ width: '90%',
906
+ maxWidth: 340,
907
+ shadowColor: '#000',
908
+ shadowOffset: { width: 0, height: 2 },
909
+ shadowOpacity: 0.25,
910
+ shadowRadius: 4,
911
+ elevation: 5,
787
912
  },
788
- documentTypeName: {
789
- flex: 1,
790
- fontSize: 16,
791
- fontWeight: '600',
913
+ modalTitle: {
914
+ fontSize: 20,
915
+ fontWeight: 'bold',
916
+ marginBottom: 12,
792
917
  color: '#333',
793
918
  },
794
- documentTypeArrow: {
795
- fontSize: 18,
919
+ modalDescription: {
920
+ fontSize: 16,
796
921
  color: '#666',
797
- fontWeight: 'bold',
922
+ textAlign: 'center',
923
+ marginBottom: 20,
924
+ lineHeight: 22,
925
+ },
926
+ qrCodeImage: {
927
+ width: 200,
928
+ height: 200,
929
+ marginBottom: 20,
930
+ },
931
+ closeButton: {
932
+ paddingVertical: 10,
933
+ paddingHorizontal: 20,
934
+ backgroundColor: '#f5f5f5',
935
+ borderRadius: 8,
936
+ },
937
+ closeButtonText: {
938
+ fontSize: 16,
939
+ color: '#666',
940
+ fontWeight: '600',
798
941
  },
799
942
  });