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

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 (24) hide show
  1. package/build/package.json +1 -1
  2. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  3. package/build/src/components/KYCElements/CountrySelection.js +259 -63
  4. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  5. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  6. package/build/src/components/KYCElements/IDCardCapture.js +222 -69
  7. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  8. package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
  9. package/build/src/components/KYCElements/PhoneVerificationTemplate.js +160 -21
  10. package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
  11. package/build/src/config/region_mapping.json +727 -0
  12. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  13. package/build/src/modules/api/CardAuthentification.js +3 -7
  14. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  15. package/build/src/modules/api/KYCService.d.ts +1 -2
  16. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  17. package/build/src/modules/api/KYCService.js +106 -59
  18. package/build/src/modules/api/KYCService.js.map +1 -1
  19. package/package.json +1 -1
  20. package/src/components/KYCElements/CountrySelection.tsx +300 -74
  21. package/src/components/KYCElements/IDCardCapture.tsx +310 -156
  22. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +201 -29
  23. package/src/modules/api/CardAuthentification.ts +5 -8
  24. package/src/modules/api/KYCService.ts +167 -105
@@ -12,6 +12,13 @@ import { backVerification, checkTemplateType, frontVerification } from '../../mo
12
12
  import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
13
13
  import pathToBase64 from '../../utils/pathToBase64';
14
14
  import { cropByObb, cropImageWithBBoxWithTolerance, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../utils/cropByObb';
15
+ import REGION_MAPPING from '../../config/region_mapping.json';
16
+ // 🌍 Map ISO codes to exact JSON root keys to ensure perfect dictionary lookups
17
+ const ISO_TO_COUNTRY_NAME = {
18
+ 'KE': 'Kenya', 'CM': 'Cameroon', 'NG': 'Nigeria', 'CA': 'Canada',
19
+ 'FR': 'France', 'GH': 'Ghana', 'ZA': 'South Africa', 'GB': 'Britain',
20
+ 'CI': 'Ivory Coast', 'SN': 'Senegal', 'TG': 'Togo', 'ML': 'Mali'
21
+ };
15
22
  export const IDCardCapture = ({ component, value = {}, onValueChange, error, language = 'en' }) => {
16
23
  const { t, locale } = useI18n();
17
24
  const [showCamera, setShowCamera] = useState(false);
@@ -48,17 +55,20 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
48
55
  if (JSON.stringify(value) !== JSON.stringify(capturedImages)) {
49
56
  const updatedImages = value;
50
57
  setCapturedImages(updatedImages);
51
- Object.keys(updatedImages).forEach((side) => {
52
- const imageData = updatedImages[side];
53
- if (imageData?.dir) {
54
- setSilentCaptureResult(prev => ({
55
- ...prev, path: imageData.dir, success: true, isAnalyzing: false, mrz: imageData.mrz || '', templatePath: imageData.templatePath || '',
56
- }));
57
- }
58
- });
58
+ const currentImageData = updatedImages[currentSide];
59
+ if (currentImageData?.dir) {
60
+ setSilentCaptureResult(prev => ({
61
+ ...prev,
62
+ path: currentImageData.dir,
63
+ success: true,
64
+ isAnalyzing: false,
65
+ mrz: currentImageData.mrz || '',
66
+ templatePath: currentImageData.templatePath || '',
67
+ }));
68
+ }
59
69
  }
60
70
  }
61
- }, [value]);
71
+ }, [value, currentSide]);
62
72
  const cameraConfig = useMemo(() => {
63
73
  const instructions = selectedDocumentType
64
74
  ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr)
@@ -69,7 +79,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
69
79
  };
70
80
  }, [selectedDocumentType, locale, component.instructions]);
71
81
  const retakePicture = (sideToRetake) => {
72
- // Completely wipe all processing states to prevent leakage
73
82
  setIsProcessingCapture(false);
74
83
  setProcessingImagePath(null);
75
84
  setSilentCaptureResult({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
@@ -82,13 +91,30 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
82
91
  onValueChange(newValue);
83
92
  }
84
93
  };
85
- const getCurrentSideVerification = (currentSide) => {
86
- if (!selectedDocumentType || !countryData?.regionMapping)
94
+ const getCurrentSideVerification = (currentSide, countryKey) => {
95
+ const rawDocType = countrySelectionData?.documentType;
96
+ if (!rawDocType || !countryKey) {
87
97
  return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
88
- const regionMapping = countryData.regionMapping[selectedDocumentType.type];
98
+ }
99
+ const rawCountryName = ISO_TO_COUNTRY_NAME[countryData?.code || ''] || countryData?.code || countryKey;
100
+ const baseMapping = REGION_MAPPING.regionMapping || REGION_MAPPING;
101
+ let countryMapping = baseMapping[rawCountryName];
102
+ // Fallback search in case of case mismatches
103
+ if (!countryMapping) {
104
+ const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase() || k.toLowerCase() === countryKey.toLowerCase());
105
+ if (foundKey)
106
+ countryMapping = baseMapping[foundKey];
107
+ }
108
+ if (!countryMapping) {
109
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
110
+ }
111
+ const regionMapping = countryMapping[rawDocType];
112
+ if (!regionMapping) {
113
+ return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
114
+ }
89
115
  const authMethod = [];
90
116
  const mrzTypes = [];
91
- const key = selectedDocumentType.region?.trim()?.length > 0 ? selectedDocumentType.region.trim() : 'root';
117
+ const key = countrySelectionData.region?.trim()?.length > 0 ? countrySelectionData.region.trim() : 'root';
92
118
  if (regionMapping?.[key] && Array.isArray(regionMapping[key])) {
93
119
  regionMapping[key].forEach((item) => {
94
120
  if (item[currentSide]) {
@@ -168,16 +194,16 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
168
194
  return;
169
195
  if (result.success && result.path) {
170
196
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: true, success: false, error: '' }));
171
- let templatePath = silentCaptureResult.templatePath || '';
197
+ // 🚨 Force a template fetch if we haven't successfully saved the current side yet
198
+ let templatePath = capturedImages[currentSide]?.templatePath || '';
172
199
  let templateBbox;
173
200
  let templateResponse;
174
201
  if (!selectedDocumentType) {
175
202
  setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, success: false, error: 'Document type not selected' }));
176
203
  return;
177
204
  }
178
- const regionMappings = getCurrentSideVerification(currentSide);
179
205
  try {
180
- if (templatePath.length === 0) {
206
+ if (!templatePath) {
181
207
  const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type, docRegion: countryData?.code || "", postfix: currentSide }, env);
182
208
  templateResponse = templateType;
183
209
  if (templateType.template_path)
@@ -195,16 +221,33 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
195
221
  catch { }
196
222
  }
197
223
  }
224
+ const extractedCountryKey = templatePath ? templatePath.split('/')[0] : (ISO_TO_COUNTRY_NAME[countryData?.code || ''] || 'root');
225
+ const regionMappings = getCurrentSideVerification(currentSide, extractedCountryKey);
198
226
  let verificationRes;
199
227
  if (currentSide === 'front') {
200
228
  const matchedAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'front');
201
- const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
229
+ const mrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
202
230
  verificationRes = await frontVerification({ path: result.path, regionMapping: { authMethod: matchedAuthMethod ? [matchedAuthMethod] : regionMappings.authMethod, mrzTypes: regionMappings.mrzTypes }, selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType?.type] || '', code: countryData?.code || '', currentSide, templatePath, mrzType }, env);
203
231
  }
204
232
  else {
205
- const matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
206
- const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || '';
207
- verificationRes = await backVerification({ path: result.path, regionMapping: { authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : regionMappings.authMethod, mrzTypes: regionMappings.mrzTypes }, selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type] || '', code: countryData?.code || '', currentSide, templatePath, mrzType: backMrzType, templateResponse }, env);
233
+ let matchedBackAuthMethod = getCorrespondingAuthMethod(templatePath, regionMappings.regionMapping, regionMappings.key || '', 'back');
234
+ if (!matchedBackAuthMethod && currentSide === 'back') {
235
+ matchedBackAuthMethod = 'MRZ';
236
+ }
237
+ const backMrzType = getCorrespondingMrzType(templatePath, regionMappings.regionMapping, regionMappings.key || '') || 'TD1';
238
+ verificationRes = await backVerification({
239
+ path: result.path,
240
+ regionMapping: {
241
+ authMethod: matchedBackAuthMethod ? [matchedBackAuthMethod] : regionMappings.authMethod,
242
+ mrzTypes: regionMappings.mrzTypes
243
+ },
244
+ selectedDocumentType: GovernmentDocumentTypeShorted[selectedDocumentType.type] || '',
245
+ code: countryData?.code || '',
246
+ currentSide,
247
+ templatePath,
248
+ mrzType: backMrzType,
249
+ templateResponse
250
+ }, env);
208
251
  }
209
252
  const bbox = verificationRes?.bbox || templateBbox;
210
253
  const mrz = verificationRes?.mrz ? JSON.stringify(verificationRes.mrz) : "";
@@ -229,7 +272,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
229
272
  useEffect(() => { actions.showCustomStepper(!showCamera); }, [showCamera]);
230
273
  if (!countrySelectionData || !selectedDocumentType) {
231
274
  return (<View style={styles.root}>
232
- <View style={styles.container}>
275
+ <View style={styles.previewContainer}>
233
276
  <Text style={styles.title}>{getLocalizedText(component.labels)}</Text>
234
277
  <Text style={styles.description}>
235
278
  {state.currentLanguage === "en" ? "Please complete the country and document selection first." : "Veuillez d'abord compléter la sélection du pays et du document."}
@@ -240,34 +283,45 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
240
283
  // --- CAMERA RENDER ---
241
284
  if (showCamera) {
242
285
  const isBusy = isProcessingCapture;
243
- return (<View style={styles.cameraContainer}>
244
- <EnhancedCameraView key={currentSide} // 🚨 BUG FIX: Forces the camera instance to completely reset when switching sides
245
- showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType} style={styles.camera} onError={handleError} onSilentCapture={handleSilentCapture} silentCaptureResult={silentCaptureResult} overlayComponent={<>
246
- {!isBusy && silentCaptureResult.isAnalyzing && (<View style={styles.topAnalyzingPillContainer}>
247
- <View style={styles.topAnalyzingPill}>
248
- <ActivityIndicator size="small" color="white"/>
249
- <Text style={styles.analyzingPillText}>
250
- {state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
251
- </Text>
252
- </View>
253
- </View>)}
254
-
255
- {isBusy && (<View style={StyleSheet.absoluteFillObject}>
256
- {processingImagePath && (<Image source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }} style={StyleSheet.absoluteFillObject} resizeMode="cover"/>)}
257
- <View style={styles.processingOverlay}>
258
- <ActivityIndicator size="large" color="#2DBD60"/>
259
- <Text style={styles.processingText}>
260
- {state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
261
- </Text>
262
- </View>
263
- </View>)}
286
+ return (<View style={styles.root}>
287
+ <View style={styles.cameraWrapper}>
288
+
289
+ {/* Web/Desktop Clean Header */}
290
+ <View style={styles.headerContainer}>
291
+ <Text style={styles.headerTitle}>
292
+ {selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : ''}
293
+ </Text>
294
+ <View style={styles.stepBadge}>
295
+ <Text style={styles.stepText}>
296
+ {t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
297
+ </Text>
298
+ </View>
299
+ </View>
264
300
 
265
- <IdCardOverlay xMin={cameraConfig.overlay.bbox.xMin} yMin={cameraConfig.overlay.bbox.yMin} xMax={cameraConfig.overlay.bbox.xMax} yMax={cameraConfig.overlay.bbox.yMax} instructions={cameraConfig.overlay.guideText} cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0} isSuccess={silentCaptureResult.success} language={state.currentLanguage} stepperProps={{
301
+ <View style={styles.cameraFeedContainer}>
302
+ <EnhancedCameraView key={currentSide} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType} style={styles.camera} onError={handleError} onSilentCapture={handleSilentCapture} silentCaptureResult={silentCaptureResult} overlayComponent={<>
303
+ {!isBusy && silentCaptureResult.isAnalyzing && (<View style={styles.topAnalyzingPillContainer}>
304
+ <View style={styles.topAnalyzingPill}>
305
+ <ActivityIndicator size="small" color="white"/>
306
+ <Text style={styles.analyzingPillText}>
307
+ {state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
308
+ </Text>
309
+ </View>
310
+ </View>)}
311
+ {isBusy && (<View style={StyleSheet.absoluteFillObject}>
312
+ {processingImagePath && (<Image source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }} style={StyleSheet.absoluteFillObject} resizeMode="cover"/>)}
313
+ <View style={styles.processingOverlay}>
314
+ <ActivityIndicator size="large" color="#2DBD60"/>
315
+ <Text style={styles.processingText}>
316
+ {state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
317
+ </Text>
318
+ </View>
319
+ </View>)}
320
+ <IdCardOverlay xMin={cameraConfig.overlay.bbox.xMin} yMin={cameraConfig.overlay.bbox.yMin} xMax={cameraConfig.overlay.bbox.xMax} yMax={cameraConfig.overlay.bbox.yMax} instructions={cameraConfig.overlay.guideText} cornerOpacity={cameraConfig.overlay.bbox.cornerRadius || 0} isSuccess={silentCaptureResult.success} language={state.currentLanguage} stepperProps={{
266
321
  back: () => {
267
322
  if (currentSide === 'back') {
268
323
  setCurrentSide('front');
269
324
  setShowCamera(false);
270
- // 🚨 Clean up any residual state when going backwards
271
325
  setIsProcessingCapture(false);
272
326
  setProcessingImagePath(null);
273
327
  if (capturedImages['front']?.dir) {
@@ -285,11 +339,13 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
285
339
  selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
286
340
  step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
287
341
  }}/>
288
- </>}/>
289
-
290
- {silentCaptureResult.error && !isBusy ? (<View style={styles.floatingErrorBanner}>
291
- <Text style={styles.floatingErrorText}>{silentCaptureResult.error}</Text>
292
- </View>) : null}
342
+ </>}/>
343
+ {/* Elegant Floating Error Banner below the cutout */}
344
+ {silentCaptureResult.error && !isBusy ? (<View style={styles.floatingErrorBanner}>
345
+ <Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
346
+ </View>) : null}
347
+ </View>
348
+ </View>
293
349
  </View>);
294
350
  }
295
351
  return (<View style={styles.root}>
@@ -303,19 +359,15 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
303
359
  {getLocalizedText(component.instructions)}
304
360
  </Text>
305
361
  <View style={{ alignItems: 'center', justifyContent: 'center', flexDirection: "column", gap: 16 }}>
306
-
307
362
  {silentCaptureResult?.error === 'TOO_FAR_AWAY' && (<View style={styles.warningBanner}>
308
363
  <Text style={styles.warningText}>
309
364
  {state.currentLanguage === "en" ? "Move the document closer to the camera and place it on a flat surface." : "Veuillez rapprocher le document de la caméra et le poser à plat."}
310
365
  </Text>
311
366
  </View>)}
312
-
313
367
  <View style={styles.imagePreviewWrapper}>
314
368
  {capturedImages[currentSide]?.dir ? (<Image source={{ uri: capturedImages[currentSide].dir }} style={styles.previewImage}/>) : silentCaptureResult.path ? (<Image source={{ uri: silentCaptureResult.path }} style={styles.previewImage}/>) : null}
315
369
  </View>
316
-
317
370
  {!capturedImages[currentSide]?.dir && (<Button title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"} onPress={() => { setShowCamera(true); actions.showCustomStepper(false); }} variant="primary" size="large" fullWidth/>)}
318
-
319
371
  {capturedImages[currentSide]?.dir && (<>
320
372
  <Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => retakePicture(currentSide)} variant="outline" size="medium" fullWidth/>
321
373
  <Button title={t('common.next')} onPress={() => {
@@ -323,14 +375,13 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
323
375
  showAlert('Error', 'Document type not selected');
324
376
  return;
325
377
  }
326
- // 🚨 BUG FIX: Clear all state before moving forward
327
378
  if (currentSide === 'back' || selectedDocumentType.type === 'passport') {
328
379
  actions.nextComponent();
329
380
  }
330
381
  else {
331
382
  setShowCamera(true);
332
383
  setCurrentSide('back');
333
- setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '' });
384
+ setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: undefined });
334
385
  setIsProcessingCapture(false);
335
386
  setProcessingImagePath(null);
336
387
  }
@@ -343,19 +394,124 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
343
394
  </View>);
344
395
  };
345
396
  const styles = StyleSheet.create({
346
- root: { flex: 1, maxWidth: 760, width: '100%' },
347
- container: { backgroundColor: 'white', margin: 10, borderRadius: 10, paddingVertical: 16, paddingHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.35, shadowRadius: 4.84, elevation: 10 },
348
- cameraContainer: { flex: 1, width: '100%', height: '100%' },
349
- previewContainer: { width: '95%', backgroundColor: 'white', margin: 10, borderRadius: 10, paddingVertical: 16, paddingHorizontal: 16 },
350
- previewItemContainer: {},
397
+ root: {
398
+ flex: 1,
399
+ width: '100%',
400
+ backgroundColor: 'transparent',
401
+ alignSelf: 'center',
402
+ ...(Platform.OS === 'web'
403
+ ? {
404
+ minHeight: '85vh',
405
+ justifyContent: 'center',
406
+ alignItems: 'center',
407
+ // Note: backdropFilter is valid in React Native Web but TS might complain, cast safely
408
+ backdropFilter: 'blur(8px)'
409
+ }
410
+ : {})
411
+ },
412
+ cameraWrapper: {
413
+ width: '100%',
414
+ backgroundColor: '#FFFFFF',
415
+ overflow: 'hidden',
416
+ ...(Platform.OS === 'web'
417
+ ? {
418
+ maxWidth: 500,
419
+ height: 700,
420
+ maxHeight: '90vh', // TypeScript will now ignore this thanks to the cast below
421
+ borderRadius: 24,
422
+ shadowColor: '#000',
423
+ shadowOffset: { width: 0, height: 20 },
424
+ shadowOpacity: 0.25,
425
+ shadowRadius: 35,
426
+ elevation: 24,
427
+ } // 🚨 CAST TO ANY
428
+ : {
429
+ flex: 1,
430
+ })
431
+ },
432
+ headerContainer: {
433
+ flexDirection: 'row',
434
+ alignItems: 'center',
435
+ justifyContent: 'space-between',
436
+ paddingHorizontal: 24,
437
+ paddingVertical: 18,
438
+ backgroundColor: '#FFFFFF',
439
+ borderBottomWidth: 1,
440
+ borderBottomColor: '#F1F5F9',
441
+ zIndex: 10,
442
+ // Mobile hidden, Web visible to replace floating text
443
+ ...(Platform.OS !== 'web' ? { display: 'none' } : {})
444
+ },
445
+ headerTitle: {
446
+ fontSize: 18,
447
+ fontWeight: '700',
448
+ color: '#0F172A',
449
+ },
450
+ stepBadge: {
451
+ backgroundColor: '#F1F5F9',
452
+ paddingHorizontal: 12,
453
+ paddingVertical: 6,
454
+ borderRadius: 20,
455
+ },
456
+ stepText: {
457
+ fontSize: 13,
458
+ fontWeight: '600',
459
+ color: '#64748B',
460
+ },
461
+ cameraFeedContainer: {
462
+ flex: 1,
463
+ position: 'relative',
464
+ backgroundColor: '#000',
465
+ },
466
+ camera: {
467
+ flex: 1,
468
+ },
469
+ previewContainer: {
470
+ width: '100%',
471
+ backgroundColor: 'white',
472
+ borderRadius: 12,
473
+ paddingVertical: 24,
474
+ paddingHorizontal: 20,
475
+ shadowColor: '#000',
476
+ shadowOffset: { width: 0, height: 4 },
477
+ shadowOpacity: 0.1,
478
+ shadowRadius: 12,
479
+ elevation: 8,
480
+ ...(Platform.OS === 'web' ? { alignSelf: 'center', maxWidth: 600 } : { margin: 10, width: '95%' })
481
+ },
482
+ previewItemContainer: {
483
+ flexGrow: 1,
484
+ },
351
485
  title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
352
486
  description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
353
487
  sideContainer: { marginBottom: 24 },
354
488
  sideTitle: { fontSize: 25, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
355
- imagePreviewWrapper: { width: '100%', height: 200, borderRadius: 12, padding: 1, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#000' },
356
- previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'contain' },
357
- floatingErrorBanner: { position: 'absolute', top: 60, left: '10%', right: '10%', backgroundColor: 'rgba(220, 38, 38, 0.95)', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 8, alignItems: 'center', justifyContent: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 5, elevation: 8, zIndex: 100 },
358
- floatingErrorText: { color: 'white', fontSize: 14, fontWeight: '700', textAlign: 'center' },
489
+ imagePreviewWrapper: {
490
+ width: '100%', height: 220, borderRadius: 12, padding: 1, overflow: 'hidden',
491
+ shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0'
492
+ },
493
+ previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
494
+ floatingErrorBanner: {
495
+ position: 'absolute',
496
+ bottom: 30, // Pushed to the bottom for professional feel
497
+ left: 24,
498
+ right: 24,
499
+ backgroundColor: '#FEF2F2',
500
+ borderWidth: 1,
501
+ borderColor: '#FCA5A5',
502
+ paddingVertical: 12,
503
+ paddingHorizontal: 16,
504
+ borderRadius: 12,
505
+ alignItems: 'center',
506
+ justifyContent: 'center',
507
+ shadowColor: '#DC2626',
508
+ shadowOffset: { width: 0, height: 4 },
509
+ shadowOpacity: 0.1,
510
+ shadowRadius: 8,
511
+ elevation: 8,
512
+ zIndex: 100
513
+ },
514
+ floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
359
515
  processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
360
516
  processingText: { color: '#FFF', fontSize: 18, fontWeight: 'bold', marginTop: 16, textAlign: 'center' },
361
517
  warningBanner: { backgroundColor: '#FF9500', padding: 12, borderRadius: 8, width: '100%', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4, elevation: 4 },
@@ -363,9 +519,6 @@ const styles = StyleSheet.create({
363
519
  errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
364
520
  topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
365
521
  topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
366
- analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' },
367
- camera: {
368
- flex: 1,
369
- },
522
+ analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' }
370
523
  });
371
524
  //# sourceMappingURL=IDCardCapture.js.map