@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.
Files changed (76) hide show
  1. package/build/package.json +3 -1
  2. package/build/src/components/EnhancedCameraView.d.ts +7 -1
  3. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  4. package/build/src/components/EnhancedCameraView.js +88 -30
  5. package/build/src/components/EnhancedCameraView.js.map +1 -1
  6. package/build/src/components/EnhancedCameraView.web.d.ts +5 -1
  7. package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
  8. package/build/src/components/EnhancedCameraView.web.js +80 -295
  9. package/build/src/components/EnhancedCameraView.web.js.map +1 -1
  10. package/build/src/components/KYCElements/CameraCapture.js +1 -1
  11. package/build/src/components/KYCElements/CameraCapture.js.map +1 -1
  12. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  13. package/build/src/components/KYCElements/CountrySelection.js +38 -218
  14. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  15. package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  16. package/build/src/components/KYCElements/CountrySelectionTemplate.js +119 -26
  17. package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  18. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  19. package/build/src/components/KYCElements/IDCardCapture.js +512 -87
  20. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  21. package/build/src/components/KYCElements/VerificationProgressTemplate.d.ts.map +1 -1
  22. package/build/src/components/KYCElements/VerificationProgressTemplate.js +93 -51
  23. package/build/src/components/KYCElements/VerificationProgressTemplate.js.map +1 -1
  24. package/build/src/components/OverLay/type.d.ts +1 -0
  25. package/build/src/components/OverLay/type.d.ts.map +1 -1
  26. package/build/src/components/OverLay/type.js.map +1 -1
  27. package/build/src/components/WebBarcodeScanner.d.ts +9 -0
  28. package/build/src/components/WebBarcodeScanner.d.ts.map +1 -0
  29. package/build/src/components/WebBarcodeScanner.js +152 -0
  30. package/build/src/components/WebBarcodeScanner.js.map +1 -0
  31. package/build/src/config/KYCConfigs.js +2 -2
  32. package/build/src/config/KYCConfigs.js.map +1 -1
  33. package/build/src/config/region_mapping.js +14 -14
  34. package/build/src/config/region_mapping.js.map +1 -1
  35. package/build/src/config/region_mapping.json +0 -14
  36. package/build/src/modules/api/CardAuthentification.d.ts +2 -0
  37. package/build/src/modules/api/CardAuthentification.d.ts.map +1 -1
  38. package/build/src/modules/api/CardAuthentification.js +23 -40
  39. package/build/src/modules/api/CardAuthentification.js.map +1 -1
  40. package/build/src/modules/api/KYCService.d.ts +7 -2
  41. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  42. package/build/src/modules/api/KYCService.js +163 -57
  43. package/build/src/modules/api/KYCService.js.map +1 -1
  44. package/build/src/types/KYC.types.d.ts +1 -0
  45. package/build/src/types/KYC.types.d.ts.map +1 -1
  46. package/build/src/types/KYC.types.js.map +1 -1
  47. package/build/src/utils/aamvaParser.d.ts +62 -0
  48. package/build/src/utils/aamvaParser.d.ts.map +1 -0
  49. package/build/src/utils/aamvaParser.js +91 -0
  50. package/build/src/utils/aamvaParser.js.map +1 -0
  51. package/build/src/utils/cropByObb.d.ts.map +1 -1
  52. package/build/src/utils/cropByObb.js +4 -3
  53. package/build/src/utils/cropByObb.js.map +1 -1
  54. package/build/src/web/WebKYCEntry.d.ts +1 -1
  55. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  56. package/build/src/web/WebKYCEntry.js +105 -239
  57. package/build/src/web/WebKYCEntry.js.map +1 -1
  58. package/package.json +3 -1
  59. package/src/components/EnhancedCameraView.tsx +116 -42
  60. package/src/components/EnhancedCameraView.web.tsx +98 -338
  61. package/src/components/KYCElements/CameraCapture.tsx +1 -1
  62. package/src/components/KYCElements/CountrySelection.tsx +60 -240
  63. package/src/components/KYCElements/CountrySelectionTemplate.tsx +148 -29
  64. package/src/components/KYCElements/IDCardCapture.tsx +675 -142
  65. package/src/components/KYCElements/VerificationProgressTemplate.tsx +112 -65
  66. package/src/components/OverLay/type.ts +1 -1
  67. package/src/components/WebBarcodeScanner.tsx +273 -0
  68. package/src/config/KYCConfigs.ts +2 -2
  69. package/src/config/region_mapping.json +0 -14
  70. package/src/config/region_mapping.ts +14 -14
  71. package/src/modules/api/CardAuthentification.ts +44 -58
  72. package/src/modules/api/KYCService.ts +236 -82
  73. package/src/types/KYC.types.ts +1 -0
  74. package/src/utils/aamvaParser.ts +102 -0
  75. package/src/utils/cropByObb.ts +5 -3
  76. 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
- // Standard ISO 3166-2:CA codes for Canadian provinces and territories
27
- const CANADIAN_PROVINCES = ['AB', 'BC', 'MB', 'NB', 'NL', 'NT', 'NS', 'NU', 'ON', 'PE', 'QC', 'SK', 'YT'];
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
- if (regionsCheck.length === 0 && value.code === 'CA' && value.documentType !== 'passport') {
71
- regionsCheck = CANADIAN_PROVINCES;
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 countries = idCardConfig?.selectedCountries || config.allowed_countries || Object.keys(countryData);
96
- return countries
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 & { code?: string }, b: Country & { code?: string }) => a.name.localeCompare(b.name));
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 || !idCardConfig?.documentTypesByCountry) return [];
104
- return idCardConfig.documentTypesByCountry[selectedCountryCode] || [];
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
- regions = idCardConfig.regionsByCountry[selectedCountryCode][selectedDocumentType];
194
+ rawRegions = idCardConfig.regionsByCountry[selectedCountryCode][selectedDocumentType];
113
195
  }
114
196
 
115
-
116
- if (regions.length === 0 && selectedCountryCode === 'CA' && selectedDocumentType !== 'passport') {
117
- regions = CANADIAN_PROVINCES;
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
- return Array.isArray(regions) ? regions : [];
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 = (region: string) => {
143
- setSelectedRegion(region);
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
- {availableCountries.map((country: Country & { code?: string }) => (
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((region: string, index: number) => {
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 === regionLabel && styles.selectedOption
420
+ selectedRegion === regionObj.value && styles.selectedOption
307
421
  ]}
308
- onPress={() => handleRegionSelect(regionLabel)}
422
+ onPress={() => handleRegionSelect(regionObj.value)}
309
423
  >
310
424
  <View style={styles.optionContent}>
311
425
  <Text style={[
312
426
  styles.optionName,
313
- selectedRegion === regionLabel && styles.selectedOptionName
427
+ selectedRegion === regionObj.value && styles.selectedOptionName
314
428
  ]}>
315
- {regionLabel}
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 === regionLabel && (
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
- listContainer: { maxHeight: 400, marginBottom: 24 },
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
  });