@sanctum-key/react-native-sdk 1.0.10 → 1.0.12

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 (166) hide show
  1. package/README.md +3 -3
  2. package/android/build/.transforms/{c9d62bb333688ab562f51958998d5a48 → 9e34a0354bf8964d60c4c1392f5aa5b2}/transformed/classes/classes_dex/classes.dex +0 -0
  3. package/android/build/generated/source/buildConfig/debug/kyc/{SanctumKey → sanctumkey}/com/BuildConfig.java +2 -2
  4. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +1 -1
  5. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +1 -1
  6. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  7. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  8. package/android/build/intermediates/compiled_local_resources/debug/compileDebugLibraryResources/out/xml_file_paths.xml.flat +0 -0
  9. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +2 -2
  10. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +1 -1
  11. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +1 -1
  12. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +1 -1
  13. package/android/build/intermediates/incremental/packageDebugAssets/merger.xml +1 -1
  14. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/kyc/{SanctumKey → sanctumkey}/com/BuildConfig.class +0 -0
  15. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +42 -42
  16. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +1 -1
  17. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  18. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -1
  19. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  20. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream +0 -0
  21. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len +0 -0
  22. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  23. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i +0 -0
  24. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  56. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  57. package/android/build/outputs/logs/manifest-merger-debug-report.txt +52 -52
  58. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$1$5$1.class +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$1$5$2.class +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$1.class +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$2.class +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$3.class +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$4.class +0 -0
  65. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$5.class +0 -0
  66. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$6.class +0 -0
  67. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$1.class +0 -0
  68. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$2.class +0 -0
  69. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$3.class +0 -0
  70. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$4.class +0 -0
  71. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$$inlined$View$1.class +0 -0
  72. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$1.class +0 -0
  73. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$2.class +0 -0
  74. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkModule.class +0 -0
  75. package/android/build/tmp/kotlin-classes/debug/kyc/{SanctumKey → sanctumkey}/com/SanctumKeySdkView.class +0 -0
  76. package/android/build.gradle +2 -2
  77. package/android/src/main/AndroidManifest.xml +1 -1
  78. package/android/src/main/java/kyc/{transfergratis/com/TransfergratisSdkModule.kt → sanctumkey/com/SanctumKeySdkModule.kt} +21 -18
  79. package/android/src/main/java/kyc/{transfergratis/com/TransfergratisSdkView.kt → sanctumkey/com/SanctumKeySdkView.kt} +2 -2
  80. package/build/package.json +5 -5
  81. package/build/src/App.d.ts +2 -2
  82. package/build/src/App.d.ts.map +1 -1
  83. package/build/src/App.js +2 -2
  84. package/build/src/App.js.map +1 -1
  85. package/build/src/{TransfergratisSdk.types.d.ts → SanctumKeySdk.types.d.ts} +3 -3
  86. package/build/src/SanctumKeySdk.types.d.ts.map +1 -0
  87. package/build/src/SanctumKeySdk.types.js +2 -0
  88. package/build/src/SanctumKeySdk.types.js.map +1 -0
  89. package/build/src/{TransfergratisSdkModule.d.ts → SanctumKeySdkModule.d.ts} +4 -4
  90. package/build/src/SanctumKeySdkModule.d.ts.map +1 -0
  91. package/build/src/{TransfergratisSdkModule.js → SanctumKeySdkModule.js} +2 -2
  92. package/build/src/SanctumKeySdkModule.js.map +1 -0
  93. package/build/src/{TransfergratisSdkModule.web.d.ts → SanctumKeySdkModule.web.d.ts} +4 -4
  94. package/build/src/SanctumKeySdkModule.web.d.ts.map +1 -0
  95. package/build/src/{TransfergratisSdkModule.web.js → SanctumKeySdkModule.web.js} +3 -3
  96. package/build/src/SanctumKeySdkModule.web.js.map +1 -0
  97. package/build/src/SanctumKeySdkView.d.ts +4 -0
  98. package/build/src/SanctumKeySdkView.d.ts.map +1 -0
  99. package/build/src/SanctumKeySdkView.js +7 -0
  100. package/build/src/SanctumKeySdkView.js.map +1 -0
  101. package/build/src/SanctumKeySdkView.web.d.ts +4 -0
  102. package/build/src/SanctumKeySdkView.web.d.ts.map +1 -0
  103. package/build/src/{TransfergratisSdkView.web.js → SanctumKeySdkView.web.js} +2 -2
  104. package/build/src/SanctumKeySdkView.web.js.map +1 -0
  105. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  106. package/build/src/components/KYCElements/IDCardCapture.js +132 -175
  107. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  108. package/build/src/components/NativeCameraView.js +1 -1
  109. package/build/src/components/NativeCameraView.js.map +1 -1
  110. package/build/src/components/TemplateKYCExample.d.ts +4 -3
  111. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  112. package/build/src/components/TemplateKYCExample.js +2 -2
  113. package/build/src/components/TemplateKYCExample.js.map +1 -1
  114. package/build/src/config/allowedDomains.js +6 -6
  115. package/build/src/config/allowedDomains.js.map +1 -1
  116. package/build/src/index.d.ts +3 -3
  117. package/build/src/index.d.ts.map +1 -1
  118. package/build/src/index.js +3 -3
  119. package/build/src/index.js.map +1 -1
  120. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  121. package/build/src/modules/api/KYCService.js +9 -4
  122. package/build/src/modules/api/KYCService.js.map +1 -1
  123. package/build/src/modules/camera/NativeCameraModule.js +17 -17
  124. package/build/src/modules/camera/NativeCameraModule.js.map +1 -1
  125. package/build/src/web/WebKYCEntry.d.ts +2 -2
  126. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  127. package/build/src/web/WebKYCEntry.js +3 -2
  128. package/build/src/web/WebKYCEntry.js.map +1 -1
  129. package/expo-module.config.json +3 -3
  130. package/ios/TransfergratisSdk.podspec +2 -2
  131. package/ios/TransfergratisSdkModule.swift +12 -12
  132. package/package.json +5 -5
  133. package/src/App.tsx +2 -2
  134. package/src/{TransfergratisSdk.types.ts → SanctumKeySdk.types.ts} +2 -2
  135. package/src/{TransfergratisSdkModule.ts → SanctumKeySdkModule.ts} +3 -3
  136. package/src/{TransfergratisSdkModule.web.ts → SanctumKeySdkModule.web.ts} +3 -3
  137. package/src/SanctumKeySdkView.tsx +11 -0
  138. package/src/{TransfergratisSdkView.web.tsx → SanctumKeySdkView.web.tsx} +2 -2
  139. package/src/components/KYCElements/IDCardCapture.tsx +227 -286
  140. package/src/components/NativeCameraView.tsx +1 -1
  141. package/src/components/TemplateKYCExample.tsx +23 -4
  142. package/src/config/allowedDomains.ts +6 -6
  143. package/src/i18n/README.md +1 -1
  144. package/src/index.ts +3 -3
  145. package/src/modules/api/KYCService.ts +10 -4
  146. package/src/modules/camera/NativeCameraModule.ts +17 -17
  147. package/src/web/WebKYCEntry.tsx +3 -3
  148. package/android/build/.transforms/ab90740579f5bd05b27b4343ada2d1c9/transformed/classes/classes_dex/classes.dex +0 -0
  149. package/android/build/.transforms/c9d62bb333688ab562f51958998d5a48/results.bin +0 -1
  150. package/android/build/tmp/compileDebugJavaWithJavac/compileTransaction/stash-dir/BuildConfig.class.uniqueId0 +0 -0
  151. package/build/src/TransfergratisSdk.types.d.ts.map +0 -1
  152. package/build/src/TransfergratisSdk.types.js +0 -2
  153. package/build/src/TransfergratisSdk.types.js.map +0 -1
  154. package/build/src/TransfergratisSdkModule.d.ts.map +0 -1
  155. package/build/src/TransfergratisSdkModule.js.map +0 -1
  156. package/build/src/TransfergratisSdkModule.web.d.ts.map +0 -1
  157. package/build/src/TransfergratisSdkModule.web.js.map +0 -1
  158. package/build/src/TransfergratisSdkView.d.ts +0 -4
  159. package/build/src/TransfergratisSdkView.d.ts.map +0 -1
  160. package/build/src/TransfergratisSdkView.js +0 -7
  161. package/build/src/TransfergratisSdkView.js.map +0 -1
  162. package/build/src/TransfergratisSdkView.web.d.ts +0 -4
  163. package/build/src/TransfergratisSdkView.web.d.ts.map +0 -1
  164. package/build/src/TransfergratisSdkView.web.js.map +0 -1
  165. package/src/TransfergratisSdkView.tsx +0 -11
  166. /package/android/build/.transforms/{ab90740579f5bd05b27b4343ada2d1c9 → 9e34a0354bf8964d60c4c1392f5aa5b2}/results.bin +0 -0
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
- import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator } from 'react-native';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity } from 'react-native'; // 🚨 Added TouchableOpacity
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';
@@ -14,22 +14,10 @@ import pathToBase64 from '../../utils/pathToBase64';
14
14
  import { cropByObb, cropImageWithBBoxWithTolerance, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../utils/cropByObb';
15
15
  import REGION_MAPPING from '../../config/region_mapping.json';
16
16
 
17
- // 🌍 Map ISO codes to exact JSON root keys to ensure perfect dictionary lookups
18
- const ISO_TO_COUNTRY_NAME: Record<string, string> = {
19
- 'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada',
20
- 'FR': 'France', 'GH': 'Ghana', 'ZA': 'South Africa', 'GB': 'Britain',
21
- 'CI': 'Ivory Coast', 'SN': 'Senegal', 'TG': 'Togo', 'ML': 'Mali'
22
- };
17
+ const ISO_TO_COUNTRY_NAME: Record<string, string> = { 'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada', 'FR': 'France', 'GH': 'Ghana', 'ZA': 'South Africa', 'GB': 'Britain', 'CI': 'Ivory Coast', 'SN': 'Senegal', 'TG': 'Togo', 'ML': 'Mali' };
23
18
 
24
19
  interface IIDCardPayload { dir: string; file: string; mrz: string; templatePath?: string; }
25
- interface IDCardCaptureProps {
26
- component: TemplateComponent;
27
- value?: Record<string, IIDCardPayload>;
28
- onValueChange: (value: Record<string, IIDCardPayload | string>) => void;
29
- error?: string;
30
- language?: string;
31
- currentSide?: string;
32
- }
20
+ interface IDCardCaptureProps { component: TemplateComponent; value?: Record<string, IIDCardPayload>; onValueChange: (value: Record<string, IIDCardPayload | string>) => void; error?: string; language?: string; currentSide?: string; }
33
21
 
34
22
  export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value = {}, onValueChange, error, language = 'en' }) => {
35
23
  const { t, locale } = useI18n();
@@ -40,12 +28,11 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
40
28
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
41
29
  const [isProcessingCapture, setIsProcessingCapture] = useState(false);
42
30
  const [processingImagePath, setProcessingImagePath] = useState<string | null>(null);
31
+
32
+ // 🚨 ADDED: Key to force camera re-mount
33
+ const [cameraKey, setCameraKey] = useState(0);
43
34
 
44
- const documentTypeMapping: Record<string, GovernmentDocumentType> = {
45
- 'nationalId': 'national_id', 'passport': 'passport', 'driversLicense': 'drivers_licence',
46
- 'residencePermit': 'permanent_residence', 'healthInsuranceCard': 'health_insurance_card',
47
- };
48
-
35
+ const documentTypeMapping: Record<string, GovernmentDocumentType> = { 'nationalId': 'national_id', 'passport': 'passport', 'driversLicense': 'drivers_licence', 'residencePermit': 'permanent_residence', 'healthInsuranceCard': 'health_insurance_card', };
49
36
  const { actions, state, env } = useTemplateKYCFlowContext();
50
37
 
51
38
  const getLocalizedText = (text: LocalizedText | Record<string, LocalizedText>): string => {
@@ -67,21 +54,28 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
67
54
 
68
55
  const countryData = useMemo(() => countrySelectionData, [countrySelectionData]);
69
56
 
57
+ const [isRebootingCamera, setIsRebootingCamera] = useState(false);
58
+
59
+ const refreshCamera = () => {
60
+ setIsRebootingCamera(true);
61
+
62
+ setTimeout(() => {
63
+ setCameraKey(prev => prev + 1);
64
+ setIsRebootingCamera(false);
65
+ }, 500);
66
+ };
67
+
68
+
69
+
70
70
  useEffect(() => {
71
71
  if (value && Object.keys(value).length > 0) {
72
72
  if (JSON.stringify(value) !== JSON.stringify(capturedImages)) {
73
73
  const updatedImages = value as Record<string, IIDCardPayload>;
74
74
  setCapturedImages(updatedImages);
75
-
76
75
  const currentImageData = updatedImages[currentSide];
77
76
  if (currentImageData?.dir) {
78
77
  setSilentCaptureResult(prev => ({
79
- ...prev,
80
- path: currentImageData.dir,
81
- success: true,
82
- isAnalyzing: false,
83
- mrz: currentImageData.mrz || '',
84
- templatePath: currentImageData.templatePath || '',
78
+ ...prev, path: currentImageData.dir, success: true, isAnalyzing: false, mrz: currentImageData.mrz || '', templatePath: currentImageData.templatePath || '',
85
79
  }));
86
80
  }
87
81
  }
@@ -89,9 +83,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
89
83
  }, [value, currentSide]);
90
84
 
91
85
  const cameraConfig = useMemo(() => {
92
- const instructions = selectedDocumentType
93
- ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
94
- : getLocalizedText(component.instructions as Record<string, LocalizedText>);
86
+ const instructions = selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions as Record<string, LocalizedText>);
95
87
  return {
96
88
  cameraType: 'back' as const, flashMode: 'auto' as const,
97
89
  overlay: { guideText: instructions, bbox: { xMin: 15, yMin: 20, xMax: 85, yMax: 70, borderColor: '#2DBD60', borderWidth: 3, cornerRadius: 8 } }
@@ -109,36 +101,21 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
109
101
  };
110
102
 
111
103
  const getCurrentSideVerification = (currentSide: string, countryKey: string) => {
112
- const rawDocType = countrySelectionData?.documentType;
113
-
114
- if (!rawDocType || !countryKey) {
115
- return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
116
- }
117
-
104
+ const rawDocType = countrySelectionData?.documentType;
105
+ if (!rawDocType || !countryKey) { return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' }; }
118
106
  const rawCountryName = ISO_TO_COUNTRY_NAME[countryData?.code || ''] || countryData?.code || countryKey;
119
107
  const baseMapping = (REGION_MAPPING as any).regionMapping || REGION_MAPPING;
120
-
121
108
  let countryMapping = baseMapping[rawCountryName];
122
-
123
- // Fallback search in case of case mismatches
124
- if (!countryMapping) {
125
- const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase() || k.toLowerCase() === countryKey.toLowerCase());
126
- if (foundKey) countryMapping = baseMapping[foundKey];
127
- }
128
-
129
109
  if (!countryMapping) {
130
- return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
110
+ const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase() || k.toLowerCase() === countryKey.toLowerCase());
111
+ if (foundKey) countryMapping = baseMapping[foundKey];
131
112
  }
132
-
113
+ if (!countryMapping) { return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' }; }
133
114
  const regionMapping = countryMapping[rawDocType];
134
- if (!regionMapping) {
135
- return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
136
- }
137
-
138
- const authMethod: string[] = [];
115
+ if (!regionMapping) { return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' }; }
116
+ const authMethod: string[] = [];
139
117
  const mrzTypes: string[] = [];
140
118
  const key = countrySelectionData.region?.trim()?.length > 0 ? countrySelectionData.region.trim() : 'root';
141
-
142
119
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
143
120
  regionMapping[key].forEach((item: any) => {
144
121
  if (item[currentSide]) {
@@ -147,7 +124,6 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
147
124
  }
148
125
  });
149
126
  }
150
-
151
127
  return { authMethod: removeDuplicates(authMethod), mrzTypes: removeDuplicates(mrzTypes), regionMapping, key };
152
128
  }
153
129
 
@@ -214,16 +190,13 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
214
190
  if (silentCaptureResult.isAnalyzing || isProcessingCapture) return;
215
191
  if (result.success && result.path) {
216
192
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
217
-
218
- // 🚨 Force a template fetch if we haven't successfully saved the current side yet
219
193
  let templatePath = capturedImages[currentSide]?.templatePath || '';
220
194
  let templateBbox: IBbox | undefined;
221
195
  let templateResponse: any;
222
-
223
196
  if (!selectedDocumentType) {
224
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' })); return;
197
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
198
+ return;
225
199
  }
226
-
227
200
  try {
228
201
  if (!templatePath) {
229
202
  const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide }, env);
@@ -241,10 +214,8 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
241
214
  } catch { }
242
215
  }
243
216
  }
244
-
245
217
  const extractedCountryKey = templatePath ? templatePath.split('/')[0] : (ISO_TO_COUNTRY_NAME[countryData?.code || ''] || 'root');
246
218
  const regionMappings = getCurrentSideVerification(currentSide, extractedCountryKey);
247
-
248
219
  let verificationRes: any;
249
220
  if (currentSide === 'front') {
250
221
  const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
@@ -252,36 +223,18 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
252
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);
253
224
  } else {
254
225
  let matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
255
-
256
226
  if (!matchedBackAuthMethod && currentSide === 'back') {
257
- matchedBackAuthMethod = 'MRZ';
227
+ matchedBackAuthMethod = 'MRZ';
258
228
  }
259
-
260
229
  const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
261
-
262
- verificationRes = await backVerification({
263
- path: result.path,
264
- regionMapping: {
265
- authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : regionMappings.authMethod,
266
- mrzTypes: regionMappings.mrzTypes
267
- },
268
- selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type as keyof typeof GovernmentDocumentTypeShorted] || '',
269
- code: countryData?.code || '',
270
- currentSide,
271
- templatePath,
272
- mrzType: backMrzType,
273
- templateResponse
274
- }, env);
230
+ 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);
275
231
  }
276
-
277
232
  const bbox = verificationRes?.bbox || templateBbox;
278
233
  const mrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
279
234
  const verifiedResult: ISilentCaptureResult = { path: result.path, templatePath, bbox, success: true, mrz, isAnalyzing: false, country: countryData?.code, documentType: selectedDocumentType.type };
280
-
281
235
  setSilentCaptureResult(verifiedResult);
282
236
  if (bbox) setBboxBySide(prev => ({ ...prev, [currentSide]: bbox }));
283
237
  await autoCapture(result.path, verifiedResult);
284
-
285
238
  } catch (error: any) {
286
239
  const isCardNotFullyInFrame = error?.message === 'CARD_NOT_FULLY_IN_FRAME' || error?.message?.includes('entirement');
287
240
  const errorMessage = isCardNotFullyInFrame ? t('kyc.idCardCapture.cardNotFullyInFrame') : (error?.message || 'Erreur de détection');
@@ -291,10 +244,14 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
291
244
  };
292
245
 
293
246
  const handleError = (event: { message: string }) => {
294
- showAlert('Erreur', event.message); setShowCamera(false); setIsProcessingCapture(false);
247
+ showAlert('Erreur', event.message);
248
+ setShowCamera(false);
249
+ setIsProcessingCapture(false);
295
250
  };
296
251
 
297
- useEffect(() => { actions.showCustomStepper(!showCamera); }, [showCamera]);
252
+ useEffect(() => {
253
+ actions.showCustomStepper(!showCamera);
254
+ }, [showCamera]);
298
255
 
299
256
  if (!countrySelectionData || !selectedDocumentType) {
300
257
  return (
@@ -309,101 +266,133 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
309
266
  );
310
267
  }
311
268
 
312
- // --- CAMERA RENDER ---
313
- if (showCamera) {
314
- const isBusy = isProcessingCapture;
269
+
270
+ // --- CAMERA RENDER ---
271
+ if (showCamera) {
272
+ const isBusy = isProcessingCapture;
273
+
274
+ if (isRebootingCamera) {
315
275
  return (
316
- <View style={styles.root}>
317
- <View style={styles.cameraWrapper}>
318
-
319
- {/* Web/Desktop Clean Header */}
320
- <View style={styles.headerContainer}>
321
- <Text style={styles.headerTitle}>
322
- {selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : ''}
276
+ <View style={[styles.root, { justifyContent: 'center', alignItems: 'center', backgroundColor: '#000' }]}>
277
+ <ActivityIndicator size="large" color="#2DBD60" />
278
+ <Text style={{ color: 'white', marginTop: 20, fontSize: 16 }}>Initializing Camera...</Text>
279
+ </View>
280
+ );
281
+ }
282
+
283
+ return (
284
+ <View style={styles.root}>
285
+ <View style={[styles.cameraWrapper, { flex: 1, minHeight: 400 }]}>
286
+
287
+ <View style={styles.headerContainer}>
288
+ <Text style={styles.headerTitle}>
289
+ {selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : ''}
290
+ </Text>
291
+ <View style={styles.stepBadge}>
292
+ <Text style={styles.stepText}>
293
+ {t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
323
294
  </Text>
324
- <View style={styles.stepBadge}>
325
- <Text style={styles.stepText}>
326
- {t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
327
- </Text>
328
- </View>
329
295
  </View>
296
+ </View>
297
+
298
+ <View style={[styles.cameraFeedContainer, { flex: 1, backgroundColor: '#000' }]}>
299
+
300
+ <EnhancedCameraView
301
+ key={`${currentSide}-${cameraKey}`}
302
+ showCamera={true}
303
+ isProcessing={isBusy}
304
+ cameraType={cameraConfig.cameraType}
305
+ style={styles.absoluteFillObject}
306
+ onError={handleError}
307
+ onSilentCapture={handleSilentCapture}
308
+ silentCaptureResult={silentCaptureResult}
309
+ overlayComponent={
310
+ <>
311
+ {/* We ONLY put the ID frame here, because if the camera fails, we don't care if the frame fails too */}
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
+ setIsProcessingCapture(false);
324
+ setProcessingImagePath(null);
325
+ if (capturedImages['front']?.dir) {
326
+ const frontImage = capturedImages['front'];
327
+ setSilentCaptureResult((prev) => ({ ...prev, path: frontImage.dir, success: true, isAnalyzing: false, error: '', mrz: frontImage.mrz || '', templatePath: frontImage.templatePath || '', bbox: bboxBySide['front'] }));
328
+ } else { setSilentCaptureResult((prev) => ({ ...prev, path: '', success: false, isAnalyzing: false, error: '', templatePath: '' })); }
329
+ } else { actions.previousComponent(); }
330
+ },
331
+ selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
332
+ step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
333
+ }}
334
+ />
335
+ </>
336
+ }
337
+ />
330
338
 
331
- <View style={styles.cameraFeedContainer}>
332
- <EnhancedCameraView
333
- key={currentSide}
334
- showCamera={true}
335
- isProcessing={isBusy}
336
- cameraType={cameraConfig.cameraType}
337
- style={styles.camera}
338
- onError={handleError}
339
- onSilentCapture={handleSilentCapture}
340
- silentCaptureResult={silentCaptureResult}
341
- overlayComponent={
342
- <>
343
- {!isBusy && silentCaptureResult.isAnalyzing && (
344
- <View style={styles.topAnalyzingPillContainer}>
345
- <View style={styles.topAnalyzingPill}>
346
- <ActivityIndicator size="small" color="white" />
347
- <Text style={styles.analyzingPillText}>
348
- {state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
349
- </Text>
350
- </View>
351
- </View>
352
- )}
353
- {isBusy && (
354
- <View style={StyleSheet.absoluteFillObject}>
355
- {processingImagePath && (
356
- <Image
357
- source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }}
358
- style={StyleSheet.absoluteFillObject}
359
- resizeMode="cover"
360
- />
361
- )}
362
- <View style={styles.processingOverlay}>
363
- <ActivityIndicator size="large" color="#2DBD60" />
364
- <Text style={styles.processingText}>
365
- {state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
366
- </Text>
367
- </View>
368
- </View>
369
- )}
370
- <IdCardOverlay
371
- xMin={cameraConfig.overlay.bbox.xMin} yMin={cameraConfig.overlay.bbox.yMin} xMax={cameraConfig.overlay.bbox.xMax} yMax={cameraConfig.overlay.bbox.yMax}
372
- instructions={cameraConfig.overlay.guideText}
373
- cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0 as number}
374
- isSuccess={silentCaptureResult.success}
375
- language={state.currentLanguage}
376
- stepperProps={{
377
- back: () => {
378
- if (currentSide === 'back') {
379
- setCurrentSide('front');
380
- setShowCamera(false);
381
- setIsProcessingCapture(false);
382
- setProcessingImagePath(null);
383
- if (capturedImages['front']?.dir) {
384
- const frontImage = capturedImages['front'];
385
- setSilentCaptureResult((prev) => ({ ...prev, path: frontImage.dir, success: true, isAnalyzing: false, error: '', mrz: frontImage.mrz || '', templatePath: frontImage.templatePath || '', bbox: bboxBySide['front'] }));
386
- } else { setSilentCaptureResult((prev) => ({ ...prev, path: '', success: false, isAnalyzing: false, error: '', templatePath: '' })); }
387
- } else { actions.previousComponent(); }
388
- },
389
- selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
390
- step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
391
- }}
392
- />
393
- </>
394
- }
395
- />
396
- {/* Elegant Floating Error Banner below the cutout */}
397
- {silentCaptureResult.error && !isBusy ? (
398
- <View style={styles.floatingErrorBanner}>
399
- <Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
339
+
340
+ {!isBusy && silentCaptureResult.isAnalyzing && (
341
+ <View style={styles.topAnalyzingPillContainer}>
342
+ <View style={styles.topAnalyzingPill}>
343
+ <ActivityIndicator size="small" color="white" />
344
+ <Text style={styles.analyzingPillText}>
345
+ {state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
346
+ </Text>
400
347
  </View>
401
- ) : null}
402
- </View>
348
+ </View>
349
+ )}
350
+
351
+ {isBusy && (
352
+ <View style={[StyleSheet.absoluteFillObject, { zIndex: 9999 }]}>
353
+ {processingImagePath && (
354
+ <Image
355
+ source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }}
356
+ style={StyleSheet.absoluteFillObject}
357
+ resizeMode="cover"
358
+ />
359
+ )}
360
+ <View style={styles.processingOverlay}>
361
+ <ActivityIndicator size="large" color="#2DBD60" />
362
+ <Text style={styles.processingText}>
363
+ {state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
364
+ </Text>
365
+ </View>
366
+ </View>
367
+ )}
368
+
369
+ {!isBusy && (
370
+ <View style={styles.escapeHatchContainer}>
371
+ {/* Refresh Button */}
372
+ <TouchableOpacity style={styles.fallbackRefreshButton} onPress={refreshCamera}>
373
+ <Text style={styles.fallbackRefreshText}>↻ Refresh Camera</Text>
374
+ </TouchableOpacity>
375
+
376
+ <TouchableOpacity
377
+ style={[styles.fallbackRefreshButton, { marginTop: 15, backgroundColor: 'rgba(220, 38, 38, 0.8)', borderColor: '#DC2626' }]}
378
+ onPress={() => setShowCamera(false)}
379
+ >
380
+ <Text style={styles.fallbackRefreshText}>Cancel / Go Back</Text>
381
+ </TouchableOpacity>
382
+ </View>
383
+ )}
384
+
385
+ {silentCaptureResult.error && !isBusy ? (
386
+ <View style={[styles.floatingErrorBanner, { zIndex: 10000 }]}>
387
+ <Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
388
+ </View>
389
+ ) : null}
390
+
403
391
  </View>
404
392
  </View>
405
- );
406
- }
393
+ </View>
394
+ );
395
+ }
407
396
 
408
397
  return (
409
398
  <View style={styles.root}>
@@ -464,130 +453,82 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({ component, value =
464
453
  };
465
454
 
466
455
  const styles = StyleSheet.create({
467
- root: {
468
- flex: 1,
469
- width: '100%',
470
- backgroundColor: 'transparent',
471
- alignSelf: 'center',
472
- ...(Platform.OS === 'web'
473
- ? ({
474
- minHeight: '85vh',
475
- justifyContent: 'center',
476
- alignItems: 'center',
477
- // Note: backdropFilter is valid in React Native Web but TS might complain, cast safely
478
- backdropFilter: 'blur(8px)'
479
- } as any)
480
- : {})
456
+ root: {
457
+ flex: 1, width: '100%', backgroundColor: 'transparent', alignSelf: 'center',
458
+ ...(Platform.OS === 'web' ? ({ minHeight: '85vh', justifyContent: 'center', alignItems: 'center', backdropFilter: 'blur(8px)' } as any) : {})
481
459
  },
482
460
  cameraWrapper: {
483
- width: '100%',
484
- backgroundColor: '#FFFFFF',
485
- overflow: 'hidden',
486
- ...(Platform.OS === 'web'
487
- ? ({
488
- maxWidth: 500,
489
- height: 700,
490
- maxHeight: '90vh', // TypeScript will now ignore this thanks to the cast below
491
- borderRadius: 24,
492
- shadowColor: '#000',
493
- shadowOffset: { width: 0, height: 20 },
494
- shadowOpacity: 0.25,
495
- shadowRadius: 35,
496
- elevation: 24,
497
- } as any) // 🚨 CAST TO ANY
498
- : {
499
- flex: 1,
500
- })
461
+ width: '100%', backgroundColor: '#FFFFFF', overflow: 'hidden',
462
+ ...(Platform.OS === 'web' ? ({ maxWidth: 500, height: 700, maxHeight: '90vh', borderRadius: 24, shadowColor: '#000', shadowOffset: { width: 0, height: 20 }, shadowOpacity: 0.25, shadowRadius: 35, elevation: 24, } as any) : { flex: 1, })
501
463
  },
502
464
  headerContainer: {
503
- flexDirection: 'row',
504
- alignItems: 'center',
505
- justifyContent: 'space-between',
506
- paddingHorizontal: 24,
507
- paddingVertical: 18,
508
- backgroundColor: '#FFFFFF',
509
- borderBottomWidth: 1,
510
- borderBottomColor: '#F1F5F9',
511
- zIndex: 10,
512
- // Mobile hidden, Web visible to replace floating text
513
- ...(Platform.OS !== 'web' ? { display: 'none' } : {})
465
+ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 18, backgroundColor: '#FFFFFF', borderBottomWidth: 1, borderBottomColor: '#F1F5F9', zIndex: 10,
466
+ ...(Platform.OS !== 'web' ? { display: 'none' } : {})
514
467
  },
515
- headerTitle: {
516
- fontSize: 18,
517
- fontWeight: '700',
518
- color: '#0F172A',
468
+ headerTitle: { fontSize: 18, fontWeight: '700', color: '#0F172A', },
469
+ stepBadge: { backgroundColor: '#F1F5F9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20, },
470
+ stepText: { fontSize: 13, fontWeight: '600', color: '#64748B', },
471
+ cameraFeedContainer: { flex: 1, position: 'relative', backgroundColor: '#000', },
472
+ camera: { flex: 1, },
473
+ refreshButton: {
474
+ position: 'absolute', bottom: 100, alignSelf: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 24, zIndex: 500
519
475
  },
520
- stepBadge: {
521
- backgroundColor: '#F1F5F9',
522
- paddingHorizontal: 12,
523
- paddingVertical: 6,
524
- borderRadius: 20,
525
- },
526
- stepText: {
527
- fontSize: 13,
528
- fontWeight: '600',
529
- color: '#64748B',
476
+ refreshButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16 },
477
+ previewContainer: {
478
+ width: '100%', backgroundColor: 'white', borderRadius: 12, paddingVertical: 24, paddingHorizontal: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 8,
479
+ ...(Platform.OS === 'web' ? { alignSelf: 'center', maxWidth: 600 } : { margin: 10, width: '95%' })
530
480
  },
531
- cameraFeedContainer: {
532
- flex: 1,
533
- position: 'relative',
534
- backgroundColor: '#000',
481
+ previewItemContainer: { flexGrow: 1, },
482
+ title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
483
+ description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
484
+ sideContainer: { marginBottom: 24 },
485
+ sideTitle: { fontSize: 25, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
486
+ imagePreviewWrapper: { width: '100%', height: 220, borderRadius: 12, padding: 1, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0' },
487
+ previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
488
+ floatingErrorBanner: {
489
+ position: 'absolute', bottom: 30, left: 24, right: 24, backgroundColor: '#FEF2F2', borderWidth: 1, borderColor: '#FCA5A5', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 12, alignItems: 'center', justifyContent: 'center', shadowColor: '#DC2626', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 8, zIndex: 100
535
490
  },
536
- camera: {
537
- flex: 1,
491
+ floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
492
+ processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
493
+ processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
494
+ warningBanner: { backgroundColor: '#FF9500', padding: 12, borderRadius: 8, width: '100%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 4 },
495
+ warningText: { color: 'white', fontWeight: 'bold', textAlign: 'center', fontSize: 16 },
496
+ errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
497
+ topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
498
+ topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
499
+ analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' },
500
+ escapeHatchContainer: {
501
+ position: 'absolute',
502
+ bottom: 40,
503
+ left: 0,
504
+ right: 0,
505
+ alignItems: 'center',
506
+ justifyContent: 'center',
507
+ zIndex: 99999, // Guarantees it is the top-most element
538
508
  },
539
- previewContainer: {
540
- width: '100%',
541
- backgroundColor: 'white',
542
- borderRadius: 12,
543
- paddingVertical: 24,
544
- paddingHorizontal: 20,
545
- shadowColor: '#000',
546
- shadowOffset: { width: 0, height: 4 },
547
- shadowOpacity: 0.1,
548
- shadowRadius: 12,
549
- elevation: 8,
550
- ...(Platform.OS === 'web' ? { alignSelf: 'center', maxWidth: 600 } : { margin: 10, width: '95%' })
551
- },
552
- previewItemContainer: {
553
- flexGrow: 1,
554
- },
555
- title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
556
- description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
557
- sideContainer: { marginBottom: 24 },
558
- sideTitle: { fontSize: 25, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
559
- imagePreviewWrapper: {
560
- width: '100%', height: 220, borderRadius: 12, padding: 1, overflow: 'hidden',
561
- shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0'
562
- },
563
- previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
564
- floatingErrorBanner: {
565
- position: 'absolute',
566
- bottom: 30, // Pushed to the bottom for professional feel
567
- left: 24,
568
- right: 24,
569
- backgroundColor: '#FEF2F2',
509
+ fallbackRefreshButton: {
510
+ backgroundColor: 'rgba(0, 0, 0, 0.8)', // Darker so it's visible on white or black
570
511
  borderWidth: 1,
571
- borderColor: '#FCA5A5',
572
- paddingVertical: 12,
573
- paddingHorizontal: 16,
574
- borderRadius: 12,
575
- alignItems: 'center',
576
- justifyContent: 'center',
577
- shadowColor: '#DC2626',
578
- shadowOffset: { width: 0, height: 4 },
579
- shadowOpacity: 0.1,
580
- shadowRadius: 8,
581
- elevation: 8,
582
- zIndex: 100
583
- },
584
- floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
585
- processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
586
- processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
587
- warningBanner: { backgroundColor: '#FF9500', padding: 12, borderRadius: 8, width: '100%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 4 },
588
- warningText: { color: 'white', fontWeight: 'bold', textAlign: 'center', fontSize: 16 },
589
- errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
590
- topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
591
- topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
592
- analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' }
512
+ borderColor: 'rgba(255, 255, 255, 0.5)',
513
+ paddingVertical: 12,
514
+ paddingHorizontal: 24,
515
+ borderRadius: 24,
516
+ shadowColor: '#000',
517
+ shadowOffset: { width: 0, height: 4 },
518
+ shadowOpacity: 0.3,
519
+ shadowRadius: 5,
520
+ elevation: 8,
521
+ },
522
+ fallbackRefreshText: {
523
+ color: '#FFFFFF',
524
+ fontWeight: 'bold',
525
+ fontSize: 16,
526
+ },
527
+ absoluteFillObject: {
528
+ position: 'absolute',
529
+ top: 0,
530
+ left: 0,
531
+ right: 0,
532
+ bottom: 0,
533
+ },
593
534
  });