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

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 +107 -330
  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 +114 -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 +166 -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 +282 -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 +12 -1
  150. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  151. package/build/src/modules/api/KYCService.js +102 -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 +131 -385
  167. package/src/components/KYCElements/EmailVerificationTemplate.tsx +141 -26
  168. package/src/components/KYCElements/IDCardCapture.tsx +228 -868
  169. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +342 -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 +169 -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,662 +1,330 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import { View, Text, StyleSheet, ScrollView, Image, TouchableOpacity } from 'react-native';
3
- import { showAlert } from '../../utils/platformAlert';
4
- import { TemplateComponent, LocalizedText, ISilentCaptureResult, OrientationType, GovernmentDocumentType } from '../../types/KYC.types';
5
- import { EnhancedCameraView } from '../EnhancedCameraView';
6
- import { Button } from '../ui/Button';
7
- import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
8
- import { useI18n } from '../../hooks/useI18n';
9
- import SelfieOverlay from '../OverLay/SelfieOverlay';
10
- import selfieVerification from '../../modules/api/SelfieVerification';
11
- import pathToBase64 from '../../utils/pathToBase64';
12
- import { truncateFields } from '../../modules/api/KYCService';
13
-
14
- interface IImagePayload {
15
- dir: string;
16
- file: string;
17
- }
18
- interface SelfieCaptureTemplateProps {
19
- component: TemplateComponent;
20
- value: Record<string, IImagePayload> | undefined;
21
- onValueChange: (value: Record<string, IImagePayload | string>) => void;
22
- error?: string;
23
- language: string;
24
- }
25
-
26
- export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
27
- component,
28
- value,
29
- onValueChange,
30
- error,
31
- language,
32
- }) => {
33
- const { t } = useI18n();
34
- // const config = component.config as SelfieConfig;
35
- const orientations: OrientationType[] = (['center','left','right']) as OrientationType[];
36
- const { actions, state, env } = useTemplateKYCFlowContext();
37
-
38
- const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
39
-
40
- const [showCamera, setShowCamera] = useState(false);
41
- const [showPreview, setShowPreview] = useState(false);
42
- const [previewImagePath, setPreviewImagePath] = useState<string | null>(null);
43
- const [currentOrientation, setCurrentOrientation] = useState<OrientationType>(orientations[0]);
44
- const [capturedImages, setCapturedImages] = useState<Record<string, IImagePayload>>(value || {});
45
-
46
- const getLocalizedText = (text: LocalizedText | Record<string, LocalizedText>): string => {
47
- if (!text) return '';
48
- // If text is a nested record (e.g., per theme/device), try to pick a default then localize
49
- const maybeNested = text as Record<string, LocalizedText>;
50
- if (maybeNested && typeof maybeNested === 'object' && 'en' in maybeNested === false && 'fr' in maybeNested === false) {
51
- const firstKey = Object.keys(maybeNested)[0];
52
- const nested = maybeNested[firstKey] as LocalizedText;
53
- return nested?.[language] || nested?.en || '';
54
- }
55
- const loc = text as LocalizedText;
56
- return loc[language] || loc.en || '';
57
- };
58
-
59
- const getOrientationInstructions = (orientation: OrientationType): string => {
60
- switch (orientation) {
61
- case 'center':
62
- return state.currentLanguage === "en" ? "Take a selfie of face, look straight ahead you" : "Prenez un selfie de face, regardez droit devant vous";
63
- case 'left':
64
- return state.currentLanguage === "en" ? "Turn your head to the left, keep your shoulders straight" : "Tournez votre tête vers la gauche, gardez les épaules droites";
65
- case 'right':
66
- return state.currentLanguage === "en" ? "Turn your head to the right, keep your shoulders straight" : "Tournez votre tête vers la droite, gardez les épaules droites";
67
- default:
68
- return getLocalizedText(component.instructions as LocalizedText);
69
- }
70
- };
71
-
72
- useEffect(() => {
73
- actions.showCustomStepper(!showCamera);
74
- }, [showCamera]);
75
-
76
- const getOrientationLabel = (orientation: OrientationType): string => {
77
- switch (orientation) {
78
- case 'center':
79
- return state.currentLanguage === "en" ? "Front Profile Selfie" : "Selfie de face";
80
- case 'left':
81
- return state.currentLanguage === "en" ? "Left Profile Selfie" : "Selfie profil gauche";
82
- case 'right':
83
- return state.currentLanguage === "en" ? "Right Profile Selfie" : "Selfie profil droit";
84
- default:
85
- return getLocalizedText(component.labels as LocalizedText);
86
- }
87
- };
88
- const getOrientationOpposite = (orientation: OrientationType): OrientationType => {
89
- switch (orientation) {
90
- case 'center':
91
- return 'center';
92
- case 'left':
93
- return 'left';
94
- case 'right':
95
- return 'right';
96
- default:
97
- return orientation;
98
- }
99
- }
100
- // const countryData = useMemo(() => {
101
- // const geCountryID = Object.keys(state.componentData).find((c: string) => c === "6");
102
-
103
- // if (geCountryID) {
104
- // const countryMapping = state.componentData[geCountryID];
105
- // return countryMapping?.code;
106
- // }
107
- // return null;
108
- // }, [state.componentData]);
109
-
110
- const getInstructions = (orientation: OrientationType): { title: string, subtitle: string } => {
111
- const lang = state.currentLanguage;
112
- switch (orientation) {
113
- case 'center':
114
- return {
115
- title:
116
-
117
- lang === "en" ? 'Center your face' : 'Mettez votre face au centre',
118
- subtitle: lang === "en"
119
- ? 'Align your face to the center of the selfie area and then take a photo'
120
- : 'Alignez votre visage au centre de la zone selfie puis prenez une photo'
121
- };
122
- case 'left':
123
- return {
124
- title:
125
- lang === 'en'
126
- ? 'Turn your head left'
127
- : 'Tournez la tête vers la gauche',
128
- subtitle:
129
- lang === 'en'
130
- ? 'Slowly turn your head to the left while keeping your face in the selfie area'
131
- : 'Tournez lentement la tête vers la gauche en gardant votre visage dans la zone selfie'
132
- };
133
- case 'right':
134
- return {
135
- title:
136
- lang === 'en'
137
- ? 'Turn your head right'
138
- : 'Tournez la tête vers la droite',
139
- subtitle:
140
- lang === 'en'
141
- ? 'Slowly turn your head to the right while keeping your face in the selfie area'
142
- : 'Tournez lentement la tête vers la droite en gardant votre visage dans la zone selfie'
143
- };
144
- default:
145
- return { title: getLocalizedText(component.labels as LocalizedText), subtitle: getLocalizedText(component.instructions as LocalizedText) };
146
- }
147
- };
148
-
149
- const handleSilentCapture = (result: { success: boolean; path?: string; error?: string }) => {
150
- if (silentCaptureResult.isAnalyzing) {
151
- return;
152
- }
153
- if (result.success && result.path) {
154
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
155
- selfieVerification(result.path, env).then((response) => {
156
- if (response.length > 0) {
157
- const res = response[0];
158
- // In SANDBOX mode, always accept the result regardless of orientation
159
- if (env === 'SANDBOX' && res?.capture) {
160
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: true, error: '', path: result.path }));
161
- } else if (res?.orientation_direction === getOrientationOpposite(currentOrientation) && res?.capture) {
162
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: true, error: '', path: result.path }));
163
- } else {
164
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Le selfie n\'est pas correct' }));
165
- }
166
- } else {
167
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Le selfie n\'est pas correct' }));
168
- }
169
- }).catch((e: any) => {
170
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du selfie' }));
171
- });
172
- }
173
- }
174
-
175
- // Capture manuelle - affiche le preview
176
- const handleCapture = async (result: { success: boolean; path?: string; error?: string }) => {
177
- console.log("handleCapture called", result, silentCaptureResult);
178
-
179
- // Priorité 1: Si la capture silencieuse a réussi, utiliser ce path
180
- if (silentCaptureResult.success && silentCaptureResult.path) {
181
- setPreviewImagePath(silentCaptureResult.path);
182
- setShowCamera(false);
183
- setShowPreview(true);
184
- return;
185
- }
186
-
187
- // Priorité 2: Capture manuelle normale
188
- if (result.success && result.path) {
189
- setPreviewImagePath(result.path);
190
- setShowCamera(false);
191
- setShowPreview(true);
192
- }
193
- };
194
-
195
- // Confirmer la capture depuis le preview
196
- const handleConfirmCapture = async () => {
197
- if (!previewImagePath) return;
198
-
199
- try {
200
- const base64 = await pathToBase64(previewImagePath);
201
- const newImages: Record<string, any> = {
202
- ...capturedImages,
203
- [currentOrientation]: { dir: previewImagePath, file: base64 } as IImagePayload,
204
- };
205
- setCapturedImages(newImages);
206
- onValueChange(newImages);
207
- setShowPreview(false);
208
- setPreviewImagePath(null);
209
- setSilentCaptureResult({ success: false, isAnalyzing: false });
210
-
211
- // Passer à l'orientation suivante si disponible
212
- const currentIndex = orientations.indexOf(currentOrientation as OrientationType);
213
- if (currentIndex < orientations.length - 1) {
214
- const nextOrientation = orientations[currentIndex + 1];
215
- setCurrentOrientation(nextOrientation);
216
- // Rouvrir automatiquement la caméra pour la prochaine orientation
217
- setShowCamera(true);
218
- }
219
- // Si toutes les orientations sont complétées, on reste sur la vue principale
220
- } catch (e) {
221
- console.error("Error confirming capture", e);
222
- showAlert('Erreur', 'Erreur lors de la sauvegarde de l\'image');
223
- }
224
- };
225
-
226
- // Reprendre la photo depuis le preview
227
- const handleRetakeFromPreview = () => {
228
- setShowPreview(false);
229
- setPreviewImagePath(null);
230
- setSilentCaptureResult({ success: false, isAnalyzing: false });
231
- setShowCamera(true);
232
- };
233
- const idCardData = useMemo(() => {
234
- const idCardID = Object.keys(state.componentData).find((c: string) => c === "1");
235
- if (idCardID) {
236
- const _idCardData = state.componentData[idCardID];
237
- const documentType = _idCardData?.documentType;
238
- // Map national_id to identity_card for selfie capture
239
- const mappedDocumentType = documentType === 'national_id' ? 'identity_card' : documentType;
240
- return {
241
- country: _idCardData?.country,
242
- documentType: mappedDocumentType as GovernmentDocumentType
243
- };
244
- }
245
- return null;
246
- }, [state.componentData]);
247
- console.log("idCardData", truncateFields(idCardData), JSON.stringify(truncateFields(state.componentData), null, 2));
248
-
249
-
250
- const handleError = (event: { message: string }) => {
251
- showAlert('Erreur', event.message);
252
- setShowCamera(false);
253
- };
254
-
255
-
256
- const isOrientationCompleted = (orientation: OrientationType): boolean => {
257
- return !!capturedImages[orientation];
258
- };
259
-
260
- const isAllOrientationsCompleted = (): boolean => {
261
- return orientations.every(orientation => isOrientationCompleted(orientation));
262
- };
263
- console.log("Current Orientation", currentOrientation);
264
-
265
- // Vue Preview
266
- if (showPreview && previewImagePath) {
267
- return (
268
- <View style={styles.containerCamera}>
269
- <View style={styles.previewContainer}>
270
- <View style={styles.previewHeader}>
271
- <TouchableOpacity onPress={handleRetakeFromPreview} style={styles.backButton}>
272
- <Text style={styles.backButtonText}>←</Text>
273
- </TouchableOpacity>
274
- <Text style={styles.previewTitle}>
275
- {state.currentLanguage === "en" ? "Preview" : "Aperçu"}
276
- </Text>
277
- <View style={{ width: 40 }} />
278
- </View>
279
-
280
- <View style={styles.previewImageContainer}>
281
- <Image
282
- source={{ uri: previewImagePath }}
283
- style={styles.previewImage}
284
- resizeMode="contain"
285
- />
286
- </View>
287
-
288
- <Text style={styles.previewInstructions}>
289
- {state.currentLanguage === "en"
290
- ? "Is your face clearly visible?"
291
- : "Votre visage est-il clairement visible ?"}
292
- </Text>
293
-
294
- <View style={styles.previewButtonsContainer}>
295
- <TouchableOpacity
296
- style={styles.retakeButton}
297
- onPress={handleRetakeFromPreview}
298
- >
299
- <Text style={styles.retakeButtonText}>
300
- {state.currentLanguage === "en" ? "Retake" : "Reprendre"}
301
- </Text>
302
- </TouchableOpacity>
303
-
304
- <TouchableOpacity
305
- style={styles.confirmButton}
306
- onPress={handleConfirmCapture}
307
- >
308
- <Text style={styles.confirmButtonText}>
309
- {state.currentLanguage === "en" ? "Use Photo" : "Utiliser"}
310
- </Text>
311
- </TouchableOpacity>
312
- </View>
313
- </View>
314
- </View>
315
- );
316
- }
317
-
318
- // Vue Camera
319
- if (showCamera) {
320
- return (
321
- <View style={styles.containerCamera}>
322
- <EnhancedCameraView
323
- instructions={getOrientationInstructions(currentOrientation)}
324
- showCamera={true}
325
- cameraType="front"
326
- style={styles.camera}
327
- onCapture={handleCapture}
328
- onError={handleError}
329
- onClose={() => setShowCamera(false)}
330
- quality="high"
331
- showCaptureButton={true}
332
- showSwitchCamera={false}
333
- enableFlash={false}
334
- silentCaptureResult={silentCaptureResult}
335
- onSilentCapture={handleSilentCapture}
336
- overlayComponent={<SelfieOverlay
337
- xMin={20}
338
- yMin={140}
339
- xMax={370}
340
- yMax={340}
341
- cornerOpacity={1}
342
- instructions={""}
343
- language={state.currentLanguage}
344
- orientation={currentOrientation as 'center' | 'left' | 'right'}
345
- instructionsTile={getInstructions(currentOrientation).title}
346
- instructionsSubtitle={getInstructions(currentOrientation).subtitle}
347
- stepperProps={{
348
- back: () => {
349
- setShowCamera(false);
350
- },
351
- selectedDocumentType: "Selfie",
352
- step: state.currentComponentIndex + 1,
353
- totalSteps: state.template.components.length,
354
- side: currentOrientation,
355
- }} />}
356
- />
357
- </View>
358
- );
359
- }
360
-
361
- return (
362
- <View style={[{
363
- maxWidth: 760,
364
- width: '100%',
365
- height: '100%',
366
- }]}>
367
- <View style={styles.container}>
368
- <Text style={styles.title}>{getLocalizedText(component.labels as LocalizedText)}</Text>
369
- <Text style={styles.description}>{getLocalizedText(component.instructions as LocalizedText)}</Text>
370
-
371
- <ScrollView style={styles.orientationsContainer} showsVerticalScrollIndicator={false}>
372
- <View style={{ flexDirection: 'column', alignItems: 'center', gap: 10 }}>
373
-
374
- {orientations.map((orientation, key) => (
375
- <View key={orientation} style={{
376
- backgroundColor: '#F3F4F6',
377
- borderRadius: 12,
378
- flexDirection: 'row',
379
- alignItems: 'center',
380
- gap: 10,
381
- width: '100%',
382
- paddingVertical: 10,
383
- paddingHorizontal: 10,
384
- }}>
385
- <View style={{
386
- flexDirection: 'row',
387
- alignItems: 'center',
388
- justifyContent: 'center',
389
- backgroundColor: isOrientationCompleted(orientation) ? '#2DBE60' : '#D1D5DB',
390
- borderRadius: 100,
391
- width: 30,
392
- height: 30,
393
- }}>
394
- <Text style={{ color: 'white', fontSize: 16, fontWeight: 'bold' }}>{key + 1}</Text>
395
- </View>
396
-
397
-
398
- <Text style={styles.orientationTitle}>
399
- {getOrientationLabel(orientation)}
400
- </Text>
401
-
402
-
403
-
404
- </View>
405
- ))}
406
- <View style={{ backgroundColor: "#F6CB0D1A", width: "100%", padding: 10, borderRadius: 10 }} >
407
- <Text style={{ color: '#997E06', fontSize: 16, fontWeight: 'bold' }}>
408
- {state.currentLanguage === "en"
409
- ? "Tips for a good selfie:"
410
- : "Conseils pour une bonne photo selfie :"}
411
- </Text>
412
- <View style={{ paddingLeft: 10, paddingTop: 4 }}>
413
- <Text style={{ color: '#997E06', fontSize: 14, fontWeight: 'bold' }}>
414
- {state.currentLanguage === "en"
415
- ? `• Remove glasses and hat \n• Look directly at the camera \n• Ensure good lighting \n`
416
- : `• Retirez vos lunettes et votre chapeau \n• Regardez directement la caméra \n• Assurez-vous d'une bonne luminosité \n`
417
- }
418
- </Text>
419
- </View>
420
- </View>
421
- <View style={{ height: 10 }} />
422
- {isAllOrientationsCompleted() ? <>
423
- <Button title={t('common.continue')} fullWidth style={{ paddingVertical: 20, paddingTop: 12 }} onPress={async () => {
424
- const value = {
425
- ...capturedImages,
426
- ...(idCardData?.country ? { country: idCardData.country } : {}),
427
- ...(idCardData?.documentType ? { documentType: idCardData.documentType } : {}),
428
- }
429
- console.log("value", JSON.stringify(truncateFields(value), null, 2));
430
- onValueChange(value);
431
- actions.nextComponent();
432
- }} />
433
- </> : (
434
- <Button title={t('kyc.selfieCapture.captureButton')} fullWidth style={{ paddingVertical: 20, paddingTop: 12 }} onPress={() => {
435
- if (isAllOrientationsCompleted()) {
436
-
437
- actions.nextComponent();
438
- } else {
439
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: '' }));
440
- setShowCamera(true);
441
- }
442
- }} />
443
- )}
444
-
445
- </View>
446
- </ScrollView>
447
-
448
- {error && (
449
- <Text style={styles.errorText}>{error}</Text>
450
- )}
451
- </View>
452
- </View>
453
- );
454
- };
455
-
456
- const styles = StyleSheet.create({
457
- root: {
458
- flex: 1,
459
- paddingVertical: 10,
460
- },
461
- container: {
462
- backgroundColor: 'white',
463
- margin: 15,
464
- borderRadius: 20,
465
- paddingVertical: 20,
466
- paddingHorizontal: 16,
467
- shadowColor: '#000',
468
- shadowOffset: { width: 0, height: 2 },
469
- shadowOpacity: 0.35,
470
- shadowRadius: 4.84,
471
- elevation: 10,
472
- },
473
- containerCamera: {
474
- flex: 1,
475
- width: '100%',
476
- height: '100%',
477
- backgroundColor: '#000',
478
- },
479
- title: {
480
- fontSize: 24,
481
- fontWeight: 'bold',
482
- color: '#333',
483
- marginBottom: 8,
484
- textAlign: 'center',
485
- },
486
- description: {
487
- fontSize: 16,
488
- color: '#666',
489
- marginBottom: 20,
490
- lineHeight: 22,
491
- textAlign: 'center',
492
- },
493
- camera: {
494
- flex: 1,
495
- overflow: 'hidden',
496
- },
497
- attemptsText: {
498
- fontSize: 14,
499
- color: '#666',
500
- textAlign: 'center',
501
- marginBottom: 10,
502
- },
503
- orientationsContainer: {
504
- },
505
- orientationContainer: {
506
- backgroundColor: 'white',
507
- borderRadius: 12,
508
- padding: 16,
509
- marginBottom: 16,
510
- shadowColor: '#000',
511
- shadowOffset: {
512
- width: 0,
513
- height: 2,
514
- },
515
- shadowOpacity: 0.1,
516
- shadowRadius: 3.84,
517
- elevation: 5,
518
- },
519
- orientationTitle: {
520
- fontSize: 18,
521
- fontWeight: '600',
522
- color: '#333',
523
- },
524
- capturedImageContainer: {
525
- alignItems: 'center',
526
- },
527
- capturedImage: {
528
- width: 200,
529
- height: 200,
530
- borderRadius: 12,
531
- marginBottom: 12,
532
- },
533
- captureButton: {
534
- flexDirection: 'row',
535
- alignItems: 'center',
536
- justifyContent: 'center',
537
- backgroundColor: '#2DBD60',
538
- paddingVertical: 16,
539
- paddingHorizontal: 24,
540
- borderRadius: 12,
541
- marginTop: 8,
542
- },
543
- captureIcon: {
544
- fontSize: 24,
545
- marginRight: 8,
546
- },
547
- captureText: {
548
- fontSize: 16,
549
- fontWeight: '600',
550
- color: 'white',
551
- },
552
- // Preview styles
553
- previewContainer: {
554
- flex: 1,
555
- backgroundColor: '#000',
556
- justifyContent: 'space-between',
557
- },
558
- previewHeader: {
559
- flexDirection: 'row',
560
- alignItems: 'center',
561
- justifyContent: 'space-between',
562
- paddingHorizontal: 16,
563
- paddingTop: 50,
564
- paddingBottom: 16,
565
- },
566
- backButton: {
567
- width: 40,
568
- height: 40,
569
- borderRadius: 20,
570
- backgroundColor: 'rgba(255,255,255,0.2)',
571
- justifyContent: 'center',
572
- alignItems: 'center',
573
- },
574
- backButtonText: {
575
- color: 'white',
576
- fontSize: 24,
577
- fontWeight: 'bold',
578
- },
579
- previewTitle: {
580
- color: 'white',
581
- fontSize: 18,
582
- fontWeight: '600',
583
- },
584
- previewImageContainer: {
585
- flex: 1,
586
- justifyContent: 'center',
587
- alignItems: 'center',
588
- paddingHorizontal: 20,
589
- },
590
- previewImage: {
591
- width: '100%',
592
- height: '80%',
593
- borderRadius: 16,
594
- },
595
- previewInstructions: {
596
- color: 'white',
597
- fontSize: 16,
598
- textAlign: 'center',
599
- paddingHorizontal: 20,
600
- marginBottom: 20,
601
- },
602
- previewButtonsContainer: {
603
- flexDirection: 'row',
604
- justifyContent: 'space-between',
605
- paddingHorizontal: 20,
606
- paddingBottom: 40,
607
- gap: 16,
608
- },
609
- retakeButton: {
610
- flex: 1,
611
- backgroundColor: 'rgba(255,255,255,0.2)',
612
- paddingVertical: 16,
613
- paddingHorizontal: 20,
614
- borderRadius: 12,
615
- alignItems: 'center',
616
- },
617
- retakeButtonText: {
618
- fontSize: 16,
619
- fontWeight: '600',
620
- color: 'white',
621
- },
622
- confirmButton: {
623
- flex: 1,
624
- backgroundColor: '#2DBD60',
625
- paddingVertical: 16,
626
- paddingHorizontal: 20,
627
- borderRadius: 12,
628
- alignItems: 'center',
629
- },
630
- confirmButtonText: {
631
- fontSize: 16,
632
- fontWeight: '600',
633
- color: 'white',
634
- },
635
- completionContainer: {
636
- alignItems: 'center',
637
- padding: 20,
638
- },
639
- completionText: {
640
- fontSize: 18,
641
- fontWeight: '600',
642
- color: '#2DBD60',
643
- marginBottom: 16,
644
- },
645
- retakeAllButton: {
646
- backgroundColor: '#FF6B6B',
647
- paddingVertical: 12,
648
- paddingHorizontal: 20,
649
- borderRadius: 8,
650
- },
651
- retakeAllButtonText: {
652
- fontSize: 14,
653
- fontWeight: '600',
654
- color: 'white',
655
- },
656
- errorText: {
657
- color: '#dc2626',
658
- fontSize: 14,
659
- textAlign: 'center',
660
- marginTop: 10,
661
- },
662
- });
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import { View, Text, StyleSheet, ScrollView, Image, TouchableOpacity, ActivityIndicator, Platform } from 'react-native';
3
+ import { showAlert } from '../../utils/platformAlert';
4
+ import { TemplateComponent, LocalizedText, ISilentCaptureResult, OrientationType, GovernmentDocumentType } from '../../types/KYC.types';
5
+ import { EnhancedCameraView } from '../EnhancedCameraView';
6
+ import { Button } from '../ui/Button';
7
+ import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
8
+ import { useI18n } from '../../hooks/useI18n';
9
+ import SelfieOverlay from '../OverLay/SelfieOverlay';
10
+ import selfieVerification from '../../modules/api/SelfieVerification';
11
+ import pathToBase64 from '../../utils/pathToBase64';
12
+
13
+ interface IImagePayload { dir: string; file: string; }
14
+ interface SelfieCaptureTemplateProps {
15
+ component: TemplateComponent;
16
+ value: Record<string, IImagePayload> | undefined;
17
+ onValueChange: (value: Record<string, IImagePayload | string>) => void;
18
+ error?: string;
19
+ language: string;
20
+ }
21
+
22
+ export const SelfieCaptureTemplate: React.FC<SelfieCaptureTemplateProps> = ({
23
+ component, value, onValueChange, error, language,
24
+ }) => {
25
+ const { t } = useI18n();
26
+ const orientations: OrientationType[] = (['center','left','right']) as OrientationType[];
27
+ const { actions, state, env } = useTemplateKYCFlowContext();
28
+
29
+ const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
30
+ const [showCamera, setShowCamera] = useState(false);
31
+ const [showPreview, setShowPreview] = useState(false);
32
+ const [previewImagePath, setPreviewImagePath] = useState<string | null>(null);
33
+ const [currentOrientation, setCurrentOrientation] = useState<OrientationType>(orientations[0]);
34
+ const [capturedImages, setCapturedImages] = useState<Record<string, IImagePayload>>(value || {});
35
+
36
+ const getLocalizedText = (text: LocalizedText | Record<string, LocalizedText>): string => {
37
+ if (!text) return '';
38
+ const maybeNested = text as Record<string, LocalizedText>;
39
+ if (maybeNested && typeof maybeNested === 'object' && 'en' in maybeNested === false && 'fr' in maybeNested === false) {
40
+ const firstKey = Object.keys(maybeNested)[0];
41
+ const nested = maybeNested[firstKey] as LocalizedText;
42
+ return nested?.[language] || nested?.en || '';
43
+ }
44
+ const loc = text as LocalizedText;
45
+ return loc[language] || loc.en || '';
46
+ };
47
+
48
+
49
+ useEffect(() => {
50
+ actions.showCustomStepper(!showCamera);
51
+ }, [showCamera]);
52
+
53
+ const getOrientationLabel = (orientation: OrientationType): string => {
54
+ switch (orientation) {
55
+ case 'center': return state.currentLanguage === "en" ? "Front Profile Selfie" : "Selfie de face";
56
+ case 'left': return state.currentLanguage === "en" ? "Left Profile Selfie" : "Selfie profil gauche";
57
+ case 'right': return state.currentLanguage === "en" ? "Right Profile Selfie" : "Selfie profil droit";
58
+ default: return getLocalizedText(component.labels as LocalizedText);
59
+ }
60
+ };
61
+
62
+ const getOrientationOpposite = (orientation: OrientationType): OrientationType => {
63
+ switch (orientation) {
64
+ case 'center': return 'center';
65
+ case 'left': return 'left';
66
+ case 'right': return 'right';
67
+ default: return orientation;
68
+ }
69
+ }
70
+
71
+ const getInstructions = (orientation: OrientationType): { title: string, subtitle: string } => {
72
+ const lang = state.currentLanguage;
73
+ switch (orientation) {
74
+ case 'center': return { title: lang === "en" ? 'Center your face' : 'Mettez votre face au centre', subtitle: lang === "en" ? 'Align your face to the center of the selfie area and then take a photo' : 'Alignez votre visage au centre de la zone selfie puis prenez une photo' };
75
+ case 'left': return { title: lang === 'en' ? 'Turn your head left' : 'Tournez la tête vers la gauche', subtitle: lang === 'en' ? 'Slowly turn your head to the left while keeping your face in the selfie area' : 'Tournez lentement la tête vers la gauche en gardant votre visage dans la zone selfie' };
76
+ case 'right': return { title: lang === 'en' ? 'Turn your head right' : 'Tournez la tête vers la droite', subtitle: lang === 'en' ? 'Slowly turn your head to the right while keeping your face in the selfie area' : 'Tournez lentement la tête vers la droite en gardant votre visage dans la zone selfie' };
77
+ default: return { title: getLocalizedText(component.labels as LocalizedText), subtitle: getLocalizedText(component.instructions as LocalizedText) };
78
+ }
79
+ };
80
+
81
+ // 🚨 THE CRITICAL FIX: The AI automatically triggers the preview!
82
+ const handleSilentCapture = (result: { success: boolean; path?: string; error?: string }) => {
83
+ if (silentCaptureResult.isAnalyzing) return;
84
+
85
+ if (result.success && result.path) {
86
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
87
+
88
+ selfieVerification(result.path, env).then((response) => {
89
+ if (response.length > 0) {
90
+ const res = response[0];
91
+
92
+ if ((env === 'SANDBOX' && res?.capture) || (res?.orientation_direction === getOrientationOpposite(currentOrientation) && res?.capture)) {
93
+
94
+ // 🎯 PERFECT MATCH! Skip the manual button and AUTO CAPTURE instantly
95
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: true, error: '', path: result.path }));
96
+ setPreviewImagePath(result.path ?? null);
97
+ setShowCamera(false);
98
+ setShowPreview(true);
99
+
100
+ } else {
101
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Le selfie n\'est pas correct' }));
102
+ }
103
+ } else {
104
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Le selfie n\'est pas correct' }));
105
+ }
106
+ }).catch((e: any) => {
107
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: e?.message || 'Erreur de vérification du selfie' }));
108
+ });
109
+ }
110
+ }
111
+
112
+ // Legacy manual capture removed. Error handler kept.
113
+ const handleError = (event: { message: string }) => {
114
+ showAlert('Erreur', event.message);
115
+ setShowCamera(false);
116
+ };
117
+
118
+ const handleConfirmCapture = async () => {
119
+ if (!previewImagePath) return;
120
+ try {
121
+ const base64 = await pathToBase64(previewImagePath);
122
+ const newImages: Record<string, any> = {
123
+ ...capturedImages,
124
+ [currentOrientation]: { dir: previewImagePath, file: base64 } as IImagePayload,
125
+ };
126
+ setCapturedImages(newImages);
127
+ onValueChange(newImages);
128
+ setShowPreview(false);
129
+ setPreviewImagePath(null);
130
+ setSilentCaptureResult({ success: false, isAnalyzing: false });
131
+
132
+ const currentIndex = orientations.indexOf(currentOrientation as OrientationType);
133
+ if (currentIndex < orientations.length - 1) {
134
+ const nextOrientation = orientations[currentIndex + 1];
135
+ setCurrentOrientation(nextOrientation);
136
+ setShowCamera(true);
137
+ }
138
+ } catch (e) {
139
+ console.error("Error confirming capture", e);
140
+ showAlert('Erreur', 'Erreur lors de la sauvegarde de l\'image');
141
+ }
142
+ };
143
+
144
+ const handleRetakeFromPreview = () => {
145
+ setShowPreview(false);
146
+ setPreviewImagePath(null);
147
+ setSilentCaptureResult({ success: false, isAnalyzing: false });
148
+ setShowCamera(true);
149
+ };
150
+
151
+ const idCardData = useMemo(() => {
152
+ const idCardID = Object.keys(state.componentData).find((c: string) => c === "1");
153
+ if (idCardID) {
154
+ const _idCardData = state.componentData[idCardID];
155
+ const documentType = _idCardData?.documentType;
156
+ const mappedDocumentType = documentType === 'national_id' ? 'identity_card' : documentType;
157
+ return { country: _idCardData?.country, documentType: mappedDocumentType as GovernmentDocumentType };
158
+ }
159
+ return null;
160
+ }, [state.componentData]);
161
+
162
+ const isOrientationCompleted = (orientation: OrientationType): boolean => { return !!capturedImages[orientation]; };
163
+ const isAllOrientationsCompleted = (): boolean => { return orientations.every(orientation => isOrientationCompleted(orientation)); };
164
+
165
+ // --- PREVIEW RENDER ---
166
+ if (showPreview && previewImagePath) {
167
+ return (
168
+ <View style={styles.containerCamera}>
169
+ <View style={styles.previewContainer}>
170
+ <View style={styles.previewHeader}>
171
+ <TouchableOpacity onPress={handleRetakeFromPreview} style={styles.backButton}>
172
+ <Text style={styles.backButtonText}>←</Text>
173
+ </TouchableOpacity>
174
+ <Text style={styles.previewTitle}>{state.currentLanguage === "en" ? "Preview" : "Aperçu"}</Text>
175
+ <View style={{ width: 40 }} />
176
+ </View>
177
+ <View style={styles.previewImageContainer}>
178
+ <Image source={{ uri: previewImagePath }} style={styles.previewImage} resizeMode="contain" />
179
+ </View>
180
+ <Text style={styles.previewInstructions}>
181
+ {state.currentLanguage === "en" ? "Is your face clearly visible?" : "Votre visage est-il clairement visible ?"}
182
+ </Text>
183
+ <View style={styles.previewButtonsContainer}>
184
+ <TouchableOpacity style={styles.retakeButton} onPress={handleRetakeFromPreview}>
185
+ <Text style={styles.retakeButtonText}>{state.currentLanguage === "en" ? "Retake" : "Reprendre"}</Text>
186
+ </TouchableOpacity>
187
+ <TouchableOpacity style={styles.confirmButton} onPress={handleConfirmCapture}>
188
+ <Text style={styles.confirmButtonText}>{state.currentLanguage === "en" ? "Use Photo" : "Utiliser"}</Text>
189
+ </TouchableOpacity>
190
+ </View>
191
+ </View>
192
+ </View>
193
+ );
194
+ }
195
+
196
+ // --- CAMERA RENDER ---
197
+ if (showCamera) {
198
+ return (
199
+ <View style={styles.containerCamera}>
200
+ <EnhancedCameraView
201
+ key={currentOrientation} // Forces complete reset when direction changes
202
+ showCamera={true}
203
+ cameraType="front"
204
+ style={styles.camera}
205
+ onError={handleError}
206
+ onClose={() => setShowCamera(false)}
207
+ quality="high"
208
+ silentCaptureResult={silentCaptureResult}
209
+ onSilentCapture={handleSilentCapture}
210
+ overlayComponent={
211
+ <>
212
+ {/* Subtle AI processing pill */}
213
+ {silentCaptureResult.isAnalyzing && (
214
+ <View style={styles.topAnalyzingPillContainer}>
215
+ <View style={styles.topAnalyzingPill}>
216
+ <ActivityIndicator size="small" color="white" />
217
+ <Text style={styles.analyzingPillText}>
218
+ {state.currentLanguage === 'en' ? 'Verifying position...' : 'Vérification...'}
219
+ </Text>
220
+ </View>
221
+ </View>
222
+ )}
223
+
224
+ <SelfieOverlay
225
+ xMin={20} yMin={140} xMax={370} yMax={340}
226
+ cornerOpacity={1}
227
+ instructions={""}
228
+ language={state.currentLanguage}
229
+ orientation={currentOrientation as 'center' | 'left' | 'right'}
230
+ instructionsTile={getInstructions(currentOrientation).title}
231
+ instructionsSubtitle={getInstructions(currentOrientation).subtitle}
232
+ stepperProps={{
233
+ back: () => setShowCamera(false),
234
+ selectedDocumentType: "Selfie",
235
+ step: state.currentComponentIndex + 1,
236
+ totalSteps: state.template.components.length,
237
+ side: currentOrientation,
238
+ }}
239
+ />
240
+ </>
241
+ }
242
+ />
243
+ </View>
244
+ );
245
+ }
246
+
247
+ return (
248
+ <View style={[{ maxWidth: 760, width: '100%', height: '100%' }]}>
249
+ <View style={styles.container}>
250
+ <Text style={styles.title}>{getLocalizedText(component.labels as LocalizedText)}</Text>
251
+ <Text style={styles.description}>{getLocalizedText(component.instructions as LocalizedText)}</Text>
252
+ <ScrollView style={styles.orientationsContainer} showsVerticalScrollIndicator={false}>
253
+ <View style={{ flexDirection: 'column', alignItems: 'center', gap: 10 }}>
254
+ {orientations.map((orientation, key) => (
255
+ <View key={orientation} style={styles.orientationListItem}>
256
+ <View style={[styles.orientationListCircle, { backgroundColor: isOrientationCompleted(orientation) ? '#2DBE60' : '#D1D5DB' }]}>
257
+ <Text style={{ color: 'white', fontSize: 16, fontWeight: 'bold' }}>{key + 1}</Text>
258
+ </View>
259
+ <Text style={styles.orientationTitle}>{getOrientationLabel(orientation)}</Text>
260
+ </View>
261
+ ))}
262
+ <View style={styles.tipsContainer}>
263
+ <Text style={styles.tipsHeader}>
264
+ {state.currentLanguage === "en" ? "Tips for a good selfie:" : "Conseils pour une bonne photo selfie :"}
265
+ </Text>
266
+ <View style={{ paddingLeft: 10, paddingTop: 4 }}>
267
+ <Text style={styles.tipsBody}>
268
+ {state.currentLanguage === "en"
269
+ ? `• Remove glasses and hat \n• Look directly at the camera \n• Ensure good lighting \n`
270
+ : `• Retirez vos lunettes et votre chapeau \n• Regardez directement la caméra \n• Assurez-vous d'une bonne luminosité \n`}
271
+ </Text>
272
+ </View>
273
+ </View>
274
+ <View style={{ height: 10 }} />
275
+ {isAllOrientationsCompleted() ? (
276
+ <Button title={t('common.continue')} fullWidth style={{ paddingVertical: 20, paddingTop: 12 }} onPress={async () => {
277
+ const value = {
278
+ ...capturedImages,
279
+ ...(idCardData?.country ? { country: idCardData.country } : {}),
280
+ ...(idCardData?.documentType ? { documentType: idCardData.documentType } : {}),
281
+ }
282
+ onValueChange(value);
283
+ actions.nextComponent();
284
+ }} />
285
+ ) : (
286
+ <Button title={t('kyc.selfieCapture.captureButton')} fullWidth style={{ paddingVertical: 20, paddingTop: 12 }} onPress={() => {
287
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: '' }));
288
+ setShowCamera(true);
289
+ }} />
290
+ )}
291
+ </View>
292
+ </ScrollView>
293
+ {error && <Text style={styles.errorText}>{error}</Text>}
294
+ </View>
295
+ </View>
296
+ );
297
+ };
298
+
299
+ const styles = StyleSheet.create({
300
+ root: { flex: 1, paddingVertical: 10 },
301
+ container: { backgroundColor: 'white', margin: 15, borderRadius: 20, paddingVertical: 20, paddingHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.35, shadowRadius: 4.84, elevation: 10 },
302
+ containerCamera: { ...StyleSheet.absoluteFillObject, backgroundColor: '#000', zIndex: 999 },
303
+ title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
304
+ description: { fontSize: 16, color: '#666', marginBottom: 20, lineHeight: 22, textAlign: 'center' },
305
+ camera: { flex: 1 },
306
+ orientationsContainer: {},
307
+ orientationListItem: { backgroundColor: '#F3F4F6', borderRadius: 12, flexDirection: 'row', alignItems: 'center', gap: 10, width: '100%', paddingVertical: 10, paddingHorizontal: 10 },
308
+ orientationListCircle: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', borderRadius: 100, width: 30, height: 30 },
309
+ orientationTitle: { fontSize: 18, fontWeight: '600', color: '#333' },
310
+ tipsContainer: { backgroundColor: "#F6CB0D1A", width: "100%", padding: 10, borderRadius: 10 },
311
+ tipsHeader: { color: '#997E06', fontSize: 16, fontWeight: 'bold' },
312
+ tipsBody: { color: '#997E06', fontSize: 14, fontWeight: 'bold' },
313
+ previewContainer: { flex: 1, backgroundColor: '#000', justifyContent: 'space-between' },
314
+ previewHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingTop: 50, paddingBottom: 16 },
315
+ backButton: { width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(255,255,255,0.2)', justifyContent: 'center', alignItems: 'center' },
316
+ backButtonText: { color: 'white', fontSize: 24, fontWeight: 'bold' },
317
+ previewTitle: { color: 'white', fontSize: 18, fontWeight: '600' },
318
+ previewImageContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingHorizontal: 20 },
319
+ previewImage: { width: '100%', height: '80%', borderRadius: 16 },
320
+ previewInstructions: { color: 'white', fontSize: 16, textAlign: 'center', paddingHorizontal: 20, marginBottom: 20 },
321
+ previewButtonsContainer: { flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 20, paddingBottom: 40, gap: 16 },
322
+ retakeButton: { flex: 1, backgroundColor: 'rgba(255,255,255,0.2)', paddingVertical: 16, paddingHorizontal: 20, borderRadius: 12, alignItems: 'center' },
323
+ retakeButtonText: { fontSize: 16, fontWeight: '600', color: 'white' },
324
+ confirmButton: { flex: 1, backgroundColor: '#2DBD60', paddingVertical: 16, paddingHorizontal: 20, borderRadius: 12, alignItems: 'center' },
325
+ confirmButtonText: { fontSize: 16, fontWeight: '600', color: 'white' },
326
+ errorText: { color: '#dc2626', fontSize: 14, textAlign: 'center', marginTop: 10 },
327
+ topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
328
+ topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
329
+ analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' },
330
+ });