@sanctum-key/react-native-sdk 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/package.json +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/EmailVerificationTemplate.js +69 -37
- package/build/src/components/KYCElements/EmailVerificationTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +131 -105
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js +97 -65
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +1 -1
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +127 -95
- package/src/components/KYCElements/IDCardCapture.tsx +210 -173
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +185 -165
- package/src/modules/api/KYCService.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity } from 'react-native';
|
|
2
|
+
import { View, Text, StyleSheet, Image, ScrollView, Platform, ActivityIndicator, TouchableOpacity } from 'react-native';
|
|
3
3
|
import { showAlert } from '../../utils/platformAlert';
|
|
4
4
|
import { EnhancedCameraView } from '../EnhancedCameraView';
|
|
5
5
|
import { GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
|
|
@@ -23,8 +23,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
23
23
|
const [silentCaptureResult, setSilentCaptureResult] = useState({ success: false, isAnalyzing: false });
|
|
24
24
|
const [isProcessingCapture, setIsProcessingCapture] = useState(false);
|
|
25
25
|
const [processingImagePath, setProcessingImagePath] = useState(null);
|
|
26
|
-
// 🚨 ADDED: Key to force camera re-mount
|
|
27
26
|
const [cameraKey, setCameraKey] = useState(0);
|
|
27
|
+
const [isRebootingCamera, setIsRebootingCamera] = useState(false);
|
|
28
|
+
// Web specific state
|
|
29
|
+
const [cameraType, setCameraType] = useState('back');
|
|
28
30
|
const documentTypeMapping = { 'nationalId': 'national_id', 'passport': 'passport', 'driversLicense': 'drivers_licence', 'residencePermit': 'permanent_residence', 'healthInsuranceCard': 'health_insurance_card', };
|
|
29
31
|
const { actions, state, env } = useTemplateKYCFlowContext();
|
|
30
32
|
const getLocalizedText = (text) => {
|
|
@@ -44,7 +46,6 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
44
46
|
return { type: mappedType, region: countrySelectionData.region || 'root' };
|
|
45
47
|
}, [countrySelectionData, documentTypeMapping]);
|
|
46
48
|
const countryData = useMemo(() => countrySelectionData, [countrySelectionData]);
|
|
47
|
-
const [isRebootingCamera, setIsRebootingCamera] = useState(false);
|
|
48
49
|
const refreshCamera = () => {
|
|
49
50
|
setIsRebootingCamera(true);
|
|
50
51
|
setTimeout(() => {
|
|
@@ -52,6 +53,10 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
52
53
|
setIsRebootingCamera(false);
|
|
53
54
|
}, 500);
|
|
54
55
|
};
|
|
56
|
+
const toggleCameraLens = () => {
|
|
57
|
+
setCameraType(prev => prev === 'back' ? 'front' : 'back');
|
|
58
|
+
refreshCamera();
|
|
59
|
+
};
|
|
55
60
|
useEffect(() => {
|
|
56
61
|
if (value && Object.keys(value).length > 0) {
|
|
57
62
|
if (JSON.stringify(value) !== JSON.stringify(capturedImages)) {
|
|
@@ -69,15 +74,17 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
69
74
|
const cameraConfig = useMemo(() => {
|
|
70
75
|
const instructions = selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).instructions.en : getDocumentTypeInfo(selectedDocumentType.type).instructions.fr) : getLocalizedText(component.instructions);
|
|
71
76
|
return {
|
|
72
|
-
cameraType: 'back',
|
|
77
|
+
cameraType: Platform.OS === 'web' ? cameraType : 'back', // Keep strictly 'back' on mobile
|
|
78
|
+
flashMode: 'auto',
|
|
73
79
|
overlay: { guideText: instructions, bbox: { xMin: 15, yMin: 20, xMax: 85, yMax: 70, borderColor: '#2DBD60', borderWidth: 3, cornerRadius: 8 } }
|
|
74
80
|
};
|
|
75
|
-
}, [selectedDocumentType, locale, component.instructions]);
|
|
81
|
+
}, [selectedDocumentType, locale, component.instructions, cameraType]);
|
|
76
82
|
const retakePicture = (sideToRetake) => {
|
|
77
83
|
setIsProcessingCapture(false);
|
|
78
84
|
setProcessingImagePath(null);
|
|
79
85
|
setSilentCaptureResult({ path: '', success: false, isAnalyzing: false, error: '', templatePath: '', mrz: '', bbox: undefined });
|
|
80
86
|
setShowCamera(true);
|
|
87
|
+
refreshCamera();
|
|
81
88
|
actions.showCustomStepper(false);
|
|
82
89
|
setCapturedImages((prev) => { const newState = { ...prev }; delete newState[sideToRetake]; return newState; });
|
|
83
90
|
if (value) {
|
|
@@ -177,7 +184,9 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
177
184
|
}, 600);
|
|
178
185
|
}
|
|
179
186
|
catch (e) {
|
|
180
|
-
|
|
187
|
+
console.error("Backend Error:", e);
|
|
188
|
+
const friendlyError = state.currentLanguage === 'en' ? 'Unable to process document. Please try again.' : 'Impossible de traiter le document. Veuillez réessayer.';
|
|
189
|
+
showAlert('Error', friendlyError);
|
|
181
190
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false }));
|
|
182
191
|
setIsProcessingCapture(false);
|
|
183
192
|
setProcessingImagePath(null);
|
|
@@ -239,8 +248,11 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
239
248
|
await autoCapture(result.path, verifiedResult);
|
|
240
249
|
}
|
|
241
250
|
catch (error) {
|
|
251
|
+
console.error("Backend Error:", error);
|
|
242
252
|
const isCardNotFullyInFrame = error?.message === 'CARD_NOT_FULLY_IN_FRAME' || error?.message?.includes('entirement');
|
|
243
|
-
const errorMessage = isCardNotFullyInFrame
|
|
253
|
+
const errorMessage = isCardNotFullyInFrame
|
|
254
|
+
? t('kyc.idCardCapture.cardNotFullyInFrame')
|
|
255
|
+
: (t('errors.genericVerificationFailed') || 'Verification failed. Please ensure the document is clear and try again.');
|
|
244
256
|
setSilentCaptureResult(prev => ({ ...prev, isAnalyzing: false, success: false, error: errorMessage }));
|
|
245
257
|
}
|
|
246
258
|
}
|
|
@@ -268,29 +280,35 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
268
280
|
const isBusy = isProcessingCapture;
|
|
269
281
|
if (isRebootingCamera) {
|
|
270
282
|
return (<View style={[styles.root, { justifyContent: 'center', alignItems: 'center', backgroundColor: '#000' }]}>
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
283
|
+
<ActivityIndicator size="large" color="#2DBD60"/>
|
|
284
|
+
<Text style={{ color: 'white', marginTop: 20, fontSize: 16 }}>Initializing Camera...</Text>
|
|
285
|
+
</View>);
|
|
274
286
|
}
|
|
275
287
|
return (<View style={styles.root}>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
</Text>
|
|
282
|
-
<View style={styles.stepBadge}>
|
|
283
|
-
<Text style={styles.stepText}>
|
|
284
|
-
{t('kyc.idCardCapture.captureTitle', { side: currentSide === 'front' ? locale === 'en' ? 'Front' : 'Recto' : locale === 'en' ? 'Back' : 'Verso' })}
|
|
288
|
+
<View style={[styles.cameraWrapper, { flex: 1 }]}>
|
|
289
|
+
|
|
290
|
+
<View style={styles.headerContainer}>
|
|
291
|
+
<Text style={styles.headerTitle}>
|
|
292
|
+
{selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : ''}
|
|
285
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>
|
|
286
299
|
</View>
|
|
287
|
-
</View>
|
|
288
|
-
|
|
289
|
-
<View style={[styles.cameraFeedContainer, { flex: 1, backgroundColor: '#000' }]}>
|
|
290
300
|
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
301
|
+
<View style={[styles.cameraFeedContainer, { flex: 1, backgroundColor: '#000' }]}>
|
|
302
|
+
|
|
303
|
+
{/* WEB ONLY: Flip Camera Top Button */}
|
|
304
|
+
{Platform.OS === 'web' && (<View style={styles.webTopControls}>
|
|
305
|
+
<TouchableOpacity style={styles.webFlipButton} onPress={toggleCameraLens}>
|
|
306
|
+
<Text style={styles.webFlipText}>🔄 Flip Lens</Text>
|
|
307
|
+
</TouchableOpacity>
|
|
308
|
+
</View>)}
|
|
309
|
+
|
|
310
|
+
<EnhancedCameraView key={`${currentSide}-${cameraKey}`} showCamera={true} isProcessing={isBusy} cameraType={cameraConfig.cameraType} style={StyleSheet.absoluteFillObject} onError={handleError} onSilentCapture={handleSilentCapture} silentCaptureResult={silentCaptureResult} overlayComponent={<>
|
|
311
|
+
<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={{
|
|
294
312
|
back: () => {
|
|
295
313
|
if (currentSide === 'back') {
|
|
296
314
|
setCurrentSide('front');
|
|
@@ -312,47 +330,59 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
312
330
|
selectedDocumentType: selectedDocumentType ? (locale === 'en' ? getDocumentTypeInfo(selectedDocumentType.type).name.en : getDocumentTypeInfo(selectedDocumentType.type).name.fr) : '',
|
|
313
331
|
step: state.currentComponentIndex + 1, totalSteps: state.template.components.length, side: currentSide,
|
|
314
332
|
}}/>
|
|
315
|
-
|
|
333
|
+
</>}/>
|
|
316
334
|
|
|
335
|
+
{!isBusy && silentCaptureResult.isAnalyzing && (<View style={styles.topAnalyzingPillContainer}>
|
|
336
|
+
<View style={styles.topAnalyzingPill}>
|
|
337
|
+
<ActivityIndicator size="small" color="white"/>
|
|
338
|
+
<Text style={styles.analyzingPillText}>
|
|
339
|
+
{state.currentLanguage === 'en' ? 'Scanning...' : 'Analyse...'}
|
|
340
|
+
</Text>
|
|
341
|
+
</View>
|
|
342
|
+
</View>)}
|
|
317
343
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
<
|
|
321
|
-
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
{isBusy && (<View style={[StyleSheet.absoluteFillObject, { zIndex: 9999 }]}>
|
|
328
|
-
{processingImagePath && (<Image source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }} style={StyleSheet.absoluteFillObject} resizeMode="cover"/>)}
|
|
329
|
-
<View style={styles.processingOverlay}>
|
|
330
|
-
<ActivityIndicator size="large" color="#2DBD60"/>
|
|
331
|
-
<Text style={styles.processingText}>
|
|
332
|
-
{state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
|
|
333
|
-
</Text>
|
|
334
|
-
</View>
|
|
335
|
-
</View>)}
|
|
344
|
+
{isBusy && (<View style={[StyleSheet.absoluteFillObject, { zIndex: 9999 }]}>
|
|
345
|
+
{processingImagePath && (<Image source={{ uri: processingImagePath.startsWith('file://') ? processingImagePath : `file://${processingImagePath}` }} style={StyleSheet.absoluteFillObject} resizeMode="cover"/>)}
|
|
346
|
+
<View style={styles.processingOverlay}>
|
|
347
|
+
<ActivityIndicator size="large" color="#2DBD60"/>
|
|
348
|
+
<Text style={styles.processingText}>
|
|
349
|
+
{state.currentLanguage === 'en' ? 'Perfect!\nProcessing Document...' : 'Parfait!\nTraitement du document...'}
|
|
350
|
+
</Text>
|
|
351
|
+
</View>
|
|
352
|
+
</View>)}
|
|
336
353
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
354
|
+
{/* 🚨 THE ESCAPE HATCH: Branching UI for Web vs Mobile */}
|
|
355
|
+
{!isBusy && Platform.OS === 'web' ? (<View style={styles.webBottomControlBar}>
|
|
356
|
+
{silentCaptureResult.error ? (<View style={styles.floatingErrorBanner}>
|
|
357
|
+
<Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
|
|
358
|
+
</View>) : <View style={{ height: 10 }}/>}
|
|
342
359
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
360
|
+
<View style={styles.webActionButtonsRow}>
|
|
361
|
+
<TouchableOpacity style={styles.webSecondaryButton} onPress={() => setShowCamera(false)}>
|
|
362
|
+
<Text style={styles.webSecondaryButtonText}>Cancel</Text>
|
|
363
|
+
</TouchableOpacity>
|
|
364
|
+
<TouchableOpacity style={styles.webPrimaryButton} onPress={refreshCamera}>
|
|
365
|
+
<Text style={styles.webPrimaryButtonText}>↻ Refresh Camera</Text>
|
|
366
|
+
</TouchableOpacity>
|
|
367
|
+
</View>
|
|
368
|
+
</View>) : !isBusy ? (<View style={styles.escapeHatchContainer}>
|
|
369
|
+
<TouchableOpacity style={styles.fallbackRefreshButton} onPress={refreshCamera}>
|
|
370
|
+
<Text style={styles.fallbackRefreshText}>↻ Refresh Camera</Text>
|
|
371
|
+
</TouchableOpacity>
|
|
372
|
+
<TouchableOpacity style={[styles.fallbackRefreshButton, { marginTop: 15, backgroundColor: 'rgba(220, 38, 38, 0.8)', borderColor: '#DC2626' }]} onPress={() => setShowCamera(false)}>
|
|
373
|
+
<Text style={styles.fallbackRefreshText}>Cancel / Go Back</Text>
|
|
374
|
+
</TouchableOpacity>
|
|
375
|
+
</View>) : null}
|
|
347
376
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
377
|
+
{silentCaptureResult.error && !isBusy && Platform.OS !== 'web' ? (<View style={[styles.floatingErrorBanner, { zIndex: 10000 }]}>
|
|
378
|
+
<Text style={styles.floatingErrorText}>⚠️ {silentCaptureResult.error}</Text>
|
|
379
|
+
</View>) : null}
|
|
351
380
|
|
|
381
|
+
</View>
|
|
352
382
|
</View>
|
|
353
|
-
</View>
|
|
354
|
-
</View>);
|
|
383
|
+
</View>);
|
|
355
384
|
}
|
|
385
|
+
// --- PREVIEW RENDER ---
|
|
356
386
|
return (<View style={styles.root}>
|
|
357
387
|
<View style={styles.previewContainer}>
|
|
358
388
|
<ScrollView style={styles.previewItemContainer} showsVerticalScrollIndicator={false}>
|
|
@@ -363,16 +393,20 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
363
393
|
<Text style={{ fontSize: 14, color: '#666', textAlign: 'center', marginBottom: 16, lineHeight: 22 }}>
|
|
364
394
|
{getLocalizedText(component.instructions)}
|
|
365
395
|
</Text>
|
|
396
|
+
|
|
366
397
|
<View style={{ alignItems: 'center', justifyContent: 'center', flexDirection: "column", gap: 16 }}>
|
|
367
398
|
{silentCaptureResult?.error === 'TOO_FAR_AWAY' && (<View style={styles.warningBanner}>
|
|
368
399
|
<Text style={styles.warningText}>
|
|
369
400
|
{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."}
|
|
370
401
|
</Text>
|
|
371
402
|
</View>)}
|
|
403
|
+
|
|
404
|
+
{/* 🚨 PREVIEW CONTAINER WITH PLATFORM OVERRIDES */}
|
|
372
405
|
<View style={styles.imagePreviewWrapper}>
|
|
373
406
|
{capturedImages[currentSide]?.dir ? (<Image source={{ uri: capturedImages[currentSide].dir }} style={styles.previewImage}/>) : silentCaptureResult.path ? (<Image source={{ uri: silentCaptureResult.path }} style={styles.previewImage}/>) : null}
|
|
374
407
|
</View>
|
|
375
|
-
|
|
408
|
+
|
|
409
|
+
{!capturedImages[currentSide]?.dir && (<Button title={state.currentLanguage === "en" ? "Start Scanning" : "Commencer la numérisation"} onPress={() => { setShowCamera(true); refreshCamera(); actions.showCustomStepper(false); }} variant="primary" size="large" fullWidth/>)}
|
|
376
410
|
{capturedImages[currentSide]?.dir && (<>
|
|
377
411
|
<Button title={t('kyc.idCardCapture.retakeButton')} onPress={() => retakePicture(currentSide)} variant="outline" size="medium" fullWidth/>
|
|
378
412
|
<Button title={t('common.next')} onPress={() => {
|
|
@@ -385,6 +419,7 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
385
419
|
}
|
|
386
420
|
else {
|
|
387
421
|
setShowCamera(true);
|
|
422
|
+
refreshCamera();
|
|
388
423
|
setCurrentSide('back');
|
|
389
424
|
setSilentCaptureResult({ success: false, isAnalyzing: false, path: '', error: '', templatePath: undefined });
|
|
390
425
|
setIsProcessingCapture(false);
|
|
@@ -401,38 +436,63 @@ export const IDCardCapture = ({ component, value = {}, onValueChange, error, lan
|
|
|
401
436
|
const styles = StyleSheet.create({
|
|
402
437
|
root: {
|
|
403
438
|
flex: 1, width: '100%', backgroundColor: 'transparent', alignSelf: 'center',
|
|
404
|
-
...
|
|
439
|
+
...Platform.select({
|
|
440
|
+
web: { minHeight: '85vh', justifyContent: 'center', alignItems: 'center', backdropFilter: 'blur(8px)' },
|
|
441
|
+
})
|
|
405
442
|
},
|
|
406
443
|
cameraWrapper: {
|
|
407
|
-
width: '100%', backgroundColor: '#
|
|
408
|
-
...
|
|
444
|
+
width: '100%', backgroundColor: '#000000', overflow: 'hidden',
|
|
445
|
+
...Platform.select({
|
|
446
|
+
web: { maxWidth: 550, height: '80vh', minHeight: 600, borderRadius: 24, shadowColor: '#000', shadowOffset: { width: 0, height: 20 }, shadowOpacity: 0.25, shadowRadius: 35, elevation: 24 },
|
|
447
|
+
default: { flex: 1 }
|
|
448
|
+
})
|
|
409
449
|
},
|
|
410
450
|
headerContainer: {
|
|
411
451
|
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 18, backgroundColor: '#FFFFFF', borderBottomWidth: 1, borderBottomColor: '#F1F5F9', zIndex: 10,
|
|
412
|
-
...
|
|
452
|
+
...Platform.select({ web: { display: 'flex' }, default: { display: 'none' } })
|
|
413
453
|
},
|
|
414
454
|
headerTitle: { fontSize: 18, fontWeight: '700', color: '#0F172A', },
|
|
415
455
|
stepBadge: { backgroundColor: '#F1F5F9', paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20, },
|
|
416
456
|
stepText: { fontSize: 13, fontWeight: '600', color: '#64748B', },
|
|
417
457
|
cameraFeedContainer: { flex: 1, position: 'relative', backgroundColor: '#000', },
|
|
418
458
|
camera: { flex: 1, },
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
},
|
|
422
|
-
|
|
459
|
+
// MOBILE: Escape Hatch layout
|
|
460
|
+
escapeHatchContainer: { position: 'absolute', bottom: 40, left: 0, right: 0, alignItems: 'center', justifyContent: 'center', zIndex: 99999 },
|
|
461
|
+
fallbackRefreshButton: { backgroundColor: 'rgba(0, 0, 0, 0.8)', borderWidth: 1, borderColor: 'rgba(255, 255, 255, 0.5)', paddingVertical: 12, paddingHorizontal: 24, borderRadius: 24, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 5, elevation: 8 },
|
|
462
|
+
fallbackRefreshText: { color: '#FFFFFF', fontWeight: 'bold', fontSize: 16 },
|
|
463
|
+
// WEB: Control Bar layout
|
|
464
|
+
webBottomControlBar: { position: 'absolute', bottom: 0, left: 0, right: 0, paddingHorizontal: 20, paddingBottom: 20, paddingTop: 20, backgroundColor: 'rgba(0,0,0,0.6)', zIndex: 99999 },
|
|
465
|
+
webActionButtonsRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 12, marginTop: 12 },
|
|
466
|
+
webPrimaryButton: { flex: 1, backgroundColor: '#2DBD60', paddingVertical: 14, borderRadius: 12, alignItems: 'center' },
|
|
467
|
+
webPrimaryButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16 },
|
|
468
|
+
webSecondaryButton: { flex: 1, backgroundColor: 'rgba(255,255,255,0.2)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.4)', paddingVertical: 14, borderRadius: 12, alignItems: 'center' },
|
|
469
|
+
webSecondaryButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16 },
|
|
470
|
+
// WEB: Flip Controls
|
|
471
|
+
webTopControls: { position: 'absolute', top: 20, right: 20, zIndex: 9999 },
|
|
472
|
+
webFlipButton: { backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, borderWidth: 1, borderColor: 'rgba(255,255,255,0.3)' },
|
|
473
|
+
webFlipText: { color: 'white', fontWeight: 'bold', fontSize: 14 },
|
|
423
474
|
previewContainer: {
|
|
424
475
|
width: '100%', backgroundColor: 'white', borderRadius: 12, paddingVertical: 24, paddingHorizontal: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.1, shadowRadius: 12, elevation: 8,
|
|
425
|
-
...
|
|
476
|
+
...Platform.select({ web: { alignSelf: 'center', maxWidth: 600 }, default: { margin: 10, width: '95%' } })
|
|
426
477
|
},
|
|
427
478
|
previewItemContainer: { flexGrow: 1, },
|
|
428
479
|
title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
|
|
429
480
|
description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
|
|
430
481
|
sideContainer: { marginBottom: 24 },
|
|
431
482
|
sideTitle: { fontSize: 25, fontWeight: 'bold', color: '#000', marginBottom: 12, textAlign: 'center' },
|
|
432
|
-
imagePreviewWrapper: {
|
|
483
|
+
imagePreviewWrapper: {
|
|
484
|
+
width: '100%', borderRadius: 12, padding: 1, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.18, shadowRadius: 8, elevation: 8, backgroundColor: '#f0f0f0',
|
|
485
|
+
...Platform.select({
|
|
486
|
+
web: { aspectRatio: 1.59, height: 'auto' }, // 🚨 Perfect ID ratio on web
|
|
487
|
+
default: { height: 220 } // 🚨 Strict original height on mobile
|
|
488
|
+
})
|
|
489
|
+
},
|
|
433
490
|
previewImage: { width: '100%', height: '100%', borderRadius: 12, resizeMode: 'cover' },
|
|
434
491
|
floatingErrorBanner: {
|
|
435
|
-
|
|
492
|
+
backgroundColor: '#FEF2F2', borderWidth: 1, borderColor: '#FCA5A5', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 12, alignItems: 'center', justifyContent: 'center', width: '100%',
|
|
493
|
+
...Platform.select({
|
|
494
|
+
default: { position: 'absolute', top: Platform.OS === 'ios' ? 90 : 70, left: 24, right: 24, zIndex: 10000 }
|
|
495
|
+
})
|
|
436
496
|
},
|
|
437
497
|
floatingErrorText: { color: '#991B1B', fontSize: 14, fontWeight: '700', textAlign: 'center' },
|
|
438
498
|
processingOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center', zIndex: 9999 },
|
|
@@ -442,40 +502,6 @@ const styles = StyleSheet.create({
|
|
|
442
502
|
errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
|
|
443
503
|
topAnalyzingPillContainer: { position: 'absolute', top: Platform.OS === 'android' ? 60 : 50, left: 0, right: 0, alignItems: 'center', zIndex: 100 },
|
|
444
504
|
topAnalyzingPill: { flexDirection: 'row', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.6)', paddingVertical: 8, paddingHorizontal: 16, borderRadius: 20, gap: 8 },
|
|
445
|
-
analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' }
|
|
446
|
-
escapeHatchContainer: {
|
|
447
|
-
position: 'absolute',
|
|
448
|
-
bottom: 40,
|
|
449
|
-
left: 0,
|
|
450
|
-
right: 0,
|
|
451
|
-
alignItems: 'center',
|
|
452
|
-
justifyContent: 'center',
|
|
453
|
-
zIndex: 99999, // Guarantees it is the top-most element
|
|
454
|
-
},
|
|
455
|
-
fallbackRefreshButton: {
|
|
456
|
-
backgroundColor: 'rgba(0, 0, 0, 0.8)', // Darker so it's visible on white or black
|
|
457
|
-
borderWidth: 1,
|
|
458
|
-
borderColor: 'rgba(255, 255, 255, 0.5)',
|
|
459
|
-
paddingVertical: 12,
|
|
460
|
-
paddingHorizontal: 24,
|
|
461
|
-
borderRadius: 24,
|
|
462
|
-
shadowColor: '#000',
|
|
463
|
-
shadowOffset: { width: 0, height: 4 },
|
|
464
|
-
shadowOpacity: 0.3,
|
|
465
|
-
shadowRadius: 5,
|
|
466
|
-
elevation: 8,
|
|
467
|
-
},
|
|
468
|
-
fallbackRefreshText: {
|
|
469
|
-
color: '#FFFFFF',
|
|
470
|
-
fontWeight: 'bold',
|
|
471
|
-
fontSize: 16,
|
|
472
|
-
},
|
|
473
|
-
absoluteFillObject: {
|
|
474
|
-
position: 'absolute',
|
|
475
|
-
top: 0,
|
|
476
|
-
left: 0,
|
|
477
|
-
right: 0,
|
|
478
|
-
bottom: 0,
|
|
479
|
-
},
|
|
505
|
+
analyzingPillText: { color: 'white', fontSize: 14, fontWeight: 'bold' }
|
|
480
506
|
});
|
|
481
507
|
//# sourceMappingURL=IDCardCapture.js.map
|