@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.
- package/build/package.json +1 -1
- package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +259 -63
- package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +222 -69
- 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 +160 -21
- package/build/src/components/KYCElements/PhoneVerificationTemplate.js.map +1 -1
- package/build/src/config/region_mapping.json +727 -0
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +3 -7
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +1 -2
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +106 -59
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/package.json +1 -1
- package/src/components/KYCElements/CountrySelection.tsx +300 -74
- package/src/components/KYCElements/IDCardCapture.tsx +310 -156
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +201 -29
- package/src/modules/api/CardAuthentification.ts +5 -8
- 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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
94
|
+
const getCurrentSideVerification = (currentSide, countryKey) => {
|
|
95
|
+
const rawDocType = countrySelectionData?.documentType;
|
|
96
|
+
if (!rawDocType || !countryKey) {
|
|
87
97
|
return { authMethod: [], mrzTypes: [], regionMapping: null, key: 'root' };
|
|
88
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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.
|
|
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.
|
|
244
|
-
<
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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: {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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: {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|