@sanctum-key/react-native-sdk 1.0.5 → 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.
- package/README.md +4 -4
- package/android/build/.transforms/{f62cb96b2d1f78ca96ab35932dd530dc → c9d62bb333688ab562f51958998d5a48}/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/generated/source/buildConfig/debug/kyc/{transfergratis → SanctumKey}/com/BuildConfig.java +2 -2
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +1 -1
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +1 -1
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
- package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/kyc/SanctumKey/com/BuildConfig.class +0 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +1 -1
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +1 -1
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +1 -1
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/SanctumKey/com/SanctumKeySdkModule$definition$1$5$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/SanctumKey/com/SanctumKeySdkModule$definition$1$5$2.class +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/android/build/tmp/kotlin-classes/debug/kyc/SanctumKey/com/SanctumKeySdkModule$definition$lambda$5$$inlined$View$1.class +0 -0
- 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
- 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
- package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkModule.class → SanctumKey/com/SanctumKeySdkModule.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/{transfergratis/com/TransfergratisSdkView.class → SanctumKey/com/SanctumKeySdkView.class} +0 -0
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkModule.kt +6 -6
- package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkView.kt +2 -2
- package/build/package.json +9 -7
- package/build/src/App.d.ts +2 -2
- package/build/src/App.d.ts.map +1 -1
- package/build/src/App.js +2 -2
- package/build/src/App.js.map +1 -1
- package/build/src/{TransfergratisSdk.types.d.ts → SanctumKeySdk.types.d.ts} +3 -3
- package/build/src/SanctumKeySdk.types.d.ts.map +1 -0
- package/build/src/SanctumKeySdk.types.js +2 -0
- package/build/src/SanctumKeySdk.types.js.map +1 -0
- package/build/src/{TransfergratisSdkModule.d.ts → SanctumKeySdkModule.d.ts} +4 -4
- package/build/src/SanctumKeySdkModule.d.ts.map +1 -0
- package/build/src/{TransfergratisSdkModule.js → SanctumKeySdkModule.js} +2 -2
- package/build/src/SanctumKeySdkModule.js.map +1 -0
- package/build/src/{TransfergratisSdkModule.web.d.ts → SanctumKeySdkModule.web.d.ts} +4 -4
- package/build/src/SanctumKeySdkModule.web.d.ts.map +1 -0
- package/build/src/{TransfergratisSdkModule.web.js → SanctumKeySdkModule.web.js} +3 -3
- package/build/src/SanctumKeySdkModule.web.js.map +1 -0
- package/build/src/SanctumKeySdkView.d.ts +4 -0
- package/build/src/SanctumKeySdkView.d.ts.map +1 -0
- package/build/src/SanctumKeySdkView.js +7 -0
- package/build/src/SanctumKeySdkView.js.map +1 -0
- package/build/src/SanctumKeySdkView.web.d.ts +4 -0
- package/build/src/SanctumKeySdkView.web.d.ts.map +1 -0
- package/build/src/{TransfergratisSdkView.web.js → SanctumKeySdkView.web.js} +2 -2
- package/build/src/SanctumKeySdkView.web.js.map +1 -0
- package/build/src/api/axios.js +2 -2
- package/build/src/api/axios.js.map +1 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +66 -338
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.js +93 -15
- package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +167 -695
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js +269 -40
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/SelfieCapture.d.ts +1 -1
- package/build/src/components/KYCElements/SelfieCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/SelfieCapture.js +130 -192
- package/build/src/components/KYCElements/SelfieCapture.js.map +1 -1
- package/build/src/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/SelfieCaptureTemplate.js +131 -433
- package/build/src/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
- package/build/src/components/NativeCameraView.js +1 -1
- package/build/src/components/NativeCameraView.js.map +1 -1
- package/build/src/components/OverLay/IdCard.d.ts +3 -2
- package/build/src/components/OverLay/IdCard.d.ts.map +1 -1
- package/build/src/components/OverLay/IdCard.js +149 -141
- package/build/src/components/OverLay/IdCard.js.map +1 -1
- package/build/src/components/OverLay/SelfieOverlay.d.ts +2 -1
- package/build/src/components/OverLay/SelfieOverlay.d.ts.map +1 -1
- package/build/src/components/OverLay/SelfieOverlay.js +37 -95
- package/build/src/components/OverLay/SelfieOverlay.js.map +1 -1
- package/build/src/components/OverLay/type.d.ts +1 -0
- package/build/src/components/OverLay/type.d.ts.map +1 -1
- package/build/src/components/OverLay/type.js.map +1 -1
- package/build/src/components/Svgs/scanningLine.d.ts +2 -1
- package/build/src/components/Svgs/scanningLine.d.ts.map +1 -1
- package/build/src/components/Svgs/scanningLine.js +55 -51
- package/build/src/components/Svgs/scanningLine.js.map +1 -1
- package/build/src/config/KYCConfig.js +1 -1
- package/build/src/config/KYCConfig.js.map +1 -1
- package/build/src/config/allowedDomains.js +6 -6
- package/build/src/config/allowedDomains.js.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/src/hooks/useTemplateKYCFlow.js +37 -38
- package/build/src/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +3 -3
- package/build/src/index.js.map +1 -1
- package/build/src/modules/api/CardAuthentification.d.ts +0 -5
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +114 -116
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +11 -1
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +101 -38
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/modules/camera/NativeCameraModule.js +17 -17
- package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
- package/expo-module.config.json +2 -2
- package/ios/TransfergratisSdk.podspec +2 -2
- package/ios/TransfergratisSdkModule.swift +12 -12
- package/package.json +9 -7
- package/src/App.tsx +2 -2
- package/src/{TransfergratisSdk.types.ts → SanctumKeySdk.types.ts} +2 -2
- package/src/{TransfergratisSdkModule.ts → SanctumKeySdkModule.ts} +3 -3
- package/src/{TransfergratisSdkModule.web.ts → SanctumKeySdkModule.web.ts} +3 -3
- package/src/SanctumKeySdkView.tsx +11 -0
- package/src/{TransfergratisSdkView.web.tsx → SanctumKeySdkView.web.tsx} +2 -2
- package/src/api/axios.ts +2 -2
- package/src/components/EnhancedCameraView.tsx +81 -400
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +115 -26
- package/src/components/KYCElements/IDCardCapture.tsx +228 -868
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +328 -60
- package/src/components/KYCElements/SelfieCapture.tsx +184 -213
- package/src/components/KYCElements/SelfieCaptureTemplate.tsx +330 -662
- package/src/components/NativeCameraView.tsx +1 -1
- package/src/components/OverLay/IdCard.tsx +218 -217
- package/src/components/OverLay/SelfieOverlay.tsx +56 -134
- package/src/components/OverLay/type.ts +1 -0
- package/src/components/Svgs/scanningLine.tsx +71 -72
- package/src/config/KYCConfig.ts +1 -1
- package/src/config/allowedDomains.ts +6 -6
- package/src/hooks/useTemplateKYCFlow.tsx +45 -39
- package/src/i18n/README.md +1 -1
- package/src/index.ts +3 -3
- package/src/modules/api/CardAuthentification.ts +202 -200
- package/src/modules/api/KYCService.ts +168 -53
- package/src/modules/camera/NativeCameraModule.ts +17 -17
- package/android/build/tmp/kotlin-classes/debug/kyc/transfergratis/com/TransfergratisSdkModule$definition$1$5$1.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/transfergratis/com/TransfergratisSdkModule$definition$1$5$2.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/transfergratis/com/TransfergratisSdkModule$definition$lambda$5$$inlined$View$1.class +0 -0
- package/build/src/TransfergratisSdk.types.d.ts.map +0 -1
- package/build/src/TransfergratisSdk.types.js +0 -2
- package/build/src/TransfergratisSdk.types.js.map +0 -1
- package/build/src/TransfergratisSdkModule.d.ts.map +0 -1
- package/build/src/TransfergratisSdkModule.js.map +0 -1
- package/build/src/TransfergratisSdkModule.web.d.ts.map +0 -1
- package/build/src/TransfergratisSdkModule.web.js.map +0 -1
- package/build/src/TransfergratisSdkView.d.ts +0 -4
- package/build/src/TransfergratisSdkView.d.ts.map +0 -1
- package/build/src/TransfergratisSdkView.js +0 -7
- package/build/src/TransfergratisSdkView.js.map +0 -1
- package/build/src/TransfergratisSdkView.web.d.ts +0 -4
- package/build/src/TransfergratisSdkView.web.d.ts.map +0 -1
- package/build/src/TransfergratisSdkView.web.js.map +0 -1
- package/src/TransfergratisSdkView.tsx +0 -11
- /package/android/build/.transforms/{532c0e65d82f446633d0a7dab2772198 → ab90740579f5bd05b27b4343ada2d1c9}/results.bin +0 -0
- /package/android/build/.transforms/{532c0e65d82f446633d0a7dab2772198 → ab90740579f5bd05b27b4343ada2d1c9}/transformed/classes/classes_dex/classes.dex +0 -0
- /package/android/build/.transforms/{f62cb96b2d1f78ca96ab35932dd530dc → c9d62bb333688ab562f51958998d5a48}/results.bin +0 -0
- /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,
|
|
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
|
|
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;
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
return { type: mappedType, region };
|
|
58
|
+
return { type: mappedType, region: countrySelectionData.region || 'root' };
|
|
92
59
|
}, [countrySelectionData, documentTypeMapping]);
|
|
93
60
|
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
//
|
|
173
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
|
262
|
-
if (
|
|
263
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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
|
|
307
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
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
|
-
|
|
378
|
+
|
|
742
379
|
{!capturedImages[currentSide]?.dir && (
|
|
743
380
|
<Button
|
|
744
|
-
title={state.currentLanguage === "en" ? "
|
|
745
|
-
onPress={() => {
|
|
746
|
-
|
|
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
|
-
|
|
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
|
-
|
|
767
|
-
|
|
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
|
-
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
},
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
},
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
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
|
+
});
|