@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.
Files changed (96) hide show
  1. package/README.md +4 -4
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/AndroidManifest.xml +1 -1
  4. package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkModule.kt +6 -6
  5. package/android/src/main/java/kyc/transfergratis/com/TransfergratisSdkView.kt +2 -2
  6. package/build/package.json +5 -5
  7. package/build/src/App.d.ts +2 -2
  8. package/build/src/App.d.ts.map +1 -1
  9. package/build/src/App.js +2 -2
  10. package/build/src/App.js.map +1 -1
  11. package/build/src/{SanctumKeySdk.types.d.ts → TransfergratisSdk.types.d.ts} +3 -3
  12. package/build/src/TransfergratisSdk.types.d.ts.map +1 -0
  13. package/build/src/TransfergratisSdk.types.js +2 -0
  14. package/build/src/TransfergratisSdk.types.js.map +1 -0
  15. package/build/src/{SanctumKeySdkModule.d.ts → TransfergratisSdkModule.d.ts} +4 -4
  16. package/build/src/TransfergratisSdkModule.d.ts.map +1 -0
  17. package/build/src/{SanctumKeySdkModule.js → TransfergratisSdkModule.js} +2 -2
  18. package/build/src/TransfergratisSdkModule.js.map +1 -0
  19. package/build/src/{SanctumKeySdkModule.web.d.ts → TransfergratisSdkModule.web.d.ts} +4 -4
  20. package/build/src/TransfergratisSdkModule.web.d.ts.map +1 -0
  21. package/build/src/{SanctumKeySdkModule.web.js → TransfergratisSdkModule.web.js} +3 -3
  22. package/build/src/TransfergratisSdkModule.web.js.map +1 -0
  23. package/build/src/TransfergratisSdkView.d.ts +4 -0
  24. package/build/src/TransfergratisSdkView.d.ts.map +1 -0
  25. package/build/src/TransfergratisSdkView.js +7 -0
  26. package/build/src/TransfergratisSdkView.js.map +1 -0
  27. package/build/src/TransfergratisSdkView.web.d.ts +4 -0
  28. package/build/src/TransfergratisSdkView.web.d.ts.map +1 -0
  29. package/build/src/{SanctumKeySdkView.web.js → TransfergratisSdkView.web.js} +2 -2
  30. package/build/src/TransfergratisSdkView.web.js.map +1 -0
  31. package/build/src/api/axios.js +2 -2
  32. package/build/src/api/axios.js.map +1 -1
  33. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  34. package/build/src/components/EnhancedCameraView.js +12 -61
  35. package/build/src/components/EnhancedCameraView.js.map +1 -1
  36. package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
  37. package/build/src/components/KYCElements/EmailVerificationTemplate.js +11 -32
  38. package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
  39. package/build/src/components/KYCElements/IDCardCapture.js +2 -1
  40. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  41. package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
  42. package/build/src/components/KYCElements/PhoneVerificationTemplate.js +22 -163
  43. package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
  44. package/build/src/components/NativeCameraView.js +1 -1
  45. package/build/src/components/NativeCameraView.js.map +1 -1
  46. package/build/src/config/KYCConfig.js +1 -1
  47. package/build/src/config/KYCConfig.js.map +1 -1
  48. package/build/src/config/allowedDomains.js +6 -6
  49. package/build/src/config/allowedDomains.js.map +1 -1
  50. package/build/src/index.d.ts +3 -3
  51. package/build/src/index.d.ts.map +1 -1
  52. package/build/src/index.js +3 -3
  53. package/build/src/index.js.map +1 -1
  54. package/build/src/modules/api/KYCService.d.ts +3 -3
  55. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  56. package/build/src/modules/api/KYCService.js +25 -32
  57. package/build/src/modules/api/KYCService.js.map +1 -1
  58. package/build/src/modules/camera/NativeCameraModule.js +17 -17
  59. package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
  60. package/expo-module.config.json +2 -2
  61. package/ios/TransfergratisSdk.podspec +2 -2
  62. package/ios/TransfergratisSdkModule.swift +12 -12
  63. package/package.json +5 -5
  64. package/src/App.tsx +2 -2
  65. package/src/{SanctumKeySdk.types.ts → TransfergratisSdk.types.ts} +2 -2
  66. package/src/{SanctumKeySdkModule.ts → TransfergratisSdkModule.ts} +3 -3
  67. package/src/{SanctumKeySdkModule.web.ts → TransfergratisSdkModule.web.ts} +3 -3
  68. package/src/TransfergratisSdkView.tsx +11 -0
  69. package/src/{SanctumKeySdkView.web.tsx → TransfergratisSdkView.web.tsx} +2 -2
  70. package/src/api/axios.ts +2 -2
  71. package/src/components/EnhancedCameraView.tsx +34 -99
  72. package/src/components/KYCElements/EmailVerificationTemplate.tsx +10 -36
  73. package/src/components/KYCElements/IDCardCapture.tsx +1 -1
  74. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +30 -204
  75. package/src/components/NativeCameraView.tsx +1 -1
  76. package/src/config/KYCConfig.ts +1 -1
  77. package/src/config/allowedDomains.ts +6 -6
  78. package/src/i18n/README.md +1 -1
  79. package/src/index.ts +3 -3
  80. package/src/modules/api/KYCService.ts +26 -37
  81. package/src/modules/camera/NativeCameraModule.ts +17 -17
  82. package/build/src/SanctumKeySdk.types.d.ts.map +0 -1
  83. package/build/src/SanctumKeySdk.types.js +0 -2
  84. package/build/src/SanctumKeySdk.types.js.map +0 -1
  85. package/build/src/SanctumKeySdkModule.d.ts.map +0 -1
  86. package/build/src/SanctumKeySdkModule.js.map +0 -1
  87. package/build/src/SanctumKeySdkModule.web.d.ts.map +0 -1
  88. package/build/src/SanctumKeySdkModule.web.js.map +0 -1
  89. package/build/src/SanctumKeySdkView.d.ts +0 -4
  90. package/build/src/SanctumKeySdkView.d.ts.map +0 -1
  91. package/build/src/SanctumKeySdkView.js +0 -7
  92. package/build/src/SanctumKeySdkView.js.map +0 -1
  93. package/build/src/SanctumKeySdkView.web.d.ts +0 -4
  94. package/build/src/SanctumKeySdkView.web.d.ts.map +0 -1
  95. package/build/src/SanctumKeySdkView.web.js.map +0 -1
  96. 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 SanctumKeySdkView: ExpoView {
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("🟦 [SanctumKeySdkView] \(message)")
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("🟦 [SanctumKeySdkView] Init appelé - Vue créée")
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: SanctumKeySdkModule?
183
+ weak var module: TransfergratisSdkModule?
184
184
 
185
- init(module: SanctumKeySdkModule) {
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: SanctumKeySdkModule?
231
+ weak var module: TransfergratisSdkModule?
232
232
 
233
- init(module: SanctumKeySdkModule) {
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 SanctumKeySdkModule: Module {
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("SanctumKeySdk")
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(SanctumKeySdkView.self) {
368
- Prop("instructions") { (view: SanctumKeySdkView, instructions: String) in
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: SanctumKeySdkView, show: Bool) in
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.8",
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/SanctumKeyOrg/KYC-User-Frontend",
28
+ "repository": "https://github.com/TransfergratisOrg/KYC-User-Frontend",
29
29
  "bugs": {
30
- "url": "https://github.com/SanctumKeyOrg/KYC-User-Frontend/issues"
30
+ "url": "https://github.com/TransfergratisOrg/KYC-User-Frontend/issues"
31
31
  },
32
- "author": "mesha-SanctumKey <loic.lontchi@SanctumKey.com> (https://github.com/mesha-SanctumKey)",
32
+ "author": "mesha-transfergratis <loic.lontchi@transfergratis.com> (https://github.com/mesha-transfergratis)",
33
33
  "license": "MIT",
34
- "homepage": "https://github.com/SanctumKeyOrg/KYC-User-Frontend#readme",
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 LaunchSanctumKeyKYC = () =>{
5
+ const LaunchTransferGratisKYC = () =>{
6
6
  return <CameraCaptureEx/>
7
7
  }
8
8
 
9
- export default LaunchSanctumKeyKYC;
9
+ export default LaunchTransferGratisKYC;
@@ -15,13 +15,13 @@ export type ErrorEventPayload = {
15
15
  message: string;
16
16
  };
17
17
 
18
- export type SanctumKeySdkModuleEvents = {
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 SanctumKeySdkViewProps = {
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 { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';
5
+ import { TransfergratisSdkModuleEvents } from './TransfergratisSdk.types';
6
6
 
7
- declare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {
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<SanctumKeySdkModule>('SanctumKeySdk');
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 { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';
4
+ import { TransfergratisSdkModuleEvents } from './TransfergratisSdk.types';
5
5
 
6
- class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {
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(SanctumKeySdkModule, 'SanctumKeySdkModule');
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 { SanctumKeySdkViewProps } from './SanctumKeySdk.types';
3
+ import { TransfergratisSdkViewProps } from './TransfergratisSdk.types';
4
4
 
5
- export default function SanctumKeySdkView(props: SanctumKeySdkViewProps) {
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.SanctumKey.com',
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.SanctumKey.com',
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, ActivityIndicator } from 'react-native';
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
- ...result,
112
- path: result.path || photo.path,
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, styles.centerContent]}>
129
+ <View style={[styles.container, style, { justifyContent: 'center', alignItems: 'center' }]}>
150
130
  <Text style={styles.permissionMessage}>{t('camera.permissionRequired')}</Text>
151
- <Button
152
- title="Refresh Camera"
153
- onPress={() => setRefreshCamera(prev => !prev)}
154
- variant="primary"
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
- // 🚨 FIX: Don't return an invisible blank screen if the device is loading!
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
- centerContent: { justifyContent: 'center', alignItems: 'center', padding: 20 },
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
- // Note: Removed the buggy setTimeout from here. The useEffect handles it now!
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(); // Refocus on error
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
- isFilled && styles.otpBoxFilled,
160
- isCurrent && styles.otpBoxActive // Active overrides filled
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, // keeps it perfectly square
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, // completely invisible but handles native keyboard interactions perfectly
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}