@sanctum-key/react-native-sdk 1.0.8 → 1.0.9
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.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 +5 -5
- 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/{SanctumKeySdk.types.d.ts → TransfergratisSdk.types.d.ts} +3 -3
- package/build/src/TransfergratisSdk.types.d.ts.map +1 -0
- package/build/src/TransfergratisSdk.types.js +2 -0
- package/build/src/TransfergratisSdk.types.js.map +1 -0
- package/build/src/{SanctumKeySdkModule.d.ts → TransfergratisSdkModule.d.ts} +4 -4
- package/build/src/TransfergratisSdkModule.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkModule.js → TransfergratisSdkModule.js} +2 -2
- package/build/src/TransfergratisSdkModule.js.map +1 -0
- package/build/src/{SanctumKeySdkModule.web.d.ts → TransfergratisSdkModule.web.d.ts} +4 -4
- package/build/src/TransfergratisSdkModule.web.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkModule.web.js → TransfergratisSdkModule.web.js} +3 -3
- package/build/src/TransfergratisSdkModule.web.js.map +1 -0
- package/build/src/TransfergratisSdkView.d.ts +4 -0
- package/build/src/TransfergratisSdkView.d.ts.map +1 -0
- package/build/src/TransfergratisSdkView.js +7 -0
- package/build/src/TransfergratisSdkView.js.map +1 -0
- package/build/src/TransfergratisSdkView.web.d.ts +4 -0
- package/build/src/TransfergratisSdkView.web.d.ts.map +1 -0
- package/build/src/{SanctumKeySdkView.web.js → TransfergratisSdkView.web.js} +2 -2
- package/build/src/TransfergratisSdkView.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 +12 -61
- 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 +11 -32
- package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +2 -1
- 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 +22 -163
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/components/NativeCameraView.js +1 -1
- package/build/src/components/NativeCameraView.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/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/KYCService.d.ts +3 -3
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +25 -32
- 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 +5 -5
- package/src/App.tsx +2 -2
- package/src/{SanctumKeySdk.types.ts → TransfergratisSdk.types.ts} +2 -2
- package/src/{SanctumKeySdkModule.ts → TransfergratisSdkModule.ts} +3 -3
- package/src/{SanctumKeySdkModule.web.ts → TransfergratisSdkModule.web.ts} +3 -3
- package/src/TransfergratisSdkView.tsx +11 -0
- package/src/{SanctumKeySdkView.web.tsx → TransfergratisSdkView.web.tsx} +2 -2
- package/src/api/axios.ts +2 -2
- package/src/components/EnhancedCameraView.tsx +34 -99
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +10 -36
- package/src/components/KYCElements/IDCardCapture.tsx +1 -1
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +30 -204
- package/src/components/NativeCameraView.tsx +1 -1
- package/src/config/KYCConfig.ts +1 -1
- package/src/config/allowedDomains.ts +6 -6
- package/src/i18n/README.md +1 -1
- package/src/index.ts +3 -3
- package/src/modules/api/KYCService.ts +26 -37
- package/src/modules/camera/NativeCameraModule.ts +17 -17
- package/build/src/SanctumKeySdk.types.d.ts.map +0 -1
- package/build/src/SanctumKeySdk.types.js +0 -2
- package/build/src/SanctumKeySdk.types.js.map +0 -1
- package/build/src/SanctumKeySdkModule.d.ts.map +0 -1
- package/build/src/SanctumKeySdkModule.js.map +0 -1
- package/build/src/SanctumKeySdkModule.web.d.ts.map +0 -1
- package/build/src/SanctumKeySdkModule.web.js.map +0 -1
- package/build/src/SanctumKeySdkView.d.ts +0 -4
- package/build/src/SanctumKeySdkView.d.ts.map +0 -1
- package/build/src/SanctumKeySdkView.js +0 -7
- package/build/src/SanctumKeySdkView.js.map +0 -1
- package/build/src/SanctumKeySdkView.web.d.ts +0 -4
- package/build/src/SanctumKeySdkView.web.d.ts.map +0 -1
- package/build/src/SanctumKeySdkView.web.js.map +0 -1
- package/src/SanctumKeySdkView.tsx +0 -11
|
@@ -4,7 +4,7 @@ import UIKit
|
|
|
4
4
|
import Photos
|
|
5
5
|
|
|
6
6
|
// Vue caméra native avec logs et interface améliorée
|
|
7
|
-
public final class
|
|
7
|
+
public final class TransfergratisSdkView: ExpoView {
|
|
8
8
|
let onCapture = EventDispatcher()
|
|
9
9
|
let onError = EventDispatcher()
|
|
10
10
|
|
|
@@ -18,13 +18,13 @@ public final class SanctumKeySdkView: ExpoView {
|
|
|
18
18
|
|
|
19
19
|
// Logs pour débogage
|
|
20
20
|
private func logDebug(_ message: String) {
|
|
21
|
-
print("🟦 [
|
|
21
|
+
print("🟦 [TransfergratisSdkView] \(message)")
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
public required init(appContext: AppContext? = nil) {
|
|
25
25
|
super.init(appContext: appContext)
|
|
26
26
|
logDebug("Initialisation de la vue caméra")
|
|
27
|
-
print("🟦 [
|
|
27
|
+
print("🟦 [TransfergratisSdkView] Init appelé - Vue créée")
|
|
28
28
|
setupViews()
|
|
29
29
|
|
|
30
30
|
// Test immédiat pour voir si les événements fonctionnent
|
|
@@ -180,9 +180,9 @@ public final class SanctumKeySdkView: ExpoView {
|
|
|
180
180
|
|
|
181
181
|
// Classe déléguée pour gérer les protocoles qui nécessitent NSObject
|
|
182
182
|
class CameraDelegate: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
|
183
|
-
weak var module:
|
|
183
|
+
weak var module: TransfergratisSdkModule?
|
|
184
184
|
|
|
185
|
-
init(module:
|
|
185
|
+
init(module: TransfergratisSdkModule) {
|
|
186
186
|
self.module = module
|
|
187
187
|
super.init()
|
|
188
188
|
}
|
|
@@ -228,9 +228,9 @@ class CameraDelegate: NSObject, UIImagePickerControllerDelegate, UINavigationCon
|
|
|
228
228
|
|
|
229
229
|
// Classe déléguée pour le sélecteur de fichiers
|
|
230
230
|
class FilePickerDelegate: NSObject, UIDocumentPickerDelegate {
|
|
231
|
-
weak var module:
|
|
231
|
+
weak var module: TransfergratisSdkModule?
|
|
232
232
|
|
|
233
|
-
init(module:
|
|
233
|
+
init(module: TransfergratisSdkModule) {
|
|
234
234
|
self.module = module
|
|
235
235
|
super.init()
|
|
236
236
|
}
|
|
@@ -261,7 +261,7 @@ class FilePickerDelegate: NSObject, UIDocumentPickerDelegate {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
public class
|
|
264
|
+
public class TransfergratisSdkModule: Module {
|
|
265
265
|
var cameraPromise: Promise?
|
|
266
266
|
var filePickerPromise: Promise?
|
|
267
267
|
var currentPhotoPath: String?
|
|
@@ -269,7 +269,7 @@ public class SanctumKeySdkModule: Module {
|
|
|
269
269
|
private var filePickerDelegate: FilePickerDelegate?
|
|
270
270
|
|
|
271
271
|
public func definition() -> ModuleDefinition {
|
|
272
|
-
Name("
|
|
272
|
+
Name("TransfergratisSdk")
|
|
273
273
|
|
|
274
274
|
Constants([
|
|
275
275
|
"PI": Double.pi
|
|
@@ -364,12 +364,12 @@ public class SanctumKeySdkModule: Module {
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
// Vue native pour la caméra avec instructions
|
|
367
|
-
View(
|
|
368
|
-
Prop("instructions") { (view:
|
|
367
|
+
View(TransfergratisSdkView.self) {
|
|
368
|
+
Prop("instructions") { (view: TransfergratisSdkView, instructions: String) in
|
|
369
369
|
view.setInstructions(instructions)
|
|
370
370
|
}
|
|
371
371
|
|
|
372
|
-
Prop("showCamera") { (view:
|
|
372
|
+
Prop("showCamera") { (view: TransfergratisSdkView, show: Bool) in
|
|
373
373
|
view.showCamera(show)
|
|
374
374
|
}
|
|
375
375
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanctum-key/react-native-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Sanctum Key React Native SDK",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"types": "build/src/index.d.ts",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"kyc",
|
|
26
26
|
"identity-verification"
|
|
27
27
|
],
|
|
28
|
-
"repository": "https://github.com/
|
|
28
|
+
"repository": "https://github.com/TransfergratisOrg/KYC-User-Frontend",
|
|
29
29
|
"bugs": {
|
|
30
|
-
"url": "https://github.com/
|
|
30
|
+
"url": "https://github.com/TransfergratisOrg/KYC-User-Frontend/issues"
|
|
31
31
|
},
|
|
32
|
-
"author": "mesha-
|
|
32
|
+
"author": "mesha-transfergratis <loic.lontchi@transfergratis.com> (https://github.com/mesha-transfergratis)",
|
|
33
33
|
"license": "MIT",
|
|
34
|
-
"homepage": "https://github.com/
|
|
34
|
+
"homepage": "https://github.com/TransfergratisOrg/KYC-User-Frontend#readme",
|
|
35
35
|
"files": [
|
|
36
36
|
"build",
|
|
37
37
|
"src",
|
package/src/App.tsx
CHANGED
|
@@ -2,8 +2,8 @@ import CameraCaptureEx from "./components/example/CameraCaptureEx";
|
|
|
2
2
|
// import SelfieCaptureEx from "./components/example/SelfieCaptureEx";
|
|
3
3
|
// import { KYCExample } from "./components/KYCExample";
|
|
4
4
|
// import { KYCFlow } from "./components/KYCFlow"
|
|
5
|
-
const
|
|
5
|
+
const LaunchTransferGratisKYC = () =>{
|
|
6
6
|
return <CameraCaptureEx/>
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export default
|
|
9
|
+
export default LaunchTransferGratisKYC;
|
|
@@ -15,13 +15,13 @@ export type ErrorEventPayload = {
|
|
|
15
15
|
message: string;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type TransfergratisSdkModuleEvents = {
|
|
19
19
|
onCameraCapture: (params: CameraCaptureEventPayload) => void;
|
|
20
20
|
onFileSelected: (params: FileSelectedEventPayload) => void;
|
|
21
21
|
onError: (params: ErrorEventPayload) => void;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export type
|
|
24
|
+
export type TransfergratisSdkViewProps = {
|
|
25
25
|
instructions: string;
|
|
26
26
|
showCamera: boolean;
|
|
27
27
|
onCapture?: (event: { nativeEvent: { action: 'capture' | 'retake' } }) => void;
|
|
@@ -2,9 +2,9 @@ import { requireNativeModule } from 'expo-modules-core';
|
|
|
2
2
|
|
|
3
3
|
import { NativeModule } from 'expo';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { TransfergratisSdkModuleEvents } from './TransfergratisSdk.types';
|
|
6
6
|
|
|
7
|
-
declare class
|
|
7
|
+
declare class TransfergratisSdkModule extends NativeModule<TransfergratisSdkModuleEvents> {
|
|
8
8
|
PI: number;
|
|
9
9
|
testModule(): Promise<string>;
|
|
10
10
|
requestCameraPermission(): Promise<boolean>;
|
|
@@ -13,4 +13,4 @@ declare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
// This call loads the native module object from the JSI.
|
|
16
|
-
export default requireNativeModule<
|
|
16
|
+
export default requireNativeModule<TransfergratisSdkModule>('TransfergratisSdk');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { registerWebModule } from 'expo-modules-core';
|
|
2
2
|
import { NativeModule } from 'expo';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { TransfergratisSdkModuleEvents } from './TransfergratisSdk.types';
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class TransfergratisSdkModule extends NativeModule<TransfergratisSdkModuleEvents> {
|
|
7
7
|
PI = Math.PI;
|
|
8
8
|
|
|
9
9
|
async requestCameraPermission(): Promise<boolean> {
|
|
@@ -28,4 +28,4 @@ class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export default registerWebModule(
|
|
31
|
+
export default registerWebModule(TransfergratisSdkModule, 'TransfergratisSdkModule');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { requireNativeView } from 'expo';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { TransfergratisSdkViewProps } from './TransfergratisSdk.types';
|
|
5
|
+
|
|
6
|
+
const NativeView: React.ComponentType<TransfergratisSdkViewProps> =
|
|
7
|
+
requireNativeView('TransfergratisSdk');
|
|
8
|
+
|
|
9
|
+
export default function TransfergratisSdkView(props: TransfergratisSdkViewProps) {
|
|
10
|
+
return <NativeView {...props} />;
|
|
11
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { TransfergratisSdkViewProps } from './TransfergratisSdk.types';
|
|
4
4
|
|
|
5
|
-
export default function
|
|
5
|
+
export default function TransfergratisSdkView(props: TransfergratisSdkViewProps) {
|
|
6
6
|
return (
|
|
7
7
|
<div>
|
|
8
8
|
{/* <iframe
|
package/src/api/axios.ts
CHANGED
|
@@ -132,12 +132,12 @@ export default HttpClient;
|
|
|
132
132
|
|
|
133
133
|
// ml service api
|
|
134
134
|
export const mlService = new HttpClient({
|
|
135
|
-
baseURL: 'https://api.ml.
|
|
135
|
+
baseURL: 'https://api.ml.transfergratis.com',
|
|
136
136
|
apiKey: 'your-api-key',
|
|
137
137
|
});
|
|
138
138
|
// backedn service api
|
|
139
139
|
export const backendService = new HttpClient({
|
|
140
|
-
baseURL: 'https://api.backend.
|
|
140
|
+
baseURL: 'https://api.backend.transfergratis.com',
|
|
141
141
|
apiKey: 'your-api-key',
|
|
142
142
|
});
|
|
143
143
|
|
|
@@ -1,35 +1,33 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { View, StyleSheet, Text, AppState
|
|
2
|
+
import { View, StyleSheet, Text, AppState } from 'react-native';
|
|
3
3
|
import { Camera, useCameraDevice } from 'react-native-vision-camera';
|
|
4
4
|
import VisionCameraModule from '../modules/camera/VisionCameraModule';
|
|
5
5
|
import { useI18n } from '../hooks/useI18n';
|
|
6
6
|
import { EnhancedCameraViewProps } from './OverLay/type';
|
|
7
7
|
import { Button } from './ui/Button';
|
|
8
8
|
|
|
9
|
-
export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
9
|
+
export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
10
10
|
showCamera,
|
|
11
11
|
cameraType: initialCameraType = 'front',
|
|
12
12
|
style,
|
|
13
13
|
onError,
|
|
14
|
-
onSilentCapture,
|
|
14
|
+
onSilentCapture,
|
|
15
15
|
silentCaptureResult,
|
|
16
|
-
isProcessing = false,
|
|
16
|
+
isProcessing = false,
|
|
17
17
|
overlayComponent,
|
|
18
18
|
}) => {
|
|
19
19
|
const { t } = useI18n();
|
|
20
20
|
const camera = useRef<Camera>(null);
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
const isCapturingRef = useRef(false);
|
|
23
23
|
const isProcessingRef = useRef(isProcessing);
|
|
24
24
|
|
|
25
25
|
const [cameraType] = useState<'front' | 'back'>(initialCameraType);
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
// 🚨 BUG FIX: Initialize to null to prevent the "Flicker" on retake
|
|
27
28
|
const [hasPermission, setHasPermission] = useState<boolean | null>(null);
|
|
28
29
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
29
30
|
const [refreshCamera, setRefreshCamera] = useState(false);
|
|
30
|
-
|
|
31
|
-
// 🚨 ADDED: A timeout state to show a refresh button if the camera gets stuck booting
|
|
32
|
-
const [showInitTimeout, setShowInitTimeout] = useState(false);
|
|
33
31
|
|
|
34
32
|
const device = useCameraDevice(cameraType);
|
|
35
33
|
|
|
@@ -56,10 +54,7 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
56
54
|
};
|
|
57
55
|
|
|
58
56
|
useEffect(() => {
|
|
59
|
-
if (showCamera)
|
|
60
|
-
setIsInitialized(false); // Reset init state when checking permissions/refreshing
|
|
61
|
-
checkPermissions();
|
|
62
|
-
}
|
|
57
|
+
if (showCamera) checkPermissions();
|
|
63
58
|
}, [showCamera, refreshCamera]);
|
|
64
59
|
|
|
65
60
|
useEffect(() => {
|
|
@@ -71,26 +66,7 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
71
66
|
return () => subscription.remove();
|
|
72
67
|
}, [showCamera, hasPermission]);
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
let timer: ReturnType<typeof setTimeout>;
|
|
77
|
-
|
|
78
|
-
if (hasPermission && device && showCamera && !isInitialized) {
|
|
79
|
-
timer = setTimeout(() => setShowInitTimeout(true), 3000);
|
|
80
|
-
} else {
|
|
81
|
-
setShowInitTimeout(false);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return () => {
|
|
85
|
-
if (timer) clearTimeout(timer);
|
|
86
|
-
};
|
|
87
|
-
}, [hasPermission, device, showCamera, isInitialized]);
|
|
88
|
-
|
|
89
|
-
const onInitialized = useCallback(() => {
|
|
90
|
-
setIsInitialized(true);
|
|
91
|
-
setShowInitTimeout(false);
|
|
92
|
-
}, []);
|
|
93
|
-
|
|
69
|
+
const onInitialized = useCallback(() => setIsInitialized(true), []);
|
|
94
70
|
const onCameraError = useCallback((error: any) => {
|
|
95
71
|
onError?.({ message: error.message || t('camera.errorOccurred') });
|
|
96
72
|
}, [onError, t]);
|
|
@@ -100,27 +76,30 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
100
76
|
if (silentCaptureResult?.isAnalyzing) return;
|
|
101
77
|
|
|
102
78
|
try {
|
|
103
|
-
isCapturingRef.current = true;
|
|
79
|
+
isCapturingRef.current = true;
|
|
104
80
|
const photo = await camera.current.takePhoto({
|
|
105
|
-
enableShutterSound: false,
|
|
106
|
-
flash: 'off',
|
|
81
|
+
enableShutterSound: false,
|
|
82
|
+
flash: 'off',
|
|
107
83
|
});
|
|
108
|
-
const result = await VisionCameraModule.processPhotoResult(photo);
|
|
109
84
|
|
|
85
|
+
const result = await VisionCameraModule.processPhotoResult(photo);
|
|
86
|
+
|
|
110
87
|
onSilentCapture?.({
|
|
111
|
-
|
|
112
|
-
|
|
88
|
+
...result,
|
|
89
|
+
path: result.path || photo.path,
|
|
113
90
|
});
|
|
91
|
+
|
|
114
92
|
} catch (error) {
|
|
115
93
|
// Silent background fail
|
|
116
94
|
} finally {
|
|
117
|
-
isCapturingRef.current = false;
|
|
95
|
+
isCapturingRef.current = false;
|
|
118
96
|
}
|
|
119
97
|
}, [isInitialized, onSilentCapture, silentCaptureResult]);
|
|
120
98
|
|
|
99
|
+
// 🚨 BUG FIX: The Warm-up Timer (Fixes Blurry Images)
|
|
121
100
|
useEffect(() => {
|
|
122
101
|
if (!showCamera || !isInitialized || isProcessing) return;
|
|
123
|
-
|
|
102
|
+
|
|
124
103
|
let isActive = true;
|
|
125
104
|
let intervalId: ReturnType<typeof setInterval>;
|
|
126
105
|
|
|
@@ -128,9 +107,9 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
128
107
|
if (!isActive) return;
|
|
129
108
|
intervalId = setInterval(() => {
|
|
130
109
|
captureSilentPhoto();
|
|
131
|
-
}, 1500);
|
|
132
|
-
}, 1000);
|
|
133
|
-
|
|
110
|
+
}, 1500); // 1.5s gives the hardware more time to stabilize between shots
|
|
111
|
+
}, 1000);
|
|
112
|
+
|
|
134
113
|
return () => {
|
|
135
114
|
isActive = false;
|
|
136
115
|
clearTimeout(warmupTimer);
|
|
@@ -139,75 +118,40 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
139
118
|
}, [showCamera, isInitialized, isProcessing, captureSilentPhoto]);
|
|
140
119
|
|
|
141
120
|
// --- RENDERERS ---
|
|
142
|
-
|
|
121
|
+
|
|
122
|
+
// 🚨 BUG FIX: Show nothing while checking permissions (Stops flicker)
|
|
143
123
|
if (hasPermission === null) {
|
|
144
124
|
return <View style={[styles.container, style]} />;
|
|
145
125
|
}
|
|
146
126
|
|
|
147
127
|
if (hasPermission === false) {
|
|
148
128
|
return (
|
|
149
|
-
<View style={[styles.container, style,
|
|
129
|
+
<View style={[styles.container, style, { justifyContent: 'center', alignItems: 'center' }]}>
|
|
150
130
|
<Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>
|
|
151
|
-
<Button
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
131
|
+
<Button
|
|
132
|
+
title="Refresh Camera"
|
|
133
|
+
onPress={() => setRefreshCamera(prev => !prev)}
|
|
134
|
+
variant="primary"
|
|
155
135
|
/>
|
|
156
136
|
</View>
|
|
157
137
|
);
|
|
158
138
|
}
|
|
159
139
|
|
|
160
|
-
|
|
161
|
-
// Give them a UI and a way out.
|
|
162
|
-
if (!device || !showCamera) {
|
|
163
|
-
return (
|
|
164
|
-
<View style={[styles.container, style, styles.centerContent]}>
|
|
165
|
-
<ActivityIndicator size="large" color="#2DBD60" style={{ marginBottom: 16 }} />
|
|
166
|
-
<Text style={styles.permissionMessage}>Loading Camera Hardware...</Text>
|
|
167
|
-
<Button
|
|
168
|
-
title="Refresh"
|
|
169
|
-
onPress={() => setRefreshCamera(prev => !prev)}
|
|
170
|
-
variant="outline"
|
|
171
|
-
/>
|
|
172
|
-
</View>
|
|
173
|
-
);
|
|
174
|
-
}
|
|
140
|
+
if (!device || !showCamera) return <View style={[styles.container, style]} />;
|
|
175
141
|
|
|
176
142
|
return (
|
|
177
143
|
<View style={[styles.container, style]}>
|
|
178
144
|
<Camera
|
|
179
|
-
// 🚨 FIX: This key forces the native component to completely remount
|
|
180
|
-
// when permissions are granted or when manually refreshed.
|
|
181
|
-
key={`cam-${hasPermission}-${refreshCamera ? '1' : '0'}`}
|
|
182
145
|
ref={camera}
|
|
183
146
|
style={StyleSheet.absoluteFill}
|
|
184
147
|
device={device}
|
|
185
148
|
isActive={showCamera && !isProcessing}
|
|
186
149
|
photo={true}
|
|
187
|
-
video={false}
|
|
188
|
-
audio={false}
|
|
150
|
+
video={false}
|
|
151
|
+
audio={false}
|
|
189
152
|
onInitialized={onInitialized}
|
|
190
153
|
onError={onCameraError}
|
|
191
154
|
/>
|
|
192
|
-
|
|
193
|
-
{/* 🚨 FIX: Floating Refresh Button if the hardware freezes on a black screen */}
|
|
194
|
-
{!isInitialized && showInitTimeout && (
|
|
195
|
-
<View style={styles.stuckOverlay}>
|
|
196
|
-
<ActivityIndicator size="large" color="#2DBD60" style={{ marginBottom: 16 }} />
|
|
197
|
-
<Text style={{ color: 'white', marginBottom: 16, fontWeight: 'bold' }}>
|
|
198
|
-
Camera is taking a while to start...
|
|
199
|
-
</Text>
|
|
200
|
-
<Button
|
|
201
|
-
title="Restart Camera"
|
|
202
|
-
onPress={() => {
|
|
203
|
-
setIsInitialized(false);
|
|
204
|
-
setRefreshCamera(prev => !prev);
|
|
205
|
-
}}
|
|
206
|
-
variant="primary"
|
|
207
|
-
/>
|
|
208
|
-
</View>
|
|
209
|
-
)}
|
|
210
|
-
|
|
211
155
|
{overlayComponent}
|
|
212
156
|
</View>
|
|
213
157
|
);
|
|
@@ -215,14 +159,5 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
215
159
|
|
|
216
160
|
const styles = StyleSheet.create({
|
|
217
161
|
container: { flex: 1, backgroundColor: 'black' },
|
|
218
|
-
|
|
219
|
-
permissionMessage: { color: 'white', textAlign: 'center', marginBottom: 20, fontSize: 16, fontWeight: '600' },
|
|
220
|
-
stuckOverlay: {
|
|
221
|
-
...StyleSheet.absoluteFillObject,
|
|
222
|
-
justifyContent: 'center',
|
|
223
|
-
alignItems: 'center',
|
|
224
|
-
backgroundColor: 'rgba(0,0,0,0.85)',
|
|
225
|
-
zIndex: 1000,
|
|
226
|
-
padding: 20
|
|
227
|
-
}
|
|
162
|
+
permissionMessage: { color: 'white', textAlign: 'center', margin: 20, fontSize: 16 },
|
|
228
163
|
});
|
|
@@ -38,9 +38,6 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
38
38
|
const [otp, setOtp] = useState('');
|
|
39
39
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
40
40
|
const [isSimulating, setIsSimulating] = useState(false);
|
|
41
|
-
|
|
42
|
-
// 🚨 NEW: Track actual focus state for visual feedback
|
|
43
|
-
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
44
41
|
|
|
45
42
|
const inputRef = useRef<TextInput>(null);
|
|
46
43
|
|
|
@@ -59,22 +56,6 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
59
56
|
}
|
|
60
57
|
}, [otp]);
|
|
61
58
|
|
|
62
|
-
// --- AUTO FOCUS LOGIC ---
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
let focusTimer: ReturnType<typeof setTimeout>;
|
|
65
|
-
|
|
66
|
-
// Only attempt focus when we are on the OTP step AND the loading state is completely finished
|
|
67
|
-
if (step === 'otp' && !isSimulating) {
|
|
68
|
-
focusTimer = setTimeout(() => {
|
|
69
|
-
inputRef.current?.focus();
|
|
70
|
-
}, 100);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return () => {
|
|
74
|
-
if (focusTimer) clearTimeout(focusTimer);
|
|
75
|
-
};
|
|
76
|
-
}, [step, isSimulating]);
|
|
77
|
-
|
|
78
59
|
const handleSendCode = async () => {
|
|
79
60
|
const trimmed = email.trim();
|
|
80
61
|
if (!trimmed || !isValidEmail(trimmed)) {
|
|
@@ -88,7 +69,8 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
88
69
|
try {
|
|
89
70
|
await kycService.sendEmailVerificationCode(trimmed, auth);
|
|
90
71
|
setStep('otp');
|
|
91
|
-
//
|
|
72
|
+
// Auto-focus the OTP input shortly after switching steps
|
|
73
|
+
setTimeout(() => inputRef.current?.focus(), 100);
|
|
92
74
|
} catch (err: any) {
|
|
93
75
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send verification code');
|
|
94
76
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -114,8 +96,8 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
114
96
|
} catch (err: any) {
|
|
115
97
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.wrongCode') || 'Invalid verification code');
|
|
116
98
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
117
|
-
setOtp('');
|
|
118
|
-
inputRef.current?.focus();
|
|
99
|
+
setOtp(''); // Clear the boxes on error so they can type again
|
|
100
|
+
inputRef.current?.focus();
|
|
119
101
|
} finally {
|
|
120
102
|
setIsSimulating(false);
|
|
121
103
|
}
|
|
@@ -145,19 +127,16 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
145
127
|
<Pressable style={styles.otpBoxesContainer} onPress={() => inputRef.current?.focus()}>
|
|
146
128
|
{boxes.map((_, index) => {
|
|
147
129
|
const digit = otp[index] || '';
|
|
130
|
+
const isCurrent = index === otp.length;
|
|
148
131
|
const isFilled = index < otp.length;
|
|
149
|
-
|
|
150
|
-
// 🚨 NEW: Only highlight if the input is ACTUALLY focused.
|
|
151
|
-
const isActiveIndex = index === otp.length || (index === CODE_LENGTH - 1 && otp.length === CODE_LENGTH);
|
|
152
|
-
const isCurrent = isInputFocused && isActiveIndex;
|
|
153
132
|
|
|
154
133
|
return (
|
|
155
134
|
<View
|
|
156
135
|
key={index}
|
|
157
136
|
style={[
|
|
158
137
|
styles.otpBox,
|
|
159
|
-
|
|
160
|
-
|
|
138
|
+
isCurrent && styles.otpBoxActive,
|
|
139
|
+
isFilled && styles.otpBoxFilled
|
|
161
140
|
]}
|
|
162
141
|
>
|
|
163
142
|
<Text style={styles.otpBoxText}>{digit}</Text>
|
|
@@ -196,7 +175,7 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
196
175
|
|
|
197
176
|
<View style={styles.otpWrapper}>
|
|
198
177
|
{renderOtpBoxes()}
|
|
199
|
-
|
|
178
|
+
{/* Hidden TextInput overlaid to handle native keyboard & pasting seamlessly */}
|
|
200
179
|
<TextInput
|
|
201
180
|
ref={inputRef}
|
|
202
181
|
style={styles.hiddenInput}
|
|
@@ -207,9 +186,6 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
207
186
|
editable={!isSimulating}
|
|
208
187
|
textContentType="oneTimeCode"
|
|
209
188
|
caretHidden={true}
|
|
210
|
-
// 🚨 NEW: Track focus state
|
|
211
|
-
onFocus={() => setIsInputFocused(true)}
|
|
212
|
-
onBlur={() => setIsInputFocused(false)}
|
|
213
189
|
/>
|
|
214
190
|
</View>
|
|
215
191
|
|
|
@@ -245,8 +221,6 @@ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps>
|
|
|
245
221
|
t('common.codeResent') || 'Code Resent',
|
|
246
222
|
t('common.codeResentMessage', { email }) || 'Code resent to ' + email
|
|
247
223
|
);
|
|
248
|
-
// Refocus after resending
|
|
249
|
-
inputRef.current?.focus();
|
|
250
224
|
} catch (err: any) {
|
|
251
225
|
const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send code');
|
|
252
226
|
setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
@@ -326,7 +300,7 @@ const styles = StyleSheet.create({
|
|
|
326
300
|
},
|
|
327
301
|
otpBox: {
|
|
328
302
|
width: '14%',
|
|
329
|
-
aspectRatio: 1,
|
|
303
|
+
aspectRatio: 1,
|
|
330
304
|
borderWidth: 1,
|
|
331
305
|
borderColor: '#e0e0e0',
|
|
332
306
|
borderRadius: 12,
|
|
@@ -354,7 +328,7 @@ const styles = StyleSheet.create({
|
|
|
354
328
|
left: 0,
|
|
355
329
|
width: '100%',
|
|
356
330
|
height: '100%',
|
|
357
|
-
opacity: 0,
|
|
331
|
+
opacity: 0,
|
|
358
332
|
},
|
|
359
333
|
errorText: {
|
|
360
334
|
color: '#dc2626',
|
|
@@ -270,7 +270,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
|
|
|
270
270
|
return (
|
|
271
271
|
<View style={styles.cameraContainer}>
|
|
272
272
|
<EnhancedCameraView
|
|
273
|
-
key={currentSide}
|
|
273
|
+
key={currentSide} // 🚨 BUG FIX: Forces the camera instance to completely reset when switching sides
|
|
274
274
|
showCamera={true}
|
|
275
275
|
isProcessing={isBusy}
|
|
276
276
|
cameraType={cameraConfig.cameraType}
|