@sanctum-key/react-native-sdk 1.0.23 → 1.0.25
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 +3 -1
- package/build/src/components/EnhancedCameraView.d.ts +7 -1
- package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.js +88 -30
- package/build/src/components/EnhancedCameraView.js.map +1 -1
- package/build/src/components/EnhancedCameraView.web.d.ts +5 -1
- package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/src/components/EnhancedCameraView.web.js +80 -295
- package/build/src/components/EnhancedCameraView.web.js.map +1 -1
- package/build/src/components/KYCElements/CameraCapture.js +1 -1
- package/build/src/components/KYCElements/CameraCapture.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +38 -218
- package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.js +119 -26
- package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +512 -87
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/VerificationProgressTemplate.js +93 -51
- package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
- package/build/src/components/OverLay/type.d.ts +1 -0
- package/build/src/components/OverLay/type.d.ts.map +1 -1
- package/build/src/components/OverLay/type.js.map +1 -1
- package/build/src/components/WebBarcodeScanner.d.ts +9 -0
- package/build/src/components/WebBarcodeScanner.d.ts.map +1 -0
- package/build/src/components/WebBarcodeScanner.js +152 -0
- package/build/src/components/WebBarcodeScanner.js.map +1 -0
- package/build/src/config/KYCConfigs.js +2 -2
- package/build/src/config/KYCConfigs.js.map +1 -1
- package/build/src/config/region_mapping.js +14 -14
- package/build/src/config/region_mapping.js.map +1 -1
- package/build/src/config/region_mapping.json +0 -14
- package/build/src/modules/api/CardAuthentification.d.ts +2 -0
- package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/src/modules/api/CardAuthentification.js +23 -40
- package/build/src/modules/api/CardAuthentification.js.map +1 -1
- package/build/src/modules/api/KYCService.d.ts +7 -2
- package/build/src/modules/api/KYCService.d.ts.map +1 -1
- package/build/src/modules/api/KYCService.js +163 -57
- package/build/src/modules/api/KYCService.js.map +1 -1
- package/build/src/types/KYC.types.d.ts +1 -0
- package/build/src/types/KYC.types.d.ts.map +1 -1
- package/build/src/types/KYC.types.js.map +1 -1
- package/build/src/utils/aamvaParser.d.ts +62 -0
- package/build/src/utils/aamvaParser.d.ts.map +1 -0
- package/build/src/utils/aamvaParser.js +91 -0
- package/build/src/utils/aamvaParser.js.map +1 -0
- package/build/src/utils/cropByObb.d.ts.map +1 -1
- package/build/src/utils/cropByObb.js +4 -3
- package/build/src/utils/cropByObb.js.map +1 -1
- package/build/src/web/WebKYCEntry.d.ts +1 -1
- package/build/src/web/WebKYCEntry.d.ts.map +1 -1
- package/build/src/web/WebKYCEntry.js +105 -239
- package/build/src/web/WebKYCEntry.js.map +1 -1
- package/package.json +3 -1
- package/src/components/EnhancedCameraView.tsx +116 -42
- package/src/components/EnhancedCameraView.web.tsx +98 -338
- package/src/components/KYCElements/CameraCapture.tsx +1 -1
- package/src/components/KYCElements/CountrySelection.tsx +60 -240
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +148 -29
- package/src/components/KYCElements/IDCardCapture.tsx +675 -142
- package/src/components/KYCElements/VerificationProgressTemplate.tsx +112 -65
- package/src/components/OverLay/type.ts +1 -1
- package/src/components/WebBarcodeScanner.tsx +273 -0
- package/src/config/KYCConfigs.ts +2 -2
- package/src/config/region_mapping.json +0 -14
- package/src/config/region_mapping.ts +14 -14
- package/src/modules/api/CardAuthentification.ts +44 -58
- package/src/modules/api/KYCService.ts +236 -82
- package/src/types/KYC.types.ts +1 -0
- package/src/utils/aamvaParser.ts +102 -0
- package/src/utils/cropByObb.ts +5 -3
- package/src/web/WebKYCEntry.tsx +125 -301
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { useState, useMemo, useEffect } from 'react';
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, ScrollView, TextInput } from 'react-native';
|
|
3
3
|
import { TemplateComponent, CountrySelectionConfig, LocalizedText, Country, IDCardConfig } from '../../types/KYC.types';
|
|
4
4
|
import { countryMapping } from '../../config/region_mapping';
|
|
5
5
|
import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
|
|
6
6
|
import { Button } from '../ui/Button';
|
|
7
7
|
import { countryData } from '../../config/countriesData';
|
|
8
|
+
import REGION_MAPPING from '../../config/region_mapping.json';
|
|
8
9
|
|
|
9
10
|
interface CountrySelectionTemplateProps {
|
|
10
11
|
component: TemplateComponent;
|
|
@@ -23,8 +24,33 @@ type ExtendedIDCardConfig = IDCardConfig & {
|
|
|
23
24
|
instructionsByDocumentType?: Record<string, LocalizedText>;
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
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' };
|
|
28
|
+
|
|
29
|
+
const jsonDocMap: Record<string, string> = {
|
|
30
|
+
'nationalId': 'identity_card',
|
|
31
|
+
'national_id': 'identity_card',
|
|
32
|
+
'passport': 'passport',
|
|
33
|
+
'driversLicense': 'drivers_licence',
|
|
34
|
+
'drivers_licence': 'drivers_licence',
|
|
35
|
+
'residencePermit': 'permanent_residence',
|
|
36
|
+
'healthInsuranceCard': 'health_insurance_card'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const SUB_REGION_CODES: Record<string, string> = {
|
|
40
|
+
"Alberta": "AB",
|
|
41
|
+
"British Columbia": "BC",
|
|
42
|
+
"Manitoba": "MB",
|
|
43
|
+
"New Brunswick": "NB",
|
|
44
|
+
"Newfoundland and Labrador": "NL",
|
|
45
|
+
"Northwest Territories": "NT",
|
|
46
|
+
"Nova Scotia": "NS",
|
|
47
|
+
"Nunavut": "NU",
|
|
48
|
+
"Ontario": "ON",
|
|
49
|
+
"Prince Edward Island": "PE",
|
|
50
|
+
"Quebec": "QC",
|
|
51
|
+
"Saskatchewan": "SK",
|
|
52
|
+
"Yukon": "YT"
|
|
53
|
+
};
|
|
28
54
|
|
|
29
55
|
export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> = ({
|
|
30
56
|
component,
|
|
@@ -55,20 +81,34 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
55
81
|
const [selectedCountryCode, setSelectedCountryCode] = useState<string | null>(value?.code || null);
|
|
56
82
|
const [selectedDocumentType, setSelectedDocumentType] = useState<string | null>(value?.documentType || null);
|
|
57
83
|
const [selectedRegion, setSelectedRegion] = useState<string | null>(value?.region || null);
|
|
84
|
+
|
|
85
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
58
86
|
|
|
59
87
|
useEffect(() => {
|
|
60
88
|
if (value?.code && value?.documentType) {
|
|
61
89
|
setSelectedCountryCode(value.code);
|
|
62
90
|
setSelectedDocumentType(value.documentType);
|
|
63
91
|
setSelectedRegion(value.region || null);
|
|
64
|
-
|
|
92
|
+
|
|
65
93
|
if (value.region) {
|
|
66
94
|
setCurrentStep('region');
|
|
67
95
|
} else {
|
|
68
|
-
// Check config regions OR enforce Canadian provinces for non-passports
|
|
69
96
|
let regionsCheck = idCardConfig?.regionsByCountry?.[value.code]?.[value.documentType] || [];
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
|
|
98
|
+
if (regionsCheck.length === 0) {
|
|
99
|
+
const rawCountryName = ISO_TO_COUNTRY_NAME[value.code] || value.code;
|
|
100
|
+
const searchDocType = jsonDocMap[value.documentType] || value.documentType;
|
|
101
|
+
const baseMapping = (REGION_MAPPING as any).regionMapping || REGION_MAPPING;
|
|
102
|
+
|
|
103
|
+
let countryMapping = baseMapping[rawCountryName];
|
|
104
|
+
if (!countryMapping) {
|
|
105
|
+
const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase());
|
|
106
|
+
if (foundKey) countryMapping = baseMapping[foundKey];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (countryMapping && countryMapping[searchDocType]) {
|
|
110
|
+
regionsCheck = Object.keys(countryMapping[searchDocType]).filter(k => k !== 'root');
|
|
111
|
+
}
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
if (Array.isArray(regionsCheck) && regionsCheck.length > 0) {
|
|
@@ -92,32 +132,90 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
92
132
|
}, []);
|
|
93
133
|
|
|
94
134
|
const availableCountries = useMemo(() => {
|
|
95
|
-
const
|
|
96
|
-
|
|
135
|
+
const explicitlyAllowed = idCardConfig?.selectedCountries || config.allowed_countries || [];
|
|
136
|
+
|
|
137
|
+
const configuredCodes = new Set(explicitlyAllowed);
|
|
138
|
+
|
|
139
|
+
return Array.from(configuredCodes)
|
|
97
140
|
.filter((code: string) => countryData[code])
|
|
98
141
|
.map((code: string) => ({ code, ...countryData[code] }))
|
|
99
|
-
.sort((a: Country
|
|
142
|
+
.sort((a: Country, b: Country) => a.name.localeCompare(b.name));
|
|
100
143
|
}, [idCardConfig?.selectedCountries, config.allowed_countries]);
|
|
101
144
|
|
|
145
|
+
const filteredCountries = useMemo(() => {
|
|
146
|
+
if (!searchQuery.trim()) return availableCountries;
|
|
147
|
+
const cleanQuery = searchQuery.toLowerCase().trim();
|
|
148
|
+
|
|
149
|
+
return availableCountries.filter(country =>
|
|
150
|
+
country.name.toLowerCase().includes(cleanQuery) ||
|
|
151
|
+
country.name_en.toLowerCase().includes(cleanQuery) ||
|
|
152
|
+
country.code.toLowerCase().includes(cleanQuery)
|
|
153
|
+
);
|
|
154
|
+
}, [searchQuery, availableCountries]);
|
|
155
|
+
|
|
102
156
|
const availableDocumentTypes = useMemo(() => {
|
|
103
|
-
if (!selectedCountryCode
|
|
104
|
-
|
|
157
|
+
if (!selectedCountryCode) return [];
|
|
158
|
+
|
|
159
|
+
// Check if custom layout configuration explicitly defined items for it
|
|
160
|
+
if (idCardConfig?.documentTypesByCountry?.[selectedCountryCode]) {
|
|
161
|
+
return idCardConfig.documentTypesByCountry[selectedCountryCode];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Default Fallback: Query mapping matrix dynamically
|
|
165
|
+
const rawCountryName = ISO_TO_COUNTRY_NAME[selectedCountryCode] || selectedCountryCode;
|
|
166
|
+
const baseMapping = (REGION_MAPPING as any).regionMapping || REGION_MAPPING;
|
|
167
|
+
|
|
168
|
+
let countryMapping = baseMapping[rawCountryName];
|
|
169
|
+
if (!countryMapping) {
|
|
170
|
+
const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase());
|
|
171
|
+
if (foundKey) countryMapping = baseMapping[foundKey];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (countryMapping) {
|
|
175
|
+
const inverseDocMap: Record<string, string> = {
|
|
176
|
+
'identity_card': 'nationalId',
|
|
177
|
+
'passport': 'passport',
|
|
178
|
+
'drivers_licence': 'driversLicense',
|
|
179
|
+
'permanent_residence': 'residencePermit',
|
|
180
|
+
'health_insurance_card': 'healthInsuranceCard'
|
|
181
|
+
};
|
|
182
|
+
return Object.keys(countryMapping).map(key => inverseDocMap[key] || key);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return ['nationalId', 'passport', 'driversLicense'];
|
|
105
186
|
}, [selectedCountryCode, idCardConfig?.documentTypesByCountry]);
|
|
106
187
|
|
|
107
188
|
const availableRegions = useMemo(() => {
|
|
108
189
|
if (!selectedCountryCode || !selectedDocumentType) return [];
|
|
190
|
+
|
|
191
|
+
let rawRegions: string[] = [];
|
|
109
192
|
|
|
110
|
-
let regions: string[] = [];
|
|
111
193
|
if (idCardConfig?.regionsByCountry?.[selectedCountryCode]?.[selectedDocumentType]) {
|
|
112
|
-
|
|
194
|
+
rawRegions = idCardConfig.regionsByCountry[selectedCountryCode][selectedDocumentType];
|
|
113
195
|
}
|
|
114
196
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
197
|
+
if (rawRegions.length === 0) {
|
|
198
|
+
const rawCountryName = ISO_TO_COUNTRY_NAME[selectedCountryCode] || selectedCountryCode;
|
|
199
|
+
const searchDocType = jsonDocMap[selectedDocumentType] || selectedDocumentType;
|
|
200
|
+
const baseMapping = (REGION_MAPPING as any).regionMapping || REGION_MAPPING;
|
|
201
|
+
|
|
202
|
+
let countryMapping = baseMapping[rawCountryName];
|
|
203
|
+
if (!countryMapping) {
|
|
204
|
+
const foundKey = Object.keys(baseMapping).find(k => k.toLowerCase() === rawCountryName.toLowerCase());
|
|
205
|
+
if (foundKey) countryMapping = baseMapping[foundKey];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (countryMapping && countryMapping[searchDocType]) {
|
|
209
|
+
rawRegions = Object.keys(countryMapping[searchDocType]).filter(k => k !== 'root');
|
|
210
|
+
}
|
|
118
211
|
}
|
|
119
212
|
|
|
120
|
-
|
|
213
|
+
if (!Array.isArray(rawRegions)) return [];
|
|
214
|
+
|
|
215
|
+
return rawRegions.sort((a, b) => a.localeCompare(b)).map(region => ({
|
|
216
|
+
label: region,
|
|
217
|
+
value: SUB_REGION_CODES[region] || region
|
|
218
|
+
}));
|
|
121
219
|
}, [selectedCountryCode, selectedDocumentType, idCardConfig?.regionsByCountry]);
|
|
122
220
|
|
|
123
221
|
const needsRegion = useMemo(() => {
|
|
@@ -139,8 +237,8 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
139
237
|
setSelectedRegion(null);
|
|
140
238
|
};
|
|
141
239
|
|
|
142
|
-
const handleRegionSelect = (
|
|
143
|
-
setSelectedRegion(
|
|
240
|
+
const handleRegionSelect = (regionValue: string) => {
|
|
241
|
+
setSelectedRegion(regionValue);
|
|
144
242
|
};
|
|
145
243
|
|
|
146
244
|
const finalizeSelection = (countryCode: string, docType: string, region: string | null) => {
|
|
@@ -171,6 +269,7 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
171
269
|
setCurrentStep('country');
|
|
172
270
|
setSelectedDocumentType(null);
|
|
173
271
|
setSelectedRegion(null);
|
|
272
|
+
setSearchQuery('');
|
|
174
273
|
} else if (currentStep === 'country') {
|
|
175
274
|
actions.previousComponent();
|
|
176
275
|
}
|
|
@@ -197,8 +296,16 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
197
296
|
const renderCountrySelection = () => {
|
|
198
297
|
return (
|
|
199
298
|
<>
|
|
299
|
+
<TextInput
|
|
300
|
+
style={styles.searchBar}
|
|
301
|
+
placeholder={state.currentLanguage === "en" ? "🔍 Search country..." : "🔍 Rechercher un pays..."}
|
|
302
|
+
placeholderTextColor="#94A3B8"
|
|
303
|
+
value={searchQuery}
|
|
304
|
+
onChangeText={setSearchQuery}
|
|
305
|
+
autoCorrect={false}
|
|
306
|
+
/>
|
|
200
307
|
<ScrollView style={styles.listContainer} showsVerticalScrollIndicator={false}>
|
|
201
|
-
{
|
|
308
|
+
{filteredCountries.map((country: Country & { code?: string }) => (
|
|
202
309
|
<TouchableOpacity
|
|
203
310
|
key={country.code}
|
|
204
311
|
style={[
|
|
@@ -208,6 +315,7 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
208
315
|
onPress={() => handleCountrySelect(country.code!)}
|
|
209
316
|
>
|
|
210
317
|
<Text style={styles.flag}>{country.flag}</Text>
|
|
318
|
+
|
|
211
319
|
<View style={styles.optionContent}>
|
|
212
320
|
<Text style={[
|
|
213
321
|
styles.optionName,
|
|
@@ -217,6 +325,7 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
217
325
|
</Text>
|
|
218
326
|
<Text style={styles.optionCode}>{country.code}</Text>
|
|
219
327
|
</View>
|
|
328
|
+
|
|
220
329
|
{selectedCountryCode === country.code && (
|
|
221
330
|
<View style={[
|
|
222
331
|
styles.checkmark,
|
|
@@ -227,6 +336,12 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
227
336
|
)}
|
|
228
337
|
</TouchableOpacity>
|
|
229
338
|
))}
|
|
339
|
+
|
|
340
|
+
{filteredCountries.length === 0 && (
|
|
341
|
+
<Text style={styles.emptyText}>
|
|
342
|
+
{state.currentLanguage === "en" ? "No matching countries found." : "Aucun pays trouvé."}
|
|
343
|
+
</Text>
|
|
344
|
+
)}
|
|
230
345
|
</ScrollView>
|
|
231
346
|
</>
|
|
232
347
|
);
|
|
@@ -296,26 +411,28 @@ export const CountrySelectionTemplate: React.FC<CountrySelectionTemplateProps> =
|
|
|
296
411
|
return (
|
|
297
412
|
<>
|
|
298
413
|
<ScrollView style={styles.listContainer} showsVerticalScrollIndicator={false}>
|
|
299
|
-
{availableRegions.map((
|
|
300
|
-
const regionLabel = typeof region === 'string' ? region : String(region);
|
|
414
|
+
{availableRegions.map((regionObj, index: number) => {
|
|
301
415
|
return (
|
|
302
416
|
<TouchableOpacity
|
|
303
417
|
key={index}
|
|
304
418
|
style={[
|
|
305
419
|
styles.option,
|
|
306
|
-
selectedRegion ===
|
|
420
|
+
selectedRegion === regionObj.value && styles.selectedOption
|
|
307
421
|
]}
|
|
308
|
-
onPress={() => handleRegionSelect(
|
|
422
|
+
onPress={() => handleRegionSelect(regionObj.value)}
|
|
309
423
|
>
|
|
310
424
|
<View style={styles.optionContent}>
|
|
311
425
|
<Text style={[
|
|
312
426
|
styles.optionName,
|
|
313
|
-
selectedRegion ===
|
|
427
|
+
selectedRegion === regionObj.value && styles.selectedOptionName
|
|
314
428
|
]}>
|
|
315
|
-
{
|
|
429
|
+
{regionObj.label}
|
|
316
430
|
</Text>
|
|
431
|
+
{regionObj.value !== regionObj.label && (
|
|
432
|
+
<Text style={styles.optionCode}>{regionObj.value}</Text>
|
|
433
|
+
)}
|
|
317
434
|
</View>
|
|
318
|
-
{selectedRegion ===
|
|
435
|
+
{selectedRegion === regionObj.value && (
|
|
319
436
|
<View style={[
|
|
320
437
|
styles.checkmark,
|
|
321
438
|
{ backgroundColor: component.ui.themeColor as string || '#2DBD60' }
|
|
@@ -429,7 +546,8 @@ const styles = StyleSheet.create({
|
|
|
429
546
|
progressText: { fontSize: 14, color: '#666', fontWeight: '500' },
|
|
430
547
|
title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
|
|
431
548
|
description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
|
|
432
|
-
|
|
549
|
+
searchBar: { height: 52, backgroundColor: '#F1F5F9', borderRadius: 12, paddingHorizontal: 16, fontSize: 16, color: '#1E293B', marginBottom: 16, borderWidth: 1, borderColor: '#CBD5E1', fontWeight: '500' },
|
|
550
|
+
listContainer: { maxHeight: 360, marginBottom: 24 },
|
|
433
551
|
option: { flexDirection: 'row', alignItems: 'center', padding: 16, backgroundColor: '#F8F9FA', borderRadius: 12, marginBottom: 12, borderWidth: 2, borderColor: '#E9ECEF', minHeight: 64 },
|
|
434
552
|
selectedOption: { borderColor: '#2DBD60', backgroundColor: '#F0F9F0' },
|
|
435
553
|
flag: { fontSize: 32, marginRight: 16 },
|
|
@@ -443,4 +561,5 @@ const styles = StyleSheet.create({
|
|
|
443
561
|
infoText: { fontSize: 14, color: '#666', lineHeight: 20 },
|
|
444
562
|
buttonContainer: { flexDirection: 'row', marginTop: 24, gap: 12 },
|
|
445
563
|
errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
|
|
564
|
+
emptyText: { textAlign: 'center', color: '#64748B', fontSize: 16, marginTop: 24, fontWeight: '500' }
|
|
446
565
|
});
|