@sanctum-key/react-native-sdk 1.0.6 → 1.0.7

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 (206) hide show
  1. package/README.md +3 -3
  2. package/android/build/.transforms/{f62cb96b2d1f78ca96ab35932dd530dc → c9d62bb333688ab562f51958998d5a48}/transformed/classes/classes_dex/classes.dex +0 -0
  3. package/android/build/generated/source/buildConfig/debug/kyc/{transfergratis → 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/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
  9. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/kyc/SanctumKey/com/BuildConfig.class +0 -0
  10. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +1 -1
  11. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +1 -1
  12. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  13. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -1
  14. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  15. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  16. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  17. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  18. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  19. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  20. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  21. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  22. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  23. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  24. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  48. package/android/build/outputs/logs/manifest-merger-debug-report.txt +1 -1
  49. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  50. package/android/build/tmp/kotlin-classes/debug/kyc/SanctumKey/com/SanctumKeySdkModule$definition$1$5$1.class +0 -0
  51. package/android/build/tmp/kotlin-classes/debug/kyc/SanctumKey/com/SanctumKeySdkModule$definition$1$5$2.class +0 -0
  52. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunction$1.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$1.class} +0 -0
  53. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunction$2.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$2.class} +0 -0
  54. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunction$3.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$3.class} +0 -0
  55. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunction$4.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$4.class} +0 -0
  56. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunction$5.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$5.class} +0 -0
  57. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunction$6.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$6.class} +0 -0
  58. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$1.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$1.class} +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$2.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$2.class} +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$3.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$3.class} +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$4.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$4.class} +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/kyc/SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$View$1.class +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$lambda$4$$inlined$Prop$1.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$1.class} +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule$definition$lambda$5$lambda$4$$inlined$Prop$2.class → SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$2.class} +0 -0
  65. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule.class → SanctumKey/com/SanctumKeySdkModule.class} +0 -0
  66. package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkView.class → SanctumKey/com/SanctumKeySdkView.class} +0 -0
  67. package/android/build.gradle +2 -2
  68. package/android/src/main/AndroidManifest.xml +1 -1
  69. package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkModule.kt +6 -6
  70. package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkView.kt +2 -2
  71. package/build/package.json +9 -7
  72. package/build/src/App.d.ts +2 -2
  73. package/build/src/App.d.ts.map +1 -1
  74. package/build/src/App.js +2 -2
  75. package/build/src/App.js.map +1 -1
  76. package/build/src/{TransfergratisSdk.types.d.ts → SanctumKeySdk.types.d.ts} +3 -3
  77. package/build/src/SanctumKeySdk.types.d.ts.map +1 -0
  78. package/build/src/SanctumKeySdk.types.js +2 -0
  79. package/build/src/SanctumKeySdk.types.js.map +1 -0
  80. package/build/src/{TransfergratisSdkModule.d.ts → SanctumKeySdkModule.d.ts} +4 -4
  81. package/build/src/SanctumKeySdkModule.d.ts.map +1 -0
  82. package/build/src/{TransfergratisSdkModule.js → SanctumKeySdkModule.js} +2 -2
  83. package/build/src/SanctumKeySdkModule.js.map +1 -0
  84. package/build/src/{TransfergratisSdkModule.web.d.ts → SanctumKeySdkModule.web.d.ts} +4 -4
  85. package/build/src/SanctumKeySdkModule.web.d.ts.map +1 -0
  86. package/build/src/{TransfergratisSdkModule.web.js → SanctumKeySdkModule.web.js} +3 -3
  87. package/build/src/SanctumKeySdkModule.web.js.map +1 -0
  88. package/build/src/SanctumKeySdkView.d.ts +4 -0
  89. package/build/src/SanctumKeySdkView.d.ts.map +1 -0
  90. package/build/src/SanctumKeySdkView.js +7 -0
  91. package/build/src/SanctumKeySdkView.js.map +1 -0
  92. package/build/src/SanctumKeySdkView.web.d.ts +4 -0
  93. package/build/src/SanctumKeySdkView.web.d.ts.map +1 -0
  94. package/build/src/{TransfergratisSdkView.web.js → SanctumKeySdkView.web.js} +2 -2
  95. package/build/src/SanctumKeySdkView.web.js.map +1 -0
  96. package/build/src/api/axios.js +2 -2
  97. package/build/src/api/axios.js.map +1 -1
  98. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  99. package/build/src/components/EnhancedCameraView.js +66 -338
  100. package/build/src/components/EnhancedCameraView.js.map +1 -1
  101. package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
  102. package/build/src/components/KYCElements/EmailVerificationTemplate.js +93 -15
  103. package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
  104. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  105. package/build/src/components/KYCElements/IDCardCapture.js +167 -695
  106. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  107. package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
  108. package/build/src/components/KYCElements/PhoneVerificationTemplate.js +269 -40
  109. package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
  110. package/build/src/components/KYCElements/SelfieCapture.d.ts +1 -1
  111. package/build/src/components/KYCElements/SelfieCapture.d.ts.map +1 -1
  112. package/build/src/components/KYCElements/SelfieCapture.js +130 -192
  113. package/build/src/components/KYCElements/SelfieCapture.js.map +1 -1
  114. package/build/src/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  115. package/build/src/components/KYCElements/SelfieCaptureTemplate.js +131 -433
  116. package/build/src/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  117. package/build/src/components/NativeCameraView.js +1 -1
  118. package/build/src/components/NativeCameraView.js.map +1 -1
  119. package/build/src/components/OverLay/IdCard.d.ts +3 -2
  120. package/build/src/components/OverLay/IdCard.d.ts.map +1 -1
  121. package/build/src/components/OverLay/IdCard.js +149 -141
  122. package/build/src/components/OverLay/IdCard.js.map +1 -1
  123. package/build/src/components/OverLay/SelfieOverlay.d.ts +2 -1
  124. package/build/src/components/OverLay/SelfieOverlay.d.ts.map +1 -1
  125. package/build/src/components/OverLay/SelfieOverlay.js +37 -95
  126. package/build/src/components/OverLay/SelfieOverlay.js.map +1 -1
  127. package/build/src/components/OverLay/type.d.ts +1 -0
  128. package/build/src/components/OverLay/type.d.ts.map +1 -1
  129. package/build/src/components/OverLay/type.js.map +1 -1
  130. package/build/src/components/Svgs/scanningLine.d.ts +2 -1
  131. package/build/src/components/Svgs/scanningLine.d.ts.map +1 -1
  132. package/build/src/components/Svgs/scanningLine.js +55 -51
  133. package/build/src/components/Svgs/scanningLine.js.map +1 -1
  134. package/build/src/config/KYCConfig.js +1 -1
  135. package/build/src/config/KYCConfig.js.map +1 -1
  136. package/build/src/config/allowedDomains.js +6 -6
  137. package/build/src/config/allowedDomains.js.map +1 -1
  138. package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  139. package/build/src/hooks/useTemplateKYCFlow.js +37 -38
  140. package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
  141. package/build/src/index.d.ts +3 -3
  142. package/build/src/index.d.ts.map +1 -1
  143. package/build/src/index.js +3 -3
  144. package/build/src/index.js.map +1 -1
  145. package/build/src/modules/api/CardAuthentification.d.ts +0 -5
  146. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  147. package/build/src/modules/api/CardAuthentification.js +114 -116
  148. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  149. package/build/src/modules/api/KYCService.d.ts +11 -1
  150. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  151. package/build/src/modules/api/KYCService.js +101 -38
  152. package/build/src/modules/api/KYCService.js.map +1 -1
  153. package/build/src/modules/camera/NativeCameraModule.js +17 -17
  154. package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
  155. package/expo-module.config.json +2 -2
  156. package/ios/TransfergratisSdk.podspec +2 -2
  157. package/ios/TransfergratisSdkModule.swift +12 -12
  158. package/package.json +9 -7
  159. package/src/App.tsx +2 -2
  160. package/src/{TransfergratisSdk.types.ts → SanctumKeySdk.types.ts} +2 -2
  161. package/src/{TransfergratisSdkModule.ts → SanctumKeySdkModule.ts} +3 -3
  162. package/src/{TransfergratisSdkModule.web.ts → SanctumKeySdkModule.web.ts} +3 -3
  163. package/src/SanctumKeySdkView.tsx +11 -0
  164. package/src/{TransfergratisSdkView.web.tsx → SanctumKeySdkView.web.tsx} +2 -2
  165. package/src/api/axios.ts +2 -2
  166. package/src/components/EnhancedCameraView.tsx +81 -400
  167. package/src/components/KYCElements/EmailVerificationTemplate.tsx +115 -26
  168. package/src/components/KYCElements/IDCardCapture.tsx +228 -868
  169. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +328 -60
  170. package/src/components/KYCElements/SelfieCapture.tsx +184 -213
  171. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +330 -662
  172. package/src/components/NativeCameraView.tsx +1 -1
  173. package/src/components/OverLay/IdCard.tsx +218 -217
  174. package/src/components/OverLay/SelfieOverlay.tsx +56 -134
  175. package/src/components/OverLay/type.ts +1 -0
  176. package/src/components/Svgs/scanningLine.tsx +71 -72
  177. package/src/config/KYCConfig.ts +1 -1
  178. package/src/config/allowedDomains.ts +6 -6
  179. package/src/hooks/useTemplateKYCFlow.tsx +45 -39
  180. package/src/i18n/README.md +1 -1
  181. package/src/index.ts +3 -3
  182. package/src/modules/api/CardAuthentification.ts +202 -200
  183. package/src/modules/api/KYCService.ts +168 -53
  184. package/src/modules/camera/NativeCameraModule.ts +17 -17
  185. package/android/build/tmp/kotlin-classes/debug/kyc/transfergratis/com/TransfergratisSdkModule$definition$1$5$1.class +0 -0
  186. package/android/build/tmp/kotlin-classes/debug/kyc/transfergratis/com/TransfergratisSdkModule$definition$1$5$2.class +0 -0
  187. package/android/build/tmp/kotlin-classes/debug/kyc/transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$View$1.class +0 -0
  188. package/build/src/TransfergratisSdk.types.d.ts.map +0 -1
  189. package/build/src/TransfergratisSdk.types.js +0 -2
  190. package/build/src/TransfergratisSdk.types.js.map +0 -1
  191. package/build/src/TransfergratisSdkModule.d.ts.map +0 -1
  192. package/build/src/TransfergratisSdkModule.js.map +0 -1
  193. package/build/src/TransfergratisSdkModule.web.d.ts.map +0 -1
  194. package/build/src/TransfergratisSdkModule.web.js.map +0 -1
  195. package/build/src/TransfergratisSdkView.d.ts +0 -4
  196. package/build/src/TransfergratisSdkView.d.ts.map +0 -1
  197. package/build/src/TransfergratisSdkView.js +0 -7
  198. package/build/src/TransfergratisSdkView.js.map +0 -1
  199. package/build/src/TransfergratisSdkView.web.d.ts +0 -4
  200. package/build/src/TransfergratisSdkView.web.d.ts.map +0 -1
  201. package/build/src/TransfergratisSdkView.web.js.map +0 -1
  202. package/src/TransfergratisSdkView.tsx +0 -11
  203. /package/android/build/.transforms/{532c0e65d82f446633d0a7dab2772198 → ab90740579f5bd05b27b4343ada2d1c9}/results.bin +0 -0
  204. /package/android/build/.transforms/{532c0e65d82f446633d0a7dab2772198 → ab90740579f5bd05b27b4343ada2d1c9}/transformed/classes/classes_dex/classes.dex +0 -0
  205. /package/android/build/.transforms/{f62cb96b2d1f78ca96ab35932dd530dc → c9d62bb333688ab562f51958998d5a48}/results.bin +0 -0
  206. /package/android/build/{intermediates/javac/debug/compileDebugJavaWithJavac/classes/kyc/transfergratis/com/BuildConfig.class → tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/BuildConfig.class.uniqueId0} +0 -0
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
- import { View, Text, StyleSheet, Image, ScrollView, Platform, Modal, TouchableOpacity, ActivityIndicator } from 'react-native';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator } 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';
@@ -11,115 +11,65 @@ import { removeDuplicates } from '../../utils/remove-duplicate';
11
11
  import { backVerification, checkTemplateType, frontVerification } from '../../modules/api/CardAuthentification';
12
12
  import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
13
13
  import pathToBase64 from '../../utils/pathToBase64';
14
- import kycService, { truncateFields } from '../../modules/api/KYCService';
15
- import { cropByObb, cropImageWithBBox, cropImageWithBBoxWithTolerance, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../utils/cropByObb';
16
- import { isMobileWeb } from '../../utils/deviceDetection';
17
- import { logger } from '../../utils/logger';
18
-
19
-
20
- interface IIDCardPayload {
21
- dir: string;
22
- file: string;
23
- mrz: string;
24
- templatePath?: string;
25
- }
14
+ import { cropByObb, cropImageWithBBoxWithTolerance, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../utils/cropByObb';
26
15
 
16
+ interface IIDCardPayload { dir: string; file: string; mrz: string; templatePath?: string; }
27
17
  interface IDCardCaptureProps {
28
18
  component: TemplateComponent;
29
19
  value?: Record<string, IIDCardPayload>;
30
20
  onValueChange: (value: Record<string, IIDCardPayload | string>) => void;
31
21
  error?: string;
32
22
  language?: string;
33
- currentSide?: string; // Nouveau prop pour spécifier le côté actuel
23
+ currentSide?: string;
34
24
  }
35
25
 
36
- export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
37
- component,
38
- value = {},
39
- onValueChange,
40
- error,
41
- language = 'en',
42
- }) => {
26
+ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value = {}, onValueChange, error, language = 'en' }) => {
43
27
  const { t, locale } = useI18n();
44
28
  const [showCamera, setShowCamera] = useState(false);
45
29
  const [capturedImages, setCapturedImages] = useState<Record<string, IIDCardPayload>>(value || {});
46
- // const [cropImageUri, setCropImageUri] = useState<string>('');
47
30
  const [currentSide, setCurrentSide] = useState<'front' | 'back'>('front');
48
31
  const [bboxBySide, setBboxBySide] = useState<Record<string, IBbox>>({});
49
32
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
50
- const [showQRModal, setShowQRModal] = useState(false);
33
+
34
+ const [isProcessingCapture, setIsProcessingCapture] = useState(false);
35
+ const [processingImagePath, setProcessingImagePath] = useState<string | null>(null);
51
36
 
52
- // Mapping des types de documents backend vers SDK
53
37
  const documentTypeMapping: Record<string, GovernmentDocumentType> = {
54
- 'nationalId': 'national_id',
55
- 'passport': 'passport',
56
- 'driversLicense': 'drivers_licence',
57
- 'residencePermit': 'permanent_residence',
58
- 'healthInsuranceCard': 'health_insurance_card',
38
+ 'nationalId': 'national_id', 'passport': 'passport', 'driversLicense': 'drivers_licence',
39
+ 'residencePermit': 'permanent_residence', 'healthInsuranceCard': 'health_insurance_card',
59
40
  };
60
- // const [imageNaturalSize, setImageNaturalSize] = useState<{ width: number; height: number } | null>(null);
61
41
 
62
42
  const { actions, state, env } = useTemplateKYCFlowContext();
63
43
 
64
-
65
-
66
44
  const getLocalizedText = (text: LocalizedText | Record<string, LocalizedText>): string => {
67
- // console.log("text", text, JSON.stringify(component, null, 2));
68
- if (text && typeof text[currentSide] === 'object' && text[currentSide][locale]) {
69
- return text[currentSide][locale] || '';
70
- }
45
+ if (text && typeof text[currentSide] === 'object' && text[currentSide][locale]) return text[currentSide][locale] || '';
71
46
  return "";
72
47
  };
73
48
 
74
- // Récupérer les données depuis le composant country_selection
75
49
  const countrySelectionData = useMemo(() => {
76
50
  const countrySelectionComponent = state.template.components.find(c => c.type === 'country_selection');
77
- if (countrySelectionComponent) {
78
- return state.componentData[countrySelectionComponent.id];
79
- }
80
- return null;
51
+ return countrySelectionComponent ? state.componentData[countrySelectionComponent.id] : null;
81
52
  }, [state.template.components, state.componentData]);
82
53
 
83
- // Extraire selectedDocumentType depuis countrySelectionData
84
54
  const selectedDocumentType = useMemo<{ type: GovernmentDocumentType; region: string } | null>(() => {
85
55
  if (!countrySelectionData?.documentType) return null;
86
-
87
56
  const backendDocType = countrySelectionData.documentType;
88
57
  const mappedType = documentTypeMapping[backendDocType] || backendDocType as GovernmentDocumentType;
89
- const region = countrySelectionData.region || 'root';
90
-
91
- return { type: mappedType, region };
58
+ return { type: mappedType, region: countrySelectionData.region || 'root' };
92
59
  }, [countrySelectionData, documentTypeMapping]);
93
60
 
94
- // Récupérer countryData pour compatibilité avec le code existant
95
- const countryData = useMemo(() => {
96
- return countrySelectionData;
97
- }, [countrySelectionData]);
98
-
99
- // console.log();
61
+ const countryData = useMemo(() => countrySelectionData, [countrySelectionData]);
100
62
 
101
- // Synchroniser capturedImages avec value quand les données sont chargées (ex: reprise de session)
102
63
  useEffect(() => {
103
64
  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));
65
+ if (JSON.stringify(value) !== JSON.stringify(capturedImages)) {
109
66
  const updatedImages = value as Record<string, IIDCardPayload>;
110
67
  setCapturedImages(updatedImages);
111
-
112
- // Si on a des images chargées, mettre à jour silentCaptureResult pour l'affichage
113
68
  Object.keys(updatedImages).forEach((side) => {
114
69
  const imageData = updatedImages[side];
115
70
  if (imageData?.dir) {
116
71
  setSilentCaptureResult(prev => ({
117
- ...prev,
118
- path: imageData.dir,
119
- success: true,
120
- isAnalyzing: false,
121
- mrz: imageData.mrz || '',
122
- templatePath: imageData.templatePath || '',
72
+ ...prev, path: imageData.dir, success: true, isAnalyzing: false, mrz: imageData.mrz || '', templatePath: imageData.templatePath || '',
123
73
  }));
124
74
  }
125
75
  });
@@ -127,129 +77,56 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
127
77
  }
128
78
  }, [value]);
129
79
 
130
- useEffect(() => {
131
- logger.log("cropImageUri", JSON.stringify(truncateFields({ box: silentCaptureResult }), null, 2));
132
- if (capturedImages[currentSide]?.dir && silentCaptureResult?.bbox) {
133
- cropImageWithBBox(capturedImages[currentSide].dir, silentCaptureResult.bbox)?.then((uri) => {
134
- // setCropImageUri(uri);
135
- });
136
- }
137
- }, [capturedImages[currentSide]?.dir, silentCaptureResult?.bbox])
138
-
139
-
140
-
141
80
  const cameraConfig = useMemo(() => {
142
81
  const instructions = selectedDocumentType
143
82
  ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
144
83
  : getLocalizedText(component.instructions as Record<string, LocalizedText>);
145
84
 
146
85
  return {
147
- aspectRatio: 4 / 3,
148
- quality: 0.7,
149
- flashMode: 'auto' as const,
150
- cameraType: 'back' as const,
151
- autoFocus: 'on',
152
- whiteBalance: 'auto',
153
- allowRetake: true,
154
- maxRetakes: 3,
155
- overlay: {
156
- showGuide: true,
157
- guideText: instructions,
158
- bbox: {
159
- xMin: 20,
160
- yMin: 140,
161
- xMax: 370,
162
- yMax: 340,
163
- borderColor: '#2DBD60',
164
- borderWidth: 3,
165
- cornerRadius: 8
166
- }
167
- }
86
+ cameraType: 'back' as const, flashMode: 'auto' as const,
87
+ overlay: { guideText: instructions, bbox: { xMin: 15, yMin: 20, xMax: 85, yMax: 70, borderColor: '#2DBD60', borderWidth: 3, cornerRadius: 8 } }
168
88
  };
169
89
  }, [selectedDocumentType, locale, component.instructions]);
170
90
 
171
91
  const retakePicture = (sideToRetake: 'front' | 'back') => {
172
- // 1. Turn the camera back on and hide the stepper
173
- setShowCamera(true);
92
+ // Completely wipe all processing states to prevent leakage
93
+ setIsProcessingCapture(false);
94
+ setProcessingImagePath(null);
95
+ setSilentCaptureResult({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
96
+
97
+ setShowCamera(true);
174
98
  actions.showCustomStepper(false);
175
-
176
- // 2. WIPE LOCAL STATE: Use functional update to prevent stale closures
177
- setCapturedImages((prev) => {
178
- const newState = { ...prev };
179
- delete newState[sideToRetake];
180
- return newState;
181
- });
182
-
183
- setSilentCaptureResult({
184
- path: '',
185
- success: false,
186
- isAnalyzing: false,
187
- error: '',
188
- templatePath: '',
189
- mrz: '',
190
- bbox: undefined,
191
- });
192
-
193
99
 
194
- if (value) {
195
- const newValue = { ...value };
196
- delete newValue[sideToRetake];
197
- onValueChange(newValue);
198
- }
100
+ setCapturedImages((prev) => { const newState = { ...prev }; delete newState[sideToRetake]; return newState; });
101
+ if (value) { const newValue = { ...value }; delete newValue[sideToRetake]; onValueChange(newValue); }
199
102
  };
200
103
 
201
-
202
-
203
104
  const getCurrentSideVerification = (currentSide: string) => {
204
- if (!selectedDocumentType || !countryData?.regionMapping) {
205
- return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
206
- }
207
-
105
+ if (!selectedDocumentType || !countryData?.regionMapping) return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
208
106
  const regionMapping = countryData.regionMapping[selectedDocumentType.type as GovernmentDocumentType];
209
- const authMethod: string[] = [];
210
- const mrzTypes: string[] = [];
211
-
107
+ const authMethod: string[] = []; const mrzTypes: string[] = [];
212
108
  const key = selectedDocumentType.region?.trim()?.length > 0 ? selectedDocumentType.region.trim() : 'root';
213
-
214
109
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
215
110
  regionMapping[key].forEach((item: any) => {
216
111
  if (item[currentSide as keyof typeof item]) {
217
112
  authMethod.push(item[currentSide as keyof typeof item]);
218
- if (item?.mrz_type) {
219
- mrzTypes.push(item?.mrz_type);
220
- }
113
+ if (item?.mrz_type) mrzTypes.push(item?.mrz_type);
221
114
  }
222
115
  });
223
116
  }
224
-
225
- logger.log("regionMapping", JSON.stringify(truncateFields({ regionMapping, selectedDocumentType: selectedDocumentType.region, key }), null, 2));
226
-
227
- return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping: regionMapping, key: key };
117
+ return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping, key };
228
118
  }
229
119
 
230
120
  const getCorrespondingMrzType = (templatePath: string, mapping: any, selectedDocumentType: string = "root") => {
231
121
  if (!mapping || !mapping[selectedDocumentType]) return null;
232
-
233
- // Extraire le nom du fichier depuis le template_path
234
122
  const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
235
-
236
123
  if (!fileName) return null;
237
-
238
- // Normaliser en .py
239
- const pyName = `${fileName}.py`; // ex: cameroon_id_back_3_1.py
240
-
241
- // Chercher dans la liste
124
+ const pyName = `${fileName}.py`;
242
125
  const found = mapping[selectedDocumentType].find((item: any) => item.py_file === pyName);
243
-
244
126
  return found?.mrz_type || null;
245
127
  }
246
128
 
247
- const getCorrespondingAuthMethod = (
248
- templatePath: string,
249
- mapping: any,
250
- selectedDocumentType: string = "root",
251
- side: 'front' | 'back'
252
- ) => {
129
+ const getCorrespondingAuthMethod = (templatePath: string, mapping: any, selectedDocumentType: string = "root", side: 'front' | 'back') => {
253
130
  if (!mapping || !mapping[selectedDocumentType]) return null;
254
131
  const fileName = templatePath.split("/").pop()?.replace(".jpg", "").replace(".png", "");
255
132
  if (!fileName) return null;
@@ -258,10 +135,56 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
258
135
  return found?.[side] || null;
259
136
  }
260
137
 
261
- const handleSilentCapture = async (result: { success: boolean; path?: string; error?: string }) => {
262
- if (silentCaptureResult.isAnalyzing) {
263
- return;
138
+ const autoCapture = async (capturePath: string, verified: ISilentCaptureResult) => {
139
+ if (isProcessingCapture) return;
140
+
141
+ setIsProcessingCapture(true);
142
+ setProcessingImagePath(capturePath);
143
+
144
+ try {
145
+ let imagePathForUpload = capturePath;
146
+ if (verified.bbox) {
147
+ try {
148
+ imagePathForUpload = await cropImageWithBBoxWithTolerance(capturePath, verified.bbox, 0.15);
149
+ } catch {
150
+ imagePathForUpload = capturePath;
151
+ }
152
+ }
153
+
154
+ const base64 = await pathToBase64(imagePathForUpload);
155
+ const newImages = {
156
+ ...capturedImages,
157
+ [currentSide]: { dir: imagePathForUpload, file: base64, mrz: verified.mrz || "", templatePath: verified.templatePath },
158
+ };
159
+
160
+ setCapturedImages(newImages);
161
+ if (verified.country && verified.documentType) {
162
+ onValueChange({
163
+ ...newImages,
164
+ country: verified.country,
165
+ documentType: GovernmentDocumentTypeBackend[verified.documentType as keyof typeof GovernmentDocumentTypeBackend] || '',
166
+ });
167
+ }
168
+
169
+ setTimeout(() => {
170
+ setShowCamera(false);
171
+ actions.showCustomStepper(true);
172
+ setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false }));
173
+ setIsProcessingCapture(false);
174
+ setProcessingImagePath(null);
175
+ }, 600);
176
+
177
+ } catch (e: any) {
178
+ showAlert('Error', e?.message || 'Impossible de capturer la photo');
179
+ setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false }));
180
+ setIsProcessingCapture(false);
181
+ setProcessingImagePath(null);
264
182
  }
183
+ };
184
+
185
+ const handleSilentCapture = async (result: { success: boolean; path?: string; error?: string }) => {
186
+ if (silentCaptureResult.isAnalyzing || isProcessingCapture) return;
187
+
265
188
  if (result.success && result.path) {
266
189
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
267
190
  let templatePath = silentCaptureResult.templatePath || '';
@@ -269,405 +192,161 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
269
192
  let templateResponse: any;
270
193
 
271
194
  if (!selectedDocumentType) {
272
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
273
- return;
195
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' })); return;
274
196
  }
275
197
 
276
- const regionMappings = getCurrentSideVerification(currentSide)
277
- // logger.log("regionMappings", JSON.stringify(truncateFields({regionMappings, templatePath}), null, 2));
278
- if (templatePath.length === 0) {
279
- try {
280
- logger.log("checkTemplateType - BEFORE call", {
281
- selectedDocumentTypeType: selectedDocumentType?.type,
282
- countrySelectionDataDocumentType: countrySelectionData?.documentType,
283
- docTypeToSend: selectedDocumentType?.type
284
- });
198
+ const regionMappings = getCurrentSideVerification(currentSide);
199
+
200
+ try {
201
+ if (templatePath.length === 0) {
285
202
  const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide }, env);
286
203
  templateResponse = templateType;
287
-
288
- if (templateType.template_path) {
289
- templatePath = templateType.template_path;
290
- logger.log("templatePath", templatePath);
291
- setSilentCaptureResult((prev) => ({ ...prev, templatePath: templatePath }));
292
- }
204
+ if (templateType.template_path) templatePath = templateType.template_path;
205
+
293
206
  if (templateType.card_obb) {
294
207
  const obbConfidence = getObbConfidence((templateType as any).card_obb);
295
208
  if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
296
- setSilentCaptureResult((prev) => ({
297
- ...prev,
298
- isAnalyzing: false,
299
- success: false,
300
- error: t('kyc.idCardCapture.cardNotFullyInFrame'),
301
- }));
209
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: t('kyc.idCardCapture.cardNotFullyInFrame') }));
302
210
  return;
303
211
  }
304
- let bbox: IBbox | undefined;
305
212
  try {
306
- const crop = await cropByObb(result?.path || '', (templateType as any).card_obb);
307
- bbox = crop.bbox;
308
- templateBbox = bbox;
213
+ const crop = await cropByObb(result.path, (templateType as any).card_obb);
214
+ templateBbox = crop.bbox;
309
215
  } catch { }
310
216
  }
311
- } catch (e: any) {
312
- logger.log("error checking template type", truncateFields(e));
313
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du template' }));
314
- return; // Return early if checkTemplateType fails
315
- }
316
- }
317
- logger.log("templatePath before verification", templatePath, "currentSide", currentSide);
318
- if (currentSide === 'front') {
319
- logger.log("frontVerification - BEFORE call", {
320
- selectedDocumentTypeType: selectedDocumentType?.type,
321
- countrySelectionDataDocumentType: countrySelectionData?.documentType,
322
- docTypeToSend: selectedDocumentType?.type
323
- });
324
- logger.log("Calling frontVerification", { templatePath, selectedDocumentType: selectedDocumentType?.type, regionMappings });
325
- console.log("About to call frontVerification", typeof frontVerification);
326
- try {
327
- const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
328
- const resolvedMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '');
329
- const mrzType = resolvedMrzType || '';
330
- const authMethod = matchedAuthMethod
331
- ? regionMappings.authMethod.filter((method) => method === matchedAuthMethod)
332
- : regionMappings.authMethod.filter((method) => method !== 'MRZ' && method !== '2D_barcode');
333
- console.log("mrzType calculated", mrzType);
334
- const verificationParams = {
335
- path: result.path,
336
- regionMapping: {
337
- authMethod,
338
- mrzTypes: regionMappings.mrzTypes,
339
- },
340
- selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type as keyof typeof GovernmentDocumentTypeShorted] || '',
341
- code: countryData?.code || '',
342
- currentSide: currentSide,
343
- templatePath: templatePath,
344
- mrzType: mrzType,
345
- };
346
- console.log("frontVerification params", verificationParams);
347
- console.log("About to call frontVerification function");
348
-
349
- const promise = frontVerification(verificationParams, env);
350
- console.log("frontVerification promise created", promise);
351
-
352
- promise.then((res: any) => {
353
- logger.log("front verification result", truncateFields(res));
354
- const bbox = res?.bbox || templateBbox;
355
-
356
- setSilentCaptureResult((prev) => ({
357
- ...prev,
358
- path: result.path,
359
- templatePath: templatePath,
360
- bbox: bbox,
361
- success: true,
362
- mrz: res?.mrz ? JSON.stringify(res.mrz) : "",
363
- isAnalyzing: false,
364
- country: countryData?.code,
365
- documentType: selectedDocumentType.type,
366
- }));
367
-
368
- // Stocker le bbox pour ce côté
369
- if (bbox && typeof bbox === 'object') {
370
- setBboxBySide((prev) => ({ ...prev, [currentSide]: bbox }));
371
- }
372
-
373
- }).catch((e: any) => {
374
-
375
- }).catch((e: any) => {
376
- console.log("error front verification", e);
377
- logger.log("error front verification", truncateFields(e));
378
- const isCardNotFullyInFrame =
379
- e?.message === 'CARD_NOT_FULLY_IN_FRAME' ||
380
- e?.message?.includes('entirement') ||
381
- e?.message?.includes('fully in frame');
382
- const errorMessage = isCardNotFullyInFrame
383
- ? t('kyc.idCardCapture.cardNotFullyInFrame')
384
- : (e?.message || 'Erreur de détection du MRZ');
385
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: errorMessage }));
386
- });
387
- } catch (error: any) {
388
- console.log("Error setting up frontVerification call", error);
389
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: error?.message || 'Erreur lors de la configuration de la vérification' }));
390
217
  }
391
- } else {
392
- const backRegionMappings = getCurrentSideVerification(currentSide);
393
- logger.log("Calling backVerification", { templatePath, selectedDocumentType: selectedDocumentType.type, backRegionMappings });
394
- const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, backRegionMappings.regionMapping, backRegionMappings.key || '', 'back');
395
- const backMrzType = getCorrespondingMrzType(templatePath, backRegionMappings.regionMapping, backRegionMappings.key || '') || '';
396
-
397
- backVerification({
398
- path: result.path,
399
- regionMapping: {
400
- authMethod: matchedBackAuthMethod
401
- ? backRegionMappings.authMethod.filter((method) => method === matchedBackAuthMethod)
402
- : backRegionMappings.authMethod.filter((method) => method !== 'MRZ' && method !== '2D_barcode'),
403
- mrzTypes: backRegionMappings.mrzTypes,
404
- },
405
- selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
406
- code: countryData?.code || '',
407
- currentSide: currentSide,
408
- templatePath: templatePath,
409
- mrzType: backMrzType,
410
- templateResponse,
411
- }, env).then((res: any) => {
412
- logger.log("back verification result", truncateFields(res));
413
- const bbox = res?.bbox || templateBbox;
414
-
415
- setSilentCaptureResult((prev) => ({
416
- ...prev,
417
- path: result.path,
418
- bbox: bbox,
419
- success: true,
420
- mrz: res?.mrz ? JSON.stringify(res.mrz) : "",
421
- isAnalyzing: false,
422
- country: countryData?.code,
423
- documentType: selectedDocumentType.type,
424
- }));
425
-
426
- // Stocker le bbox pour ce côté
427
- if (bbox && typeof bbox === 'object') {
428
- setBboxBySide((prev) => ({ ...prev, [currentSide]: bbox }));
429
- }
430
- }).catch((e: any) => {
431
- logger.log("error back verification", truncateFields(e));
432
- const isCardNotFullyInFrame =
433
- e?.message === 'CARD_NOT_FULLY_IN_FRAME' ||
434
- e?.message?.includes('entirement') ||
435
- e?.message?.includes('fully in frame');
436
- const errorMessage = isCardNotFullyInFrame
437
- ? t('kyc.idCardCapture.cardNotFullyInFrame')
438
- : (e?.message || 'Erreur de détection du MRZ');
439
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: errorMessage }));
440
- });
441
- }
442
218
 
443
- }
444
- }
445
-
446
- const handleCapture = async (result: { success: boolean; path?: string; error?: string }) => {
447
- if (silentCaptureResult.path) {
448
- let imagePathForUpload = silentCaptureResult.path;
449
- if (silentCaptureResult.bbox) {
450
- try {
451
- imagePathForUpload = await cropImageWithBBoxWithTolerance(silentCaptureResult.path, silentCaptureResult.bbox, 0.05);
452
- } catch (error) {
453
- imagePathForUpload = silentCaptureResult.path;
219
+ let verificationRes: any;
220
+ if (currentSide === 'front') {
221
+ const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
222
+ const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
223
+ 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
+ } 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);
454
228
  }
455
- }
456
229
 
457
- const base64 = await pathToBase64(imagePathForUpload);
230
+ const bbox = verificationRes?.bbox || templateBbox;
231
+ const mrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
232
+
233
+ const verifiedResult: ISilentCaptureResult = { path: result.path, templatePath, bbox, success: true, mrz, isAnalyzing: false, country: countryData?.code, documentType: selectedDocumentType.type };
234
+ setSilentCaptureResult(verifiedResult);
235
+ if (bbox) setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
236
+
237
+ await autoCapture(result.path, verifiedResult);
458
238
 
459
- // FIX: Use 'imagePathForUpload' (the cropped image) for the local display directory!
460
- const newImages = {
461
- ...capturedImages,
462
- [currentSide]: {
463
- dir: imagePathForUpload, // <--- CHANGED THIS LINE
464
- file: base64,
465
- mrz: silentCaptureResult.mrz || "",
466
- templatePath: silentCaptureResult.templatePath
467
- }
468
- };
469
-
470
- setCapturedImages(newImages);
471
-
472
- if (silentCaptureResult.country && silentCaptureResult.documentType) {
473
- onValueChange({
474
- ...newImages,
475
- country: silentCaptureResult.country,
476
- documentType: GovernmentDocumentTypeBackend[silentCaptureResult.documentType as keyof typeof GovernmentDocumentTypeBackend] || '',
477
- });
239
+ } catch (error: any) {
240
+ const isCardNotFullyInFrame = error?.message === 'CARD_NOT_FULLY_IN_FRAME' || error?.message?.includes('entirement');
241
+ const errorMessage = isCardNotFullyInFrame ? t('kyc.idCardCapture.cardNotFullyInFrame') : (error?.message || 'Erreur de détection');
242
+ setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: errorMessage }));
478
243
  }
479
- setShowCamera(false);
480
- actions.showCustomStepper(true);
481
- } else {
482
- showAlert('Erreur', result.error || 'Impossible de prendre la photo');
483
244
  }
484
245
  };
485
246
 
486
247
  const handleError = (event: { message: string }) => {
487
- showAlert('Erreur', event.message);
488
- setShowCamera(false);
489
- };
490
-
491
- useEffect(() => {
492
- actions.showCustomStepper(!showCamera);
493
- }, [showCamera]);
494
-
495
- // Cross-device polling logic
496
- useEffect(() => {
497
- if (!showQRModal || !state.session.session_id) return;
498
-
499
- const pollInterval = setInterval(async () => {
500
- try {
501
- const result = await kycService.getVerificationResult(state.session.session_id);
502
- const sessionData = result[state.session.session_id]?.data;
503
-
504
- if (sessionData) {
505
- // Check if verification is completed or if we have ID card data
506
- // Since the requirement is "verification restarts", we might look for overall completion
507
- // or we could check if user_data is populated.
508
- // For now, let's assume if status is not PENDING/INITIALIZED it might be done.
509
- // Or simplier: if the mobile flow completes, the session status updates.
510
- logger.log('Polling result:', truncateFields(sessionData));
511
-
512
- if (sessionData.verification_status === 'completed' || sessionData.verification_status === 'approved' || sessionData.verification_status === 'review') {
513
- clearInterval(pollInterval);
514
- setShowQRModal(false);
515
- actions.submitVerification(); // Or handleComplete
516
- }
517
- }
518
- } catch (error) {
519
- console.error('Polling error:', error);
520
- }
521
- }, 5000);
522
-
523
- return () => clearInterval(pollInterval);
524
- }, [showQRModal, state.session.session_id, actions]);
525
-
526
- const getQrCodeUrl = (): string => {
527
- // Only available on web platform
528
- if (Platform.OS !== 'web') return '';
529
- if (typeof window === 'undefined' || !window.location || !window.location.href) return '';
530
-
531
- try {
532
- const currentUrl = new URL(window.location.href);
533
- if (!currentUrl.searchParams.has('kyc_id') && state.session.session_id) {
534
- currentUrl.searchParams.set('kyc_id', state.session.session_id);
535
- }
536
- currentUrl.searchParams.set('component_index', String(state.currentComponentIndex));
537
- if (countrySelectionData?.code) {
538
- currentUrl.searchParams.set('country', countrySelectionData.code);
539
- if (countrySelectionData.documentType) currentUrl.searchParams.set('document_type', countrySelectionData.documentType);
540
- if (countrySelectionData.region) currentUrl.searchParams.set('region', countrySelectionData.region);
541
- }
542
- return `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(currentUrl.toString())}`;
543
- } catch (error) {
544
- console.warn('Error generating QR code URL:', error);
545
- return '';
546
- }
248
+ showAlert('Erreur', event.message); setShowCamera(false); setIsProcessingCapture(false);
547
249
  };
548
250
 
251
+ useEffect(() => { actions.showCustomStepper(!showCamera); }, [showCamera]);
549
252
 
550
-
551
-
552
-
553
-
554
-
555
- // En reprise sur un autre appareil: afficher un chargement tant que les données de session ne sont pas restaurées
556
- const isResumingSession = Boolean(state.session.session_id && state.currentComponentIndex > 0);
557
- const sessionDataRestored = state.session.sessionDataRestored !== false;
558
- if (isResumingSession && !sessionDataRestored && (!countrySelectionData || !selectedDocumentType)) {
559
- return (
560
- <View style={styles.root}>
561
- <View style={[styles.container, { justifyContent: 'center', alignItems: 'center' }]}>
562
- <ActivityIndicator size="large" color="#2DBD60" />
563
- <Text style={[styles.description, { marginTop: 16 }]}>
564
- {state.currentLanguage === 'en' ? 'Loading your session...' : 'Chargement de votre session...'}
565
- </Text>
566
- </View>
567
- </View>
568
- );
569
- }
570
-
571
- // Vérifier si les données sont disponibles, sinon afficher un message d'erreur
572
253
  if (!countrySelectionData || !selectedDocumentType) {
573
254
  return (
574
255
  <View style={styles.root}>
575
256
  <View style={styles.container}>
576
257
  <Text style={styles.title}>{getLocalizedText(component.labels)}</Text>
577
258
  <Text style={styles.description}>
578
- {state.currentLanguage === "en"
579
- ? "Please complete the country and document selection first."
580
- : "Veuillez d'abord compléter la sélection du pays et du document."}
259
+ {state.currentLanguage === "en" ? "Please complete the country and document selection first." : "Veuillez d'abord compléter la sélection du pays et du document."}
581
260
  </Text>
582
- {error && (
583
- <Text style={styles.errorText}>{error}</Text>
584
- )}
585
261
  </View>
586
262
  </View>
587
263
  );
588
264
  }
589
265
 
590
-
591
-
266
+ // --- CAMERA RENDER ---
592
267
  if (showCamera) {
268
+ const isBusy = isProcessingCapture;
269
+
593
270
  return (
594
271
  <View style={styles.cameraContainer}>
595
272
  <EnhancedCameraView
273
+ key={currentSide} // 🚨 BUG FIX: Forces the camera instance to completely reset when switching sides
596
274
  showCamera={true}
275
+ isProcessing={isBusy}
597
276
  cameraType={cameraConfig.cameraType}
598
277
  style={styles.camera}
599
- onCapture={handleCapture}
600
278
  onError={handleError}
601
- onClose={() => setShowCamera(false)}
602
- quality="high"
603
- showCaptureButton={true}
604
- showSwitchCamera={true}
605
279
  onSilentCapture={handleSilentCapture}
606
280
  silentCaptureResult={silentCaptureResult}
607
- captureStabilizationDelayMs={3000}
608
- enableFlash={cameraConfig.flashMode === 'auto' || cameraConfig.flashMode === 'on'}
609
281
  overlayComponent={
610
- <IdCardOverlay
611
- xMin={cameraConfig.overlay.bbox.xMin}
612
- yMin={cameraConfig.overlay.bbox.yMin}
613
- xMax={cameraConfig.overlay.bbox.xMax}
614
- yMax={cameraConfig.overlay.bbox.yMax}
615
- instructions={cameraConfig.overlay.guideText}
616
- cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0 as number}
617
- isSuccess={silentCaptureResult.success}
618
- language={state.currentLanguage}
619
- stepperProps={{
620
- back: () => {
621
- if (currentSide === 'back') {
622
- setCurrentSide('front');
623
- setShowCamera(false);
624
- // Si une image front existe, on la restaure pour l'afficher
625
- if (capturedImages['front']?.dir) {
626
- // Restaurer l'état de capture du front pour afficher l'image
627
- const frontImage = capturedImages['front'];
628
- const frontBbox = bboxBySide['front'];
629
- setSilentCaptureResult((prev) => ({
630
- path: frontImage.dir,
631
- success: true,
632
- isAnalyzing: false,
633
- error: '',
634
- mrz: frontImage.mrz || '',
635
- templatePath: frontImage.templatePath || '',
636
- country: silentCaptureResult.country,
637
- documentType: silentCaptureResult.documentType,
638
- bbox: frontBbox // Restaurer le bbox pour recalculer le cropImageUri
639
- }));
640
- } else {
641
- // Pas d'image front, on réinitialise
642
- setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '' }));
643
- }
644
- } else {
645
- // Retour au composant précédent (country_selection)
646
- actions.previousComponent();
647
- }
648
- },
649
- selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
650
- step: state.currentComponentIndex + 1,
651
- totalSteps: state.template.components.length,
652
- side: currentSide,
653
- }}
654
- />
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
+ )}
293
+
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
+ />
302
+ )}
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
+ </>
655
338
  }
656
339
  />
657
340
 
658
- {silentCaptureResult.error ? (
341
+ {silentCaptureResult.error && !isBusy ? (
659
342
  <View style={styles.floatingErrorBanner}>
660
- <Text style={styles.floatingErrorText}>
661
- {silentCaptureResult.error}
662
- </Text>
343
+ <Text style={styles.floatingErrorText}>{silentCaptureResult.error}</Text>
663
344
  </View>
664
345
  ) : null}
665
-
666
346
  </View>
667
347
  );
668
348
  }
669
349
 
670
-
671
350
  return (
672
351
  <View style={styles.root}>
673
352
  <View style={styles.previewContainer}>
@@ -679,401 +358,82 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
679
358
  <Text style={{ fontSize: 14, color: '#666', textAlign: 'center', marginBottom: 16, lineHeight: 22 }}>
680
359
  {getLocalizedText(component.instructions)}
681
360
  </Text>
682
-
683
361
  <View style={{ alignItems: 'center', justifyContent: 'center', flexDirection: "column", gap: 16 }}>
362
+
684
363
  {silentCaptureResult?.error === 'TOO_FAR_AWAY' && (
685
- <View style={{
686
- backgroundColor: '#FF9500',
687
- padding: 12,
688
- borderRadius: 8,
689
- width: '100%',
690
- shadowColor: '#000',
691
- shadowOffset: { width: 0, height: 2 },
692
- shadowOpacity: 0.2,
693
- shadowRadius: 4,
694
- elevation: 4
695
- }}>
696
- <Text style={{ color: 'white', fontWeight: 'bold', textAlign: 'center', fontSize: 16 }}>
697
- {state.currentLanguage === "en"
698
- ? "Move the document closer to the camera."
699
- : "Veuillez rapprocher le document de la caméra."}
700
- </Text>
701
- </View>
702
- )}
364
+ <View style={styles.warningBanner}>
365
+ <Text style={styles.warningText}>
366
+ {state.currentLanguage === "en" ? "Move the document closer to the camera and place it on a flat surface." : "Veuillez rapprocher le document de la caméra et le poser à plat."}
367
+ </Text>
368
+ </View>
369
+ )}
703
370
 
704
- <View
705
- style={{
706
- width: '100%',
707
- height: 200,
708
- borderRadius: 12,
709
- padding: 1,
710
- overflow: 'hidden',
711
- shadowColor: '#000',
712
- shadowOffset: { width: 0, height: 4 },
713
- shadowOpacity: 0.18,
714
- shadowRadius: 8,
715
- elevation: 8,
716
- backgroundColor: '#000',
717
- }}
718
- >
371
+ <View style={styles.imagePreviewWrapper}>
719
372
  {capturedImages[currentSide]?.dir ? (
720
- <Image
721
- source={{ uri: capturedImages[currentSide].dir }}
722
- style={{
723
- width: '100%',
724
- height: '100%',
725
- borderRadius: 12,
726
- resizeMode: 'contain',
727
- }}
728
- />
373
+ <Image source={{ uri: capturedImages[currentSide].dir }} style={styles.previewImage} />
729
374
  ) : silentCaptureResult.path ? (
730
- <Image
731
- source={{ uri: silentCaptureResult.path }}
732
- style={{
733
- width: '100%',
734
- height: '100%',
735
- borderRadius: 12,
736
- resizeMode: 'contain',
737
- }}
738
- />
375
+ <Image source={{ uri: silentCaptureResult.path }} style={styles.previewImage} />
739
376
  ) : null}
740
377
  </View>
741
- {/* Capture button si aucune image n'a été capturée */}
378
+
742
379
  {!capturedImages[currentSide]?.dir && (
743
380
  <Button
744
- title={state.currentLanguage === "en" ? "Take Photo" : "Prendre une photo"}
745
- onPress={() => {
746
- setShowCamera(true);
747
- actions.showCustomStepper(false);
748
- }}
749
- variant="primary"
750
- size="large"
751
- fullWidth
381
+ title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"}
382
+ onPress={() => { setShowCamera(true); actions.showCustomStepper(false); }}
383
+ variant="primary" size="large" fullWidth
752
384
  />
753
385
  )}
754
- {/* retake button si une image a été capturée */}
386
+
755
387
  {capturedImages[currentSide]?.dir && (
756
388
  <>
757
- <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => {
758
- retakePicture(currentSide);
759
- }}
760
- variant="outline"
761
- size="medium"
762
- fullWidth
763
- />
389
+ <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => retakePicture(currentSide)} variant="outline" size="medium" fullWidth />
764
390
  <Button title={t('common.next')} onPress={() => {
765
- if (!selectedDocumentType) {
766
- showAlert('Error', 'Document type not selected');
767
- return;
391
+ 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);
768
402
  }
769
- if (currentSide === 'back' || selectedDocumentType.type === 'passport') {
770
- actions.nextComponent();
771
- return;
772
- } else {
773
- setShowCamera(true);
774
- setCurrentSide('back');
775
- setSilentCaptureResult((prev) => ({ path: '', success: false, isAnalyzing: false, error: '', mrz: '', templatePath: '' }));
776
- // setCropImageUri('');
777
- }
778
- }}
779
- variant="primary"
780
- size="large"
781
- fullWidth
782
- />
403
+ }} variant="primary" size="large" fullWidth />
783
404
  </>
784
405
  )}
785
406
  </View>
786
407
  </View>
787
408
  </ScrollView>
788
-
789
-
790
-
791
- {error && (
792
- <Text style={styles.errorText}>{error}</Text>
793
- )}
794
-
795
- {/* Cross-Device / Continue on Phone Button (Web Only) */}
796
- {Platform.OS === 'web' && !isMobileWeb() && !capturedImages[currentSide]?.dir && (
797
- <Button title={t('kyc.idCardCapture.continueOnPhone')} onPress={() => { setShowQRModal(true) }} />
798
- )}
799
-
800
- {/* QR Code Modal - Web Only */}
801
- {Platform.OS === 'web' && (
802
- <Modal
803
- visible={showQRModal}
804
- transparent={true}
805
- animationType="fade"
806
- onRequestClose={() => setShowQRModal(false)}
807
- >
808
- <View style={styles.modalOverlay}>
809
- <View style={styles.modalContent}>
810
- <Text style={styles.modalTitle}>
811
- {t('kyc.idCardCapture.continueOnMobile')}
812
- </Text>
813
- <Text style={styles.modalDescription}>
814
- {t('kyc.idCardCapture.scanQrCode')}
815
- </Text>
816
-
817
- {showQRModal && getQrCodeUrl() ? (
818
- <Image
819
- source={{ uri: getQrCodeUrl() }}
820
- style={styles.qrCodeImage}
821
- />
822
- ) : null}
823
-
824
- <TouchableOpacity
825
- style={styles.closeButton}
826
- onPress={() => setShowQRModal(false)}
827
- >
828
- <Text style={styles.closeButtonText}>
829
- {t('common.close')}
830
- </Text>
831
- </TouchableOpacity>
832
- </View>
833
- </View>
834
- </Modal>
835
- )}
836
-
837
409
  </View>
838
410
  </View>
839
411
  );
840
412
  };
841
413
 
842
414
  const styles = StyleSheet.create({
843
- root: {
844
- flex: 1,
845
- maxWidth: 760,
846
- width: '100%',
847
- },
848
- container: {
849
- backgroundColor: 'white',
850
- margin: 10,
851
- borderRadius: 10,
852
- paddingVertical: 16,
853
- paddingHorizontal: 16,
854
- // shadow
855
- shadowColor: '#000',
856
- shadowOffset: { width: 0, height: 2 },
857
- shadowOpacity: 0.35,
858
- shadowRadius: 4.84,
859
- elevation: 10,
860
- // padding: 16,
861
- },
862
- cameraContainer: {
863
- flex: 1,
864
- width: '100%',
865
- height: '100%',
866
- },
867
- previewContainer: {
868
- width: '95%',
869
- backgroundColor: 'white',
870
- margin: 10,
871
- borderRadius: 10,
872
- paddingVertical: 16,
873
- paddingHorizontal: 16,
874
- },
875
- previewItemContainer: {
876
-
877
- // marginBottom: 24,
878
- },
879
- title: {
880
- fontSize: 24,
881
- fontWeight: 'bold',
882
- color: '#333',
883
- marginBottom: 8,
884
- textAlign: 'center',
885
- },
886
- subtitle: {
887
- fontSize: 18,
888
- color: '#666',
889
- textAlign: 'center',
890
- marginBottom: 16,
891
- },
892
- sideInfo: {
893
- fontSize: 14,
894
- color: '#888',
895
- textAlign: 'center',
896
- marginBottom: 16,
897
- fontStyle: 'italic',
898
- },
899
- description: {
900
- fontSize: 16,
901
- color: '#666',
902
- textAlign: 'center',
903
- marginBottom: 24,
904
- lineHeight: 22,
905
- },
906
- sidesContainer: {
907
- flex: 1,
908
- },
909
- sideContainer: {
910
- marginBottom: 24,
911
- },
912
- sideTitle: {
913
- fontSize: 25,
914
- fontWeight: 'bold',
915
- color: '#000',
916
- marginBottom: 12,
917
- textAlign: 'center',
918
- },
919
- captureButton: {
920
- borderWidth: 2,
921
- borderColor: '#2DBD60',
922
- borderStyle: 'dashed',
923
- borderRadius: 12,
924
- padding: 32,
925
- alignItems: 'center',
926
- backgroundColor: '#f0f9f0',
927
- },
928
- captureIcon: {
929
- fontSize: 48,
930
- marginBottom: 16,
931
- },
932
- captureText: {
933
- fontSize: 16,
934
- color: '#2DBD60',
935
- fontWeight: '600',
936
- },
937
- capturedImageContainer: {
938
- position: 'relative',
939
- },
940
- capturedImage: {
941
- width: '100%',
942
- height: 200,
943
- borderRadius: 12,
944
- resizeMode: 'contain',
945
- backgroundColor: '#f0f0f0',
946
- },
947
- retakeButton: {
948
- position: 'absolute',
949
- top: 8,
950
- right: 8,
951
- backgroundColor: 'rgba(0, 0, 0, 0.7)',
952
- paddingHorizontal: 12,
953
- paddingVertical: 6,
954
- borderRadius: 6,
955
- },
956
- retakeButtonText: {
957
- color: 'white',
958
- fontSize: 12,
959
- fontWeight: '600',
960
- },
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' },
961
436
  camera: {
962
437
  flex: 1,
963
438
  },
964
- completionContainer: {
965
- marginTop: 24,
966
- alignItems: 'center',
967
- },
968
- completionText: {
969
- fontSize: 16,
970
- color: '#2DBD60',
971
- fontWeight: '600',
972
- marginBottom: 16,
973
- },
974
- primaryButton: {
975
- paddingHorizontal: 32,
976
- paddingVertical: 16,
977
- borderRadius: 8,
978
- // minWidth: 120,
979
- alignItems: 'center',
980
- backgroundColor: '#2DBD60',
981
- },
982
- primaryButtonText: {
983
- color: 'white',
984
- fontWeight: '600',
985
- fontSize: 16,
986
- },
987
- errorText: {
988
- color: '#dc2626',
989
- fontSize: 14,
990
- marginTop: 8,
991
- textAlign: 'center',
992
- },
993
- crossDeviceButton: {
994
- marginTop: 16,
995
- padding: 12,
996
- alignItems: 'center',
997
- borderWidth: 1,
998
- borderColor: '#2DBD60',
999
- borderRadius: 8,
1000
- backgroundColor: '#f0f9f0',
1001
- },
1002
- crossDeviceText: {
1003
- color: '#2DBD60',
1004
- fontSize: 16,
1005
- fontWeight: '600',
1006
- },
1007
- modalOverlay: {
1008
- flex: 1,
1009
- backgroundColor: 'rgba(0,0,0,0.5)',
1010
- justifyContent: 'center',
1011
- alignItems: 'center',
1012
- },
1013
- modalContent: {
1014
- backgroundColor: 'white',
1015
- borderRadius: 16,
1016
- padding: 24,
1017
- alignItems: 'center',
1018
- width: '90%',
1019
- maxWidth: 340,
1020
- shadowColor: '#000',
1021
- shadowOffset: { width: 0, height: 2 },
1022
- shadowOpacity: 0.25,
1023
- shadowRadius: 4,
1024
- elevation: 5,
1025
- },
1026
- modalTitle: {
1027
- fontSize: 20,
1028
- fontWeight: 'bold',
1029
- marginBottom: 12,
1030
- color: '#333',
1031
- },
1032
- modalDescription: {
1033
- fontSize: 16,
1034
- color: '#666',
1035
- textAlign: 'center',
1036
- marginBottom: 20,
1037
- lineHeight: 22,
1038
- },
1039
- qrCodeImage: {
1040
- width: 200,
1041
- height: 200,
1042
- marginBottom: 20,
1043
- },
1044
- closeButton: {
1045
- paddingVertical: 10,
1046
- paddingHorizontal: 20,
1047
- backgroundColor: '#f5f5f5',
1048
- borderRadius: 8,
1049
- },
1050
- closeButtonText: {
1051
- fontSize: 16,
1052
- color: '#666',
1053
- fontWeight: '600',
1054
- },
1055
- floatingErrorBanner: {
1056
- position: 'absolute',
1057
- top: 60, // Pushes it down slightly from the very top of the screen
1058
- left: '10%',
1059
- right: '10%',
1060
- backgroundColor: 'rgba(220, 38, 38, 0.95)', // Bright red
1061
- paddingVertical: 12,
1062
- paddingHorizontal: 16,
1063
- borderRadius: 8,
1064
- alignItems: 'center',
1065
- justifyContent: 'center',
1066
- shadowColor: '#000',
1067
- shadowOffset: { width: 0, height: 4 },
1068
- shadowOpacity: 0.3,
1069
- shadowRadius: 5,
1070
- elevation: 8,
1071
- zIndex: 100,
1072
- },
1073
- floatingErrorText: {
1074
- color: 'white',
1075
- fontSize: 14,
1076
- fontWeight: '700',
1077
- textAlign: 'center',
1078
- },
1079
- });
439
+ });