@sanctum-key/react-native-sdk 1.0.9 → 1.0.11

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 (180) hide show
  1. package/README.md +3 -3
  2. package/android/build/.transforms/{c9d62bb333688ab562f51958998d5a48 → 9e34a0354bf8964d60c4c1392f5aa5b2}/transformed/classes/classes_dex/classes.dex +0 -0
  3. package/android/build/generated/source/buildConfig/debug/kyc/{SanctumKey → sanctumkey}/com/BuildConfig.java +2 -2
  4. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +1 -1
  5. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +1 -1
  6. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  7. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  8. package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/xml_file_paths.xml.flat +0 -0
  9. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
  10. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +1 -1
  11. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +1 -1
  12. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +1 -1
  13. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +1 -1
  14. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/kyc/{SanctumKey → sanctumkey}/com/BuildConfig.class +0 -0
  15. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +42 -42
  16. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +1 -1
  17. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  18. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -1
  19. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  20. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  21. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  22. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  23. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  24. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  57. package/android/build/outputs/logs/manifest-merger-debug-report.txt +52 -52
  58. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$1$5$1.class +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$1$5$2.class +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$1.class +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$2.class +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$3.class +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$4.class +0 -0
  65. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$5.class +0 -0
  66. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$6.class +0 -0
  67. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$1.class +0 -0
  68. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$2.class +0 -0
  69. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$3.class +0 -0
  70. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$4.class +0 -0
  71. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$View$1.class +0 -0
  72. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$1.class +0 -0
  73. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$2.class +0 -0
  74. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule.class +0 -0
  75. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkView.class +0 -0
  76. package/android/build.gradle +2 -2
  77. package/android/src/main/AndroidManifest.xml +1 -1
  78. package/android/src/main/java/kyc/{transfergratis/com/TransfergratisSdkModule.kt → sanctumkey/com/SanctumKeySdkModule.kt} +21 -18
  79. package/android/src/main/java/kyc/{transfergratis/com/TransfergratisSdkView.kt → sanctumkey/com/SanctumKeySdkView.kt} +2 -2
  80. package/build/package.json +5 -5
  81. package/build/src/App.d.ts +2 -2
  82. package/build/src/App.d.ts.map +1 -1
  83. package/build/src/App.js +2 -2
  84. package/build/src/App.js.map +1 -1
  85. package/build/src/{TransfergratisSdk.types.d.ts → SanctumKeySdk.types.d.ts} +3 -3
  86. package/build/src/SanctumKeySdk.types.d.ts.map +1 -0
  87. package/build/src/SanctumKeySdk.types.js +2 -0
  88. package/build/src/SanctumKeySdk.types.js.map +1 -0
  89. package/build/src/{TransfergratisSdkModule.d.ts → SanctumKeySdkModule.d.ts} +4 -4
  90. package/build/src/SanctumKeySdkModule.d.ts.map +1 -0
  91. package/build/src/{TransfergratisSdkModule.js → SanctumKeySdkModule.js} +2 -2
  92. package/build/src/SanctumKeySdkModule.js.map +1 -0
  93. package/build/src/{TransfergratisSdkModule.web.d.ts → SanctumKeySdkModule.web.d.ts} +4 -4
  94. package/build/src/SanctumKeySdkModule.web.d.ts.map +1 -0
  95. package/build/src/{TransfergratisSdkModule.web.js → SanctumKeySdkModule.web.js} +3 -3
  96. package/build/src/SanctumKeySdkModule.web.js.map +1 -0
  97. package/build/src/SanctumKeySdkView.d.ts +4 -0
  98. package/build/src/SanctumKeySdkView.d.ts.map +1 -0
  99. package/build/src/SanctumKeySdkView.js +7 -0
  100. package/build/src/SanctumKeySdkView.js.map +1 -0
  101. package/build/src/SanctumKeySdkView.web.d.ts +4 -0
  102. package/build/src/SanctumKeySdkView.web.d.ts.map +1 -0
  103. package/build/src/{TransfergratisSdkView.web.js → SanctumKeySdkView.web.js} +2 -2
  104. package/build/src/SanctumKeySdkView.web.js.map +1 -0
  105. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  106. package/build/src/components/KYCElements/CountrySelection.js +259 -63
  107. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  108. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  109. package/build/src/components/KYCElements/IDCardCapture.js +231 -69
  110. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  111. package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
  112. package/build/src/components/KYCElements/PhoneVerificationTemplate.js +160 -21
  113. package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
  114. package/build/src/components/NativeCameraView.js +1 -1
  115. package/build/src/components/NativeCameraView.js.map +1 -1
  116. package/build/src/components/TemplateKYCExample.d.ts +4 -3
  117. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  118. package/build/src/components/TemplateKYCExample.js +2 -2
  119. package/build/src/components/TemplateKYCExample.js.map +1 -1
  120. package/build/src/config/allowedDomains.js +6 -6
  121. package/build/src/config/allowedDomains.js.map +1 -1
  122. package/build/src/config/region_mapping.json +727 -0
  123. package/build/src/index.d.ts +3 -3
  124. package/build/src/index.d.ts.map +1 -1
  125. package/build/src/index.js +3 -3
  126. package/build/src/index.js.map +1 -1
  127. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  128. package/build/src/modules/api/CardAuthentification.js +3 -7
  129. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  130. package/build/src/modules/api/KYCService.d.ts +1 -2
  131. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  132. package/build/src/modules/api/KYCService.js +112 -60
  133. package/build/src/modules/api/KYCService.js.map +1 -1
  134. package/build/src/modules/camera/NativeCameraModule.js +17 -17
  135. package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
  136. package/build/src/web/WebKYCEntry.d.ts +2 -2
  137. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  138. package/build/src/web/WebKYCEntry.js +3 -2
  139. package/build/src/web/WebKYCEntry.js.map +1 -1
  140. package/expo-module.config.json +3 -3
  141. package/ios/TransfergratisSdk.podspec +2 -2
  142. package/ios/TransfergratisSdkModule.swift +12 -12
  143. package/package.json +5 -5
  144. package/src/App.tsx +2 -2
  145. package/src/{TransfergratisSdk.types.ts → SanctumKeySdk.types.ts} +2 -2
  146. package/src/{TransfergratisSdkModule.ts → SanctumKeySdkModule.ts} +3 -3
  147. package/src/{TransfergratisSdkModule.web.ts → SanctumKeySdkModule.web.ts} +3 -3
  148. package/src/SanctumKeySdkView.tsx +11 -0
  149. package/src/{TransfergratisSdkView.web.tsx → SanctumKeySdkView.web.tsx} +2 -2
  150. package/src/components/KYCElements/CountrySelection.tsx +300 -74
  151. package/src/components/KYCElements/IDCardCapture.tsx +322 -157
  152. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +201 -29
  153. package/src/components/NativeCameraView.tsx +1 -1
  154. package/src/components/TemplateKYCExample.tsx +23 -4
  155. package/src/config/allowedDomains.ts +6 -6
  156. package/src/i18n/README.md +1 -1
  157. package/src/index.ts +3 -3
  158. package/src/modules/api/CardAuthentification.ts +5 -8
  159. package/src/modules/api/KYCService.ts +174 -106
  160. package/src/modules/camera/NativeCameraModule.ts +17 -17
  161. package/src/web/WebKYCEntry.tsx +3 -3
  162. package/android/build/.transforms/ab90740579f5bd05b27b4343ada2d1c9/transformed/classes/classes_dex/classes.dex +0 -0
  163. package/android/build/.transforms/c9d62bb333688ab562f51958998d5a48/results.bin +0 -1
  164. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/BuildConfig.class.uniqueId0 +0 -0
  165. package/build/src/TransfergratisSdk.types.d.ts.map +0 -1
  166. package/build/src/TransfergratisSdk.types.js +0 -2
  167. package/build/src/TransfergratisSdk.types.js.map +0 -1
  168. package/build/src/TransfergratisSdkModule.d.ts.map +0 -1
  169. package/build/src/TransfergratisSdkModule.js.map +0 -1
  170. package/build/src/TransfergratisSdkModule.web.d.ts.map +0 -1
  171. package/build/src/TransfergratisSdkModule.web.js.map +0 -1
  172. package/build/src/TransfergratisSdkView.d.ts +0 -4
  173. package/build/src/TransfergratisSdkView.d.ts.map +0 -1
  174. package/build/src/TransfergratisSdkView.js +0 -7
  175. package/build/src/TransfergratisSdkView.js.map +0 -1
  176. package/build/src/TransfergratisSdkView.web.d.ts +0 -4
  177. package/build/src/TransfergratisSdkView.web.d.ts.map +0 -1
  178. package/build/src/TransfergratisSdkView.web.js.map +0 -1
  179. package/src/TransfergratisSdkView.tsx +0 -11
  180. /package/android/build/.transforms/{ab90740579f5bd05b27b4343ada2d1c9 → 9e34a0354bf8964d60c4c1392f5aa5b2}/results.bin +0 -0
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
- import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator } from 'react-native';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity } from 'react-native';
3
3
  import { showAlert } from '../../utils/platformAlert';
4
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
5
  import { TemplateComponent, LocalizedText, GovernmentDocumentType, ISilentCaptureResult, IBbox, GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
@@ -12,6 +12,13 @@ import { backVerification, checkTemplateType, frontVerification } from '../../mo
12
12
  import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
13
13
  import pathToBase64 from '../../utils/pathToBase64';
14
14
  import { cropByObb, cropImageWithBBoxWithTolerance, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../utils/cropByObb';
15
+ import REGION_MAPPING from '../../config/region_mapping.json';
16
+
17
+ const ISO_TO_COUNTRY_NAME: Record<string, string> = {
18
+ 'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada',
19
+ 'FR': 'France', 'GH': 'Ghana', 'ZA': 'South Africa', 'GB': 'Britain',
20
+ 'CI': 'Ivory Coast', 'SN': 'Senegal', 'TG': 'Togo', 'ML': 'Mali'
21
+ };
15
22
 
16
23
  interface IIDCardPayload { dir: string; file: string; mrz: string; templatePath?: string; }
17
24
  interface IDCardCaptureProps {
@@ -30,9 +37,10 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
30
37
  const [currentSide, setCurrentSide] = useState<'front' | 'back'>('front');
31
38
  const [bboxBySide, setBboxBySide] = useState<Record<string, IBbox>>({});
32
39
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
33
-
34
40
  const [isProcessingCapture, setIsProcessingCapture] = useState(false);
35
41
  const [processingImagePath, setProcessingImagePath] = useState<string | null>(null);
42
+ const [cameraKey, setCameraKey] = useState(0);
43
+
36
44
 
37
45
  const documentTypeMapping: Record<string, GovernmentDocumentType> = {
38
46
  'nationalId': 'national_id', 'passport': 'passport', 'driversLicense': 'drivers_licence',
@@ -46,6 +54,10 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
46
54
  return "";
47
55
  };
48
56
 
57
+ const refreshCamera = () => {
58
+ setCameraKey(prev => prev + 1);
59
+ };
60
+
49
61
  const countrySelectionData = useMemo(() => {
50
62
  const countrySelectionComponent = state.template.components.find(c => c.type === 'country_selection');
51
63
  return countrySelectionComponent ? state.componentData[countrySelectionComponent.id] : null;
@@ -65,23 +77,26 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
65
77
  if (JSON.stringify(value) !== JSON.stringify(capturedImages)) {
66
78
  const updatedImages = value as Record<string, IIDCardPayload>;
67
79
  setCapturedImages(updatedImages);
68
- Object.keys(updatedImages).forEach((side) => {
69
- const imageData = updatedImages[side];
70
- if (imageData?.dir) {
71
- setSilentCaptureResult(prev => ({
72
- ...prev, path: imageData.dir, success: true, isAnalyzing: false, mrz: imageData.mrz || '', templatePath: imageData.templatePath || '',
73
- }));
74
- }
75
- });
80
+
81
+ const currentImageData = updatedImages[currentSide];
82
+ if (currentImageData?.dir) {
83
+ setSilentCaptureResult(prev => ({
84
+ ...prev,
85
+ path: currentImageData.dir,
86
+ success: true,
87
+ isAnalyzing: false,
88
+ mrz: currentImageData.mrz || '',
89
+ templatePath: currentImageData.templatePath || '',
90
+ }));
91
+ }
76
92
  }
77
93
  }
78
- }, [value]);
94
+ }, [value, currentSide]);
79
95
 
80
96
  const cameraConfig = useMemo(() => {
81
97
  const instructions = selectedDocumentType
82
98
  ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
83
99
  : getLocalizedText(component.instructions as Record<string, LocalizedText>);
84
-
85
100
  return {
86
101
  cameraType: 'back' as const, flashMode: 'auto' as const,
87
102
  overlay: { guideText: instructions, bbox: { xMin: 15, yMin: 20, xMax: 85, yMax: 70, borderColor: '#2DBD60', borderWidth: 3, cornerRadius: 8 } }
@@ -89,31 +104,55 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
89
104
  }, [selectedDocumentType, locale, component.instructions]);
90
105
 
91
106
  const retakePicture = (sideToRetake: 'front' | 'back') => {
92
- // Completely wipe all processing states to prevent leakage
93
107
  setIsProcessingCapture(false);
94
108
  setProcessingImagePath(null);
95
109
  setSilentCaptureResult({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
96
-
97
- setShowCamera(true);
110
+ setShowCamera(true);
98
111
  actions.showCustomStepper(false);
99
-
100
112
  setCapturedImages((prev) => { const newState = { ...prev }; delete newState[sideToRetake]; return newState; });
101
113
  if (value) { const newValue = { ...value }; delete newValue[sideToRetake]; onValueChange(newValue); }
102
114
  };
103
115
 
104
- const getCurrentSideVerification = (currentSide: string) => {
105
- if (!selectedDocumentType || !countryData?.regionMapping) return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
106
- const regionMapping = countryData.regionMapping[selectedDocumentType.type as GovernmentDocumentType];
107
- const authMethod: string[] = []; const mrzTypes: string[] = [];
108
- const key = selectedDocumentType.region?.trim()?.length > 0 ? selectedDocumentType.region.trim() : 'root';
116
+ const getCurrentSideVerification = (currentSide: string, countryKey: string) => {
117
+ const rawDocType = countrySelectionData?.documentType;
118
+
119
+ if (!rawDocType || !countryKey) {
120
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
121
+ }
122
+
123
+ const rawCountryName = ISO_TO_COUNTRY_NAME[countryData?.code || ''] || countryData?.code || countryKey;
124
+ const baseMapping = (REGION_MAPPING as any).regionMapping || REGION_MAPPING;
125
+
126
+ let countryMapping = baseMapping[rawCountryName];
127
+
128
+ // Fallback search in case of case mismatches
129
+ if (!countryMapping) {
130
+ const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase() || k.toLowerCase() === countryKey.toLowerCase());
131
+ if (foundKey) countryMapping = baseMapping[foundKey];
132
+ }
133
+
134
+ if (!countryMapping) {
135
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
136
+ }
137
+
138
+ const regionMapping = countryMapping[rawDocType];
139
+ if (!regionMapping) {
140
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
141
+ }
142
+
143
+ const authMethod: string[] = [];
144
+ const mrzTypes: string[] = [];
145
+ const key = countrySelectionData.region?.trim()?.length > 0 ? countrySelectionData.region.trim() : 'root';
146
+
109
147
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
110
148
  regionMapping[key].forEach((item: any) => {
111
- if (item[currentSide as keyof typeof item]) {
112
- authMethod.push(item[currentSide as keyof typeof item]);
149
+ if (item[currentSide]) {
150
+ authMethod.push(item[currentSide]);
113
151
  if (item?.mrz_type) mrzTypes.push(item?.mrz_type);
114
152
  }
115
153
  });
116
154
  }
155
+
117
156
  return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping, key };
118
157
  }
119
158
 
@@ -121,7 +160,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
121
160
  if (!mapping || !mapping[selectedDocumentType]) return null;
122
161
  const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
123
162
  if (!fileName) return null;
124
- const pyName = `${fileName}.py`;
163
+ const pyName = `${fileName}.py`;
125
164
  const found = mapping[selectedDocumentType].find((item: any) => item.py_file === pyName);
126
165
  return found?.mrz_type || null;
127
166
  }
@@ -137,10 +176,8 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
137
176
 
138
177
  const autoCapture = async (capturePath: string, verified: ISilentCaptureResult) => {
139
178
  if (isProcessingCapture) return;
140
-
141
- setIsProcessingCapture(true);
179
+ setIsProcessingCapture(true);
142
180
  setProcessingImagePath(capturePath);
143
-
144
181
  try {
145
182
  let imagePathForUpload = capturePath;
146
183
  if (verified.bbox) {
@@ -150,13 +187,11 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
150
187
  imagePathForUpload = capturePath;
151
188
  }
152
189
  }
153
-
154
190
  const base64 = await pathToBase64(imagePathForUpload);
155
191
  const newImages = {
156
192
  ...capturedImages,
157
193
  [currentSide]: { dir: imagePathForUpload, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
158
194
  };
159
-
160
195
  setCapturedImages(newImages);
161
196
  if (verified.country && verified.documentType) {
162
197
  onValueChange({
@@ -165,15 +200,13 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
165
200
  documentType: GovernmentDocumentTypeBackend[verified.documentType as keyof typeof GovernmentDocumentTypeBackend] || '',
166
201
  });
167
202
  }
168
-
169
203
  setTimeout(() => {
170
204
  setShowCamera(false);
171
205
  actions.showCustomStepper(true);
172
206
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
173
207
  setIsProcessingCapture(false);
174
- setProcessingImagePath(null);
208
+ setProcessingImagePath(null);
175
209
  }, 600);
176
-
177
210
  } catch (e: any) {
178
211
  showAlert('Error', e?.message || 'Impossible de capturer la photo');
179
212
  setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false }));
@@ -184,25 +217,23 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
184
217
 
185
218
  const handleSilentCapture = async (result: { success: boolean; path?: string; error?: string }) => {
186
219
  if (silentCaptureResult.isAnalyzing || isProcessingCapture) return;
187
-
188
220
  if (result.success && result.path) {
189
221
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
190
- let templatePath = silentCaptureResult.templatePath || '';
222
+
223
+ // 🚨 Force a template fetch if we haven't successfully saved the current side yet
224
+ let templatePath = capturedImages[currentSide]?.templatePath || '';
191
225
  let templateBbox: IBbox | undefined;
192
226
  let templateResponse: any;
193
-
227
+
194
228
  if (!selectedDocumentType) {
195
229
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' })); return;
196
230
  }
197
231
 
198
- const regionMappings = getCurrentSideVerification(currentSide);
199
-
200
232
  try {
201
- if (templatePath.length === 0) {
233
+ if (!templatePath) {
202
234
  const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide }, env);
203
235
  templateResponse = templateType;
204
236
  if (templateType.template_path) templatePath = templateType.template_path;
205
-
206
237
  if (templateType.card_obb) {
207
238
  const obbConfidence = getObbConfidence((templateType as any).card_obb);
208
239
  if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
@@ -216,26 +247,46 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
216
247
  }
217
248
  }
218
249
 
250
+ const extractedCountryKey = templatePath ? templatePath.split('/')[0] : (ISO_TO_COUNTRY_NAME[countryData?.code || ''] || 'root');
251
+ const regionMappings = getCurrentSideVerification(currentSide, extractedCountryKey);
252
+
219
253
  let verificationRes: any;
220
254
  if (currentSide === 'front') {
221
255
  const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
222
- const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
256
+ const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
223
257
  verificationRes = await frontVerification({ path: result.path, regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : regionMappings.authMethod, mrzTypes: regionMappings.mrzTypes }, selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type as keyof typeof GovernmentDocumentTypeShorted] || '', code: countryData?.code || '', currentSide, templatePath, mrzType }, env);
224
258
  } else {
225
- const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
226
- const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
227
- verificationRes = await backVerification({ path: result.path, regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : regionMappings.authMethod, mrzTypes: regionMappings.mrzTypes }, selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '', code: countryData?.code || '', currentSide, templatePath, mrzType: backMrzType, templateResponse }, env);
228
- }
259
+ let matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
260
+
261
+ if (!matchedBackAuthMethod && currentSide === 'back') {
262
+ matchedBackAuthMethod = 'MRZ';
263
+ }
229
264
 
265
+ const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
266
+
267
+ verificationRes = await backVerification({
268
+ path: result.path,
269
+ regionMapping: {
270
+ authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : regionMappings.authMethod,
271
+ mrzTypes: regionMappings.mrzTypes
272
+ },
273
+ selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
274
+ code: countryData?.code || '',
275
+ currentSide,
276
+ templatePath,
277
+ mrzType: backMrzType,
278
+ templateResponse
279
+ }, env);
280
+ }
281
+
230
282
  const bbox = verificationRes?.bbox || templateBbox;
231
283
  const mrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
232
-
233
284
  const verifiedResult: ISilentCaptureResult = { path: result.path, templatePath, bbox, success: true, mrz, isAnalyzing: false, country: countryData?.code, documentType: selectedDocumentType.type };
285
+
234
286
  setSilentCaptureResult(verifiedResult);
235
287
  if (bbox) setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
236
-
237
288
  await autoCapture(result.path, verifiedResult);
238
-
289
+
239
290
  } catch (error: any) {
240
291
  const isCardNotFullyInFrame = error?.message === 'CARD_NOT_FULLY_IN_FRAME' || error?.message?.includes('entirement');
241
292
  const errorMessage = isCardNotFullyInFrame ? t('kyc.idCardCapture.cardNotFullyInFrame') : (error?.message || 'Erreur de détection');
@@ -253,7 +304,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
253
304
  if (!countrySelectionData || !selectedDocumentType) {
254
305
  return (
255
306
  <View style={styles.root}>
256
- <View style={styles.container}>
307
+ <View style={styles.previewContainer}>
257
308
  <Text style={styles.title}>{getLocalizedText(component.labels)}</Text>
258
309
  <Text style={styles.description}>
259
310
  {state.currentLanguage === "en" ? "Please complete the country and document selection first." : "Veuillez d'abord compléter la sélection du pays et du document."}
@@ -265,84 +316,100 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
265
316
 
266
317
  // --- CAMERA RENDER ---
267
318
  if (showCamera) {
268
- const isBusy = isProcessingCapture;
269
-
319
+ const isBusy = isProcessingCapture;
270
320
  return (
271
- <View style={styles.cameraContainer}>
272
- <EnhancedCameraView
273
- key={currentSide} // 🚨 BUG FIX: Forces the camera instance to completely reset when switching sides
274
- showCamera={true}
275
- isProcessing={isBusy}
276
- cameraType={cameraConfig.cameraType}
277
- style={styles.camera}
278
- onError={handleError}
279
- onSilentCapture={handleSilentCapture}
280
- silentCaptureResult={silentCaptureResult}
281
- overlayComponent={
282
- <>
283
- {!isBusy && silentCaptureResult.isAnalyzing && (
284
- <View style={styles.topAnalyzingPillContainer}>
285
- <View style={styles.topAnalyzingPill}>
286
- <ActivityIndicator size="small" color="white" />
287
- <Text style={styles.analyzingPillText}>
288
- {state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
289
- </Text>
290
- </View>
291
- </View>
292
- )}
321
+ <View style={styles.root}>
322
+ <View style={styles.cameraWrapper}>
323
+
324
+ {/* Web/Desktop Clean Header */}
325
+ <View style={styles.headerContainer}>
326
+ <Text style={styles.headerTitle}>
327
+ {selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : ''}
328
+ </Text>
329
+ <View style={styles.stepBadge}>
330
+ <Text style={styles.stepText}>
331
+ {t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
332
+ </Text>
333
+ </View>
334
+ </View>
293
335
 
294
- {isBusy && (
295
- <View style={StyleSheet.absoluteFillObject}>
296
- {processingImagePath && (
297
- <Image
298
- source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }}
299
- style={StyleSheet.absoluteFillObject}
300
- resizeMode="cover"
301
- />
336
+ <View style={styles.cameraFeedContainer}>
337
+ <EnhancedCameraView
338
+ key={`${currentSide}-${cameraKey}`}
339
+ showCamera={true}
340
+ isProcessing={isBusy}
341
+ cameraType={cameraConfig.cameraType}
342
+ style={styles.camera}
343
+ onError={handleError}
344
+ onSilentCapture={handleSilentCapture}
345
+ silentCaptureResult={silentCaptureResult}
346
+ overlayComponent={
347
+ <>
348
+ {!isBusy && silentCaptureResult.isAnalyzing && (
349
+ <View style={styles.topAnalyzingPillContainer}>
350
+ <View style={styles.topAnalyzingPill}>
351
+ <ActivityIndicator size="small" color="white" />
352
+ <Text style={styles.analyzingPillText}>
353
+ {state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
354
+ </Text>
355
+ </View>
356
+ </View>
302
357
  )}
303
- <View style={styles.processingOverlay}>
304
- <ActivityIndicator size="large" color="#2DBD60" />
305
- <Text style={styles.processingText}>
306
- {state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
307
- </Text>
308
- </View>
309
- </View>
310
- )}
311
-
312
- <IdCardOverlay
313
- xMin={cameraConfig.overlay.bbox.xMin} yMin={cameraConfig.overlay.bbox.yMin} xMax={cameraConfig.overlay.bbox.xMax} yMax={cameraConfig.overlay.bbox.yMax}
314
- instructions={cameraConfig.overlay.guideText}
315
- cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0 as number}
316
- isSuccess={silentCaptureResult.success}
317
- language={state.currentLanguage}
318
- stepperProps={{
319
- back: () => {
320
- if (currentSide === 'back') {
321
- setCurrentSide('front');
322
- setShowCamera(false);
323
- // 🚨 Clean up any residual state when going backwards
324
- setIsProcessingCapture(false);
325
- setProcessingImagePath(null);
326
-
327
- if (capturedImages['front']?.dir) {
328
- const frontImage = capturedImages['front'];
329
- setSilentCaptureResult((prev) => ({ ...prev, path: frontImage.dir, success: true, isAnalyzing: false, error: '', mrz: frontImage.mrz || '', templatePath: frontImage.templatePath || '', bbox: bboxBySide['front'] }));
330
- } else { setSilentCaptureResult((prev) => ({ ...prev, path: '', success: false, isAnalyzing: false, error: '', templatePath: '' })); }
331
- } else { actions.previousComponent(); }
332
- },
333
- selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
334
- step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
335
- }}
336
- />
337
- </>
338
- }
339
- />
340
-
341
- {silentCaptureResult.error && !isBusy ? (
342
- <View style={styles.floatingErrorBanner}>
343
- <Text style={styles.floatingErrorText}>{silentCaptureResult.error}</Text>
358
+ {isBusy && (
359
+ <View style={StyleSheet.absoluteFillObject}>
360
+ {processingImagePath && (
361
+ <Image
362
+ source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }}
363
+ style={StyleSheet.absoluteFillObject}
364
+ resizeMode="cover"
365
+ />
366
+ )}
367
+ <View style={styles.processingOverlay}>
368
+ <ActivityIndicator size="large" color="#2DBD60" />
369
+ <Text style={styles.processingText}>
370
+ {state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
371
+ </Text>
372
+ </View>
373
+ </View>
374
+ )}
375
+ <IdCardOverlay
376
+ xMin={cameraConfig.overlay.bbox.xMin} yMin={cameraConfig.overlay.bbox.yMin} xMax={cameraConfig.overlay.bbox.xMax} yMax={cameraConfig.overlay.bbox.yMax}
377
+ instructions={cameraConfig.overlay.guideText}
378
+ cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0 as number}
379
+ isSuccess={silentCaptureResult.success}
380
+ language={state.currentLanguage}
381
+ stepperProps={{
382
+ back: () => {
383
+ if (currentSide === 'back') {
384
+ setCurrentSide('front');
385
+ setShowCamera(false);
386
+ setIsProcessingCapture(false);
387
+ setProcessingImagePath(null);
388
+ if (capturedImages['front']?.dir) {
389
+ const frontImage = capturedImages['front'];
390
+ setSilentCaptureResult((prev) => ({ ...prev, path: frontImage.dir, success: true, isAnalyzing: false, error: '', mrz: frontImage.mrz || '', templatePath: frontImage.templatePath || '', bbox: bboxBySide['front'] }));
391
+ } else { setSilentCaptureResult((prev) => ({ ...prev, path: '', success: false, isAnalyzing: false, error: '', templatePath: '' })); }
392
+ } else { actions.previousComponent(); }
393
+ },
394
+ selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
395
+ step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
396
+ }}
397
+ />
398
+
399
+ <TouchableOpacity style={styles.refreshButton} onPress={refreshCamera}>
400
+ <Text style={styles.refreshButtonText}>Refresh Camera</Text>
401
+ </TouchableOpacity>
402
+ </>
403
+ }
404
+ />
405
+ {/* Elegant Floating Error Banner below the cutout */}
406
+ {silentCaptureResult.error && !isBusy ? (
407
+ <View style={styles.floatingErrorBanner}>
408
+ <Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
409
+ </View>
410
+ ) : null}
344
411
  </View>
345
- ) : null}
412
+ </View>
346
413
  </View>
347
414
  );
348
415
  }
@@ -359,7 +426,6 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
359
426
  {getLocalizedText(component.instructions)}
360
427
  </Text>
361
428
  <View style={{ alignItems: 'center', justifyContent: 'center', flexDirection: "column", gap: 16 }}>
362
-
363
429
  {silentCaptureResult?.error === 'TOO_FAR_AWAY' && (
364
430
  <View style={styles.warningBanner}>
365
431
  <Text style={styles.warningText}>
@@ -367,7 +433,6 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
367
433
  </Text>
368
434
  </View>
369
435
  )}
370
-
371
436
  <View style={styles.imagePreviewWrapper}>
372
437
  {capturedImages[currentSide]?.dir ? (
373
438
  <Image source={{ uri: capturedImages[currentSide].dir }} style={styles.previewImage} />
@@ -375,7 +440,6 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
375
440
  <Image source={{ uri: silentCaptureResult.path }} style={styles.previewImage} />
376
441
  ) : null}
377
442
  </View>
378
-
379
443
  {!capturedImages[currentSide]?.dir && (
380
444
  <Button
381
445
  title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"}
@@ -383,22 +447,19 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
383
447
  variant="primary" size="large" fullWidth
384
448
  />
385
449
  )}
386
-
387
450
  {capturedImages[currentSide]?.dir && (
388
451
  <>
389
452
  <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => retakePicture(currentSide)} variant="outline" size="medium" fullWidth />
390
453
  <Button title={t('common.next')} onPress={() => {
391
454
  if (!selectedDocumentType) { showAlert('Error', 'Document type not selected'); return; }
392
-
393
- // 🚨 BUG FIX: Clear all state before moving forward
394
- if (currentSide === 'back' || selectedDocumentType.type === 'passport') {
395
- actions.nextComponent();
396
- } else {
397
- setShowCamera(true);
398
- setCurrentSide('back');
399
- setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '' });
400
- setIsProcessingCapture(false);
401
- setProcessingImagePath(null);
455
+ if (currentSide === 'back' || selectedDocumentType.type === 'passport') {
456
+ actions.nextComponent();
457
+ } else {
458
+ setShowCamera(true);
459
+ setCurrentSide('back');
460
+ setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: undefined });
461
+ setIsProcessingCapture(false);
462
+ setProcessingImagePath(null);
402
463
  }
403
464
  }} variant="primary" size="large" fullWidth />
404
465
  </>
@@ -412,28 +473,132 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
412
473
  };
413
474
 
414
475
  const styles = StyleSheet.create({
415
- root: { flex: 1, maxWidth: 760, width: '100%' },
416
- container: { backgroundColor: 'white', margin: 10, borderRadius: 10, paddingVertical: 16, paddingHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.35, shadowRadius: 4.84, elevation: 10 },
417
- cameraContainer: { flex: 1, width: '100%', height: '100%' },
418
- previewContainer: { width: '95%', backgroundColor: 'white', margin: 10, borderRadius: 10, paddingVertical: 16, paddingHorizontal: 16 },
419
- previewItemContainer: {},
420
- title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
421
- description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
422
- sideContainer: { marginBottom: 24 },
423
- sideTitle: { fontSize: 25, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
424
- imagePreviewWrapper: { width: '100%', height: 200, borderRadius: 12, padding: 1, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#000' },
425
- previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'contain' },
426
- floatingErrorBanner: { position: 'absolute', top: 60, left: '10%', right: '10%', backgroundColor: 'rgba(220, 38, 38, 0.95)', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 8, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 5, elevation: 8, zIndex: 100 },
427
- floatingErrorText: { color: 'white', fontSize: 14, fontWeight: '700', textAlign: 'center' },
428
- processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
429
- processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
430
- warningBanner: { backgroundColor: '#FF9500', padding: 12, borderRadius: 8, width: '100%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 4 },
431
- warningText: { color: 'white', fontWeight: 'bold', textAlign: 'center', fontSize: 16 },
432
- errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
433
- topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
434
- topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
435
- analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' },
476
+ root: {
477
+ flex: 1,
478
+ width: '100%',
479
+ backgroundColor: 'transparent',
480
+ alignSelf: 'center',
481
+ ...(Platform.OS === 'web'
482
+ ? ({
483
+ minHeight: '85vh',
484
+ justifyContent: 'center',
485
+ alignItems: 'center',
486
+ // Note: backdropFilter is valid in React Native Web but TS might complain, cast safely
487
+ backdropFilter: 'blur(8px)'
488
+ } as any)
489
+ : {})
490
+ },
491
+ cameraWrapper: {
492
+ width: '100%',
493
+ backgroundColor: '#FFFFFF',
494
+ overflow: 'hidden',
495
+ ...(Platform.OS === 'web'
496
+ ? ({
497
+ maxWidth: 500,
498
+ height: 700,
499
+ maxHeight: '90vh', // TypeScript will now ignore this thanks to the cast below
500
+ borderRadius: 24,
501
+ shadowColor: '#000',
502
+ shadowOffset: { width: 0, height: 20 },
503
+ shadowOpacity: 0.25,
504
+ shadowRadius: 35,
505
+ elevation: 24,
506
+ } as any) // 🚨 CAST TO ANY
507
+ : {
508
+ flex: 1,
509
+ })
510
+ },
511
+ headerContainer: {
512
+ flexDirection: 'row',
513
+ alignItems: 'center',
514
+ justifyContent: 'space-between',
515
+ paddingHorizontal: 24,
516
+ paddingVertical: 18,
517
+ backgroundColor: '#FFFFFF',
518
+ borderBottomWidth: 1,
519
+ borderBottomColor: '#F1F5F9',
520
+ zIndex: 10,
521
+ // Mobile hidden, Web visible to replace floating text
522
+ ...(Platform.OS !== 'web' ? { display: 'none' } : {})
523
+ },
524
+ headerTitle: {
525
+ fontSize: 18,
526
+ fontWeight: '700',
527
+ color: '#0F172A',
528
+ },
529
+ stepBadge: {
530
+ backgroundColor: '#F1F5F9',
531
+ paddingHorizontal: 12,
532
+ paddingVertical: 6,
533
+ borderRadius: 20,
534
+ },
535
+ stepText: {
536
+ fontSize: 13,
537
+ fontWeight: '600',
538
+ color: '#64748B',
539
+ },
540
+ cameraFeedContainer: {
541
+ flex: 1,
542
+ position: 'relative',
543
+ backgroundColor: '#000',
544
+ },
436
545
  camera: {
437
546
  flex: 1,
438
547
  },
548
+ previewContainer: {
549
+ width: '100%',
550
+ backgroundColor: 'white',
551
+ borderRadius: 12,
552
+ paddingVertical: 24,
553
+ paddingHorizontal: 20,
554
+ shadowColor: '#000',
555
+ shadowOffset: { width: 0, height: 4 },
556
+ shadowOpacity: 0.1,
557
+ shadowRadius: 12,
558
+ elevation: 8,
559
+ ...(Platform.OS === 'web' ? { alignSelf: 'center', maxWidth: 600 } : { margin: 10, width: '95%' })
560
+ },
561
+ previewItemContainer: {
562
+ flexGrow: 1,
563
+ },
564
+ title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
565
+ description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
566
+ sideContainer: { marginBottom: 24 },
567
+ sideTitle: { fontSize: 25, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
568
+ imagePreviewWrapper: {
569
+ width: '100%', height: 220, borderRadius: 12, padding: 1, overflow: 'hidden',
570
+ shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0'
571
+ },
572
+ previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
573
+ floatingErrorBanner: {
574
+ position: 'absolute',
575
+ bottom: 30, // Pushed to the bottom for professional feel
576
+ left: 24,
577
+ right: 24,
578
+ backgroundColor: '#FEF2F2',
579
+ borderWidth: 1,
580
+ borderColor: '#FCA5A5',
581
+ paddingVertical: 12,
582
+ paddingHorizontal: 16,
583
+ borderRadius: 12,
584
+ alignItems: 'center',
585
+ justifyContent: 'center',
586
+ shadowColor: '#DC2626',
587
+ shadowOffset: { width: 0, height: 4 },
588
+ shadowOpacity: 0.1,
589
+ shadowRadius: 8,
590
+ elevation: 8,
591
+ zIndex: 100
592
+ },
593
+ floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
594
+ processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
595
+ processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
596
+ warningBanner: { backgroundColor: '#FF9500', padding: 12, borderRadius: 8, width: '100%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 4 },
597
+ warningText: { color: 'white', fontWeight: 'bold', textAlign: 'center', fontSize: 16 },
598
+ errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
599
+ topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
600
+ topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
601
+ analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' },
602
+ refreshButton: { position: 'absolute', bottom: 100, alignSelf: 'center', backgroundColor: 'rgba(0,0,0,0.5)', padding: 10, borderRadius: 20, zIndex: 500 },
603
+ refreshButtonText: { color: 'white', fontWeight: 'bold' },
439
604
  });