@test-web/react-native-sdk 1.0.0 → 2.1.0

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 (125) hide show
  1. package/README.md +543 -26
  2. package/assets/images/Chrome-logo.svg +1 -0
  3. package/assets/images/Firefox-logo.png +0 -0
  4. package/assets/images/IDM-logo.jpg +0 -0
  5. package/assets/images/MRZOverlay.png +0 -0
  6. package/assets/images/Safari-logo.png +0 -0
  7. package/assets/images/aadhar.png +0 -0
  8. package/assets/images/camera-bg.png +0 -0
  9. package/assets/images/card-overlay-back.png +0 -0
  10. package/assets/images/card-overlay.png +0 -0
  11. package/assets/images/card-scan-back-icon.jpg +0 -0
  12. package/assets/images/card-scan-front-icon.png +0 -0
  13. package/assets/images/card-scan-icon-aadhaar-1.png +0 -0
  14. package/assets/images/card-scan-icon-aadhaar-back.png +0 -0
  15. package/assets/images/card-scan-icon-aadhaar-scan-qr.png +0 -0
  16. package/assets/images/card-scan-icon-aadhaar.png +0 -0
  17. package/assets/images/card-scan-icon-can-pr.png +0 -0
  18. package/assets/images/card-scan-icon-default-back.png +0 -0
  19. package/assets/images/card-scan-icon-dl.png +0 -0
  20. package/assets/images/card-scan-icon-greencard-back.jpg +0 -0
  21. package/assets/images/card-scan-icon-greencard.jpg +0 -0
  22. package/assets/images/card-scan-icon-hc.png +0 -0
  23. package/assets/images/card-scan-icon-ni-argentina-back.jpg +0 -0
  24. package/assets/images/card-scan-icon-ni-argentina-old.png +0 -0
  25. package/assets/images/card-scan-icon-ni-argentina.jpg +0 -0
  26. package/assets/images/card-scan-icon-ni-barcode.jpg +0 -0
  27. package/assets/images/card-scan-icon-ni-brazil-back.jpg +0 -0
  28. package/assets/images/card-scan-icon-ni-brazil.jpg +0 -0
  29. package/assets/images/card-scan-icon-ni-dominican-republic-back.png +0 -0
  30. package/assets/images/card-scan-icon-ni-dominican-republic-front.png +0 -0
  31. package/assets/images/card-scan-icon-ni-dominican-republic-mrz.png +0 -0
  32. package/assets/images/card-scan-icon-ni-dominicanaRepublic-back.jpg +0 -0
  33. package/assets/images/card-scan-icon-ni-france-back.png +0 -0
  34. package/assets/images/card-scan-icon-ni-france-front.png +0 -0
  35. package/assets/images/card-scan-icon-ni-france-scan-mrz.png +0 -0
  36. package/assets/images/card-scan-icon-ni-germany-back.jpg +0 -0
  37. package/assets/images/card-scan-icon-ni-germany.jpg +0 -0
  38. package/assets/images/card-scan-icon-ni-paraguay-back.png +0 -0
  39. package/assets/images/card-scan-icon-ni-paraguay-front.png +0 -0
  40. package/assets/images/card-scan-icon-ni-paraguay-scan-mrz.png +0 -0
  41. package/assets/images/card-scan-icon-ni-uae-back.png +0 -0
  42. package/assets/images/card-scan-icon-ni-uae-front.png +0 -0
  43. package/assets/images/card-scan-icon-ni-uae-scan-mrz.png +0 -0
  44. package/assets/images/card-scan-icon-ni-uganda-front.png +0 -0
  45. package/assets/images/card-scan-icon-ni-uganda-scan-mrz.png +0 -0
  46. package/assets/images/card-scan-icon-ni-ukrain-back.png +0 -0
  47. package/assets/images/card-scan-icon-ni-ukrain-front.png +0 -0
  48. package/assets/images/card-scan-icon-ni-ukrain-scan-mrz.png +0 -0
  49. package/assets/images/card-scan-icon-ni.png +0 -0
  50. package/assets/images/card-scan-icon-old.jpg +0 -0
  51. package/assets/images/card-scan-icon-pan.png +0 -0
  52. package/assets/images/card-scan-icon-passport-card-back.jpg +0 -0
  53. package/assets/images/card-scan-icon-passport-card.jpg +0 -0
  54. package/assets/images/card-scan-icon-passport-old.png +0 -0
  55. package/assets/images/card-scan-icon-passport.png +0 -0
  56. package/assets/images/card-scan-icon-pr.png +0 -0
  57. package/assets/images/card-scan-icon.jpg +0 -0
  58. package/assets/images/check.png +0 -0
  59. package/assets/images/chrome-animation-GPS-permissions-setting.gif +0 -0
  60. package/assets/images/chrome-animation-camera-permissions-setting.gif +0 -0
  61. package/assets/images/denied.png +0 -0
  62. package/assets/images/dl.png +0 -0
  63. package/assets/images/driver-license.png +0 -0
  64. package/assets/images/firefox-animation-permissions-setting.gif +0 -0
  65. package/assets/images/flashlight_on.png +0 -0
  66. package/assets/images/gallery.png +0 -0
  67. package/assets/images/greencard.png +0 -0
  68. package/assets/images/header.jpg +0 -0
  69. package/assets/images/health-card.png +0 -0
  70. package/assets/images/ic_camera_front_white_36px.svg +4 -0
  71. package/assets/images/ic_camera_rear_white_36px.svg +4 -0
  72. package/assets/images/ic_fullscreen_exit_white_48px.svg +4 -0
  73. package/assets/images/ic_fullscreen_white_48px.svg +4 -0
  74. package/assets/images/ic_photo_camera_white_48px.svg +5 -0
  75. package/assets/images/id-card.png +0 -0
  76. package/assets/images/idcardimg.png +0 -0
  77. package/assets/images/idmval-barcode.png +0 -0
  78. package/assets/images/information.png +0 -0
  79. package/assets/images/loader.gif +0 -0
  80. package/assets/images/loading.svg +1 -0
  81. package/assets/images/logo.jpg +0 -0
  82. package/assets/images/logo.png +0 -0
  83. package/assets/images/mrz-back.png +0 -0
  84. package/assets/images/mrz-ni.png +0 -0
  85. package/assets/images/mrz.png +0 -0
  86. package/assets/images/mrz1.png +0 -0
  87. package/assets/images/mrz_old.png +0 -0
  88. package/assets/images/mrz_small.png +0 -0
  89. package/assets/images/national-id.png +0 -0
  90. package/assets/images/nationalID.png +0 -0
  91. package/assets/images/no-wifi.png +0 -0
  92. package/assets/images/passport-card.png +0 -0
  93. package/assets/images/passport.png +0 -0
  94. package/assets/images/permit-card.png +0 -0
  95. package/assets/images/photo-overlay.png +0 -0
  96. package/assets/images/placeholder.jpg +0 -0
  97. package/assets/images/qr-code.png +0 -0
  98. package/assets/images/right-checkmark.jpg +0 -0
  99. package/assets/images/selfie.jpg +0 -0
  100. package/assets/images/showing-sec.png +0 -0
  101. package/assets/images/spinner.gif +0 -0
  102. package/assets/images/splash-icon.png +0 -0
  103. package/assets/images/take-selfie.jpg +0 -0
  104. package/assets/images/torch_off.png +0 -0
  105. package/assets/images/warning-icon.jpg +0 -0
  106. package/assets/images/warning-stick.jpg +0 -0
  107. package/assets/images/wrong-checkmark.jpg +0 -0
  108. package/package.json +35 -8
  109. package/src/apis/index.ts +338 -17
  110. package/src/components/common/Loader.tsx +16 -2
  111. package/src/config/apiConfig.ts +6 -0
  112. package/src/index.tsx +123 -7
  113. package/src/screens/BarcodeCapture.tsx +64 -12
  114. package/src/screens/DocumentCaptureBack.tsx +133 -24
  115. package/src/screens/DocumentCaptureFront.tsx +146 -24
  116. package/src/screens/MrzCapture.tsx +77 -12
  117. package/src/screens/SelectDocuments.tsx +37 -56
  118. package/src/screens/SelfieCapture.tsx +114 -18
  119. package/src/screens/ThankYou.tsx +34 -1
  120. package/src/services/getUserData.ts +111 -0
  121. package/src/types/IDMConf.ts +81 -7
  122. package/src/utils/base64.ts +25 -0
  123. package/src/utils/flowManager.ts +138 -0
  124. package/src/utils/imageProcessor.ts +96 -0
  125. package/src/utils/index.ts +18 -0
@@ -1,54 +1,176 @@
1
- import React, { useState } from 'react';
2
- import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { View, Text, TouchableOpacity, StyleSheet, Image, Alert } from 'react-native';
3
3
  import { useNavigation } from '@react-navigation/native';
4
4
  import getStyles from '../styles/DocumentCaptureStyles';
5
5
  import { useTheme } from '../context/ThemeContext';
6
6
  import { useOrientation } from '../hooks/useOrientation';
7
+ import { useIDM } from '../context/IDMConfigurationContext';
7
8
  import Loader from '../components/common/Loader';
9
+ import { captureDocumentFront } from '../apis';
10
+ import { processImageToBase64, validateImagePath } from '../utils/imageProcessor';
11
+ import { createFlowManager } from '../utils/flowManager';
8
12
 
9
13
  export default function DocumentCaptureFront() {
10
14
  const { theme } = useTheme();
15
+ const { idmConf, setIDMConf } = useIDM();
11
16
  const orientation = useOrientation();
12
17
  const styles = getStyles(theme, orientation);
13
18
  const navigation = useNavigation();
14
19
  const [loading, setLoading] = useState(false);
20
+ const [hasPermission, setHasPermission] = useState(false);
21
+ const [device, setDevice] = useState<any>(null);
22
+ const cameraRef = useRef<any>(null);
23
+
24
+ // Initialize camera
25
+ useEffect(() => {
26
+ const initializeCamera = async () => {
27
+ try {
28
+ // Try to import camera libraries
29
+ const { Camera, useCameraDevice, useCameraPermission } = require('react-native-vision-camera');
30
+
31
+ // Check permission
32
+ const { hasPermission: hasPerm, requestPermission } = useCameraPermission();
33
+
34
+ if (!hasPerm) {
35
+ const granted = await requestPermission();
36
+ if (!granted) {
37
+ navigation.navigate('CameraPermission' as never);
38
+ return;
39
+ }
40
+ }
41
+
42
+ setHasPermission(true);
43
+
44
+ // Get back camera device
45
+ const backDevice = useCameraDevice('back');
46
+ if (!backDevice) {
47
+ navigation.navigate('NoCameraFound' as never);
48
+ return;
49
+ }
50
+
51
+ setDevice(backDevice);
52
+ } catch (error) {
53
+ console.warn('Camera initialization failed:', error);
54
+ // Fallback for testing without camera
55
+ setHasPermission(true);
56
+ }
57
+ };
58
+
59
+ initializeCamera();
60
+ }, [navigation]);
61
+
62
+ // Determine next navigation step based on metadata
63
+ const handleNextStep = () => {
64
+ const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
65
+
66
+ if (!metaData) {
67
+ navigation.navigate('ThankYou' as never);
68
+ return;
69
+ }
70
+
71
+ const flowManager = createFlowManager(metaData);
72
+ const nextScreen = flowManager.getNextScreenAfterFront();
73
+ navigation.navigate(nextScreen as never);
74
+ };
15
75
 
16
76
  const takePhoto = async () => {
77
+ if (!cameraRef.current) {
78
+ // Fallback for testing without camera
79
+ Alert.alert('Camera Not Available', 'Camera is not available in this environment.');
80
+ return;
81
+ }
82
+
17
83
  try {
18
84
  setLoading(true);
19
- // Static implementation - simulate photo capture
20
- setTimeout(() => {
85
+
86
+ const photo = await cameraRef.current.takePhoto();
87
+
88
+ if (!validateImagePath(photo?.path)) {
89
+ Alert.alert('Error', 'Failed to capture image');
21
90
  setLoading(false);
22
- // Navigate to BackDocumentAdvice after capturing front
23
- navigation.navigate('BackDocumentAdvice' as never);
24
- }, 1000);
25
- } catch (error) {
91
+ return;
92
+ }
93
+
94
+ // Process image to base64
95
+ const base64Image = await processImageToBase64(photo.path);
96
+
97
+ // Prepare photo data for API
98
+ const photoData = {
99
+ latitude: String(idmConf.userDetails?.location?.coords?.latitude || ''),
100
+ longitude: String(idmConf.userDetails?.location?.coords?.longitude || ''),
101
+ token: String(idmConf.verificationCode || ''),
102
+ persistLoc: 'false',
103
+ metadataIndex: String(idmConf.selectedCountryDetails?.selectedMetaData?.id || ''),
104
+ file: base64Image,
105
+ };
106
+
107
+ // Call API to capture document front
108
+ const result = await captureDocumentFront(idmConf, photoData);
109
+
110
+ if (result.status) {
111
+ // Update configuration with response
112
+ setIDMConf({
113
+ ...idmConf,
114
+ requestConfiguration: result,
115
+ });
116
+
117
+ // Navigate to next step based on metadata
118
+ handleNextStep();
119
+ } else {
120
+ // Handle error - navigate to retake screen
121
+ navigation.navigate('RetakeSelfie' as never, {
122
+ errorMessage: result.errorMessage || 'Failed to process document',
123
+ });
124
+ }
125
+ } catch (error: any) {
26
126
  console.error('Error taking photo:', error);
127
+ Alert.alert('Error', 'Failed to capture or process the image.');
128
+ } finally {
27
129
  setLoading(false);
28
130
  }
29
131
  };
30
132
 
133
+ // Render camera if available
134
+ const renderCamera = () => {
135
+ try {
136
+ const { Camera } = require('react-native-vision-camera');
137
+
138
+ if (hasPermission && device) {
139
+ return (
140
+ <Camera
141
+ ref={cameraRef}
142
+ style={StyleSheet.absoluteFill}
143
+ device={device}
144
+ isActive={true}
145
+ photo={true}
146
+ />
147
+ );
148
+ }
149
+ } catch (error) {
150
+ console.warn('Camera render failed:', error);
151
+ }
152
+
153
+ // Fallback placeholder
154
+ return (
155
+ <View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
156
+ <Text style={{ color: '#fff', textAlign: 'center', marginTop: 100 }}>
157
+ Camera Preview
158
+ </Text>
159
+ </View>
160
+ );
161
+ };
162
+
31
163
  return (
32
164
  <View style={styles.container}>
33
165
  <Text style={styles.topLabel}>Document Front Side</Text>
34
166
 
35
- {/* Camera placeholder */}
36
- <View style={StyleSheet.absoluteFill}>
37
- <View
38
- style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}
39
- ></View>
40
- </View>
167
+ {renderCamera()}
41
168
 
42
- {/* Card overlay placeholder */}
43
- <View
44
- style={[
45
- styles.cardoverlay,
46
- {
47
- borderWidth: 2,
48
- borderColor: '#fff',
49
- backgroundColor: 'transparent',
50
- },
51
- ]}
169
+ {/* Card overlay */}
170
+ <Image
171
+ source={require('../../assets/images/card-overlay.png')}
172
+ style={styles.cardoverlay}
173
+ resizeMode="cover"
52
174
  />
53
175
 
54
176
  <Text style={styles.bottomLabel}>
@@ -1,36 +1,88 @@
1
- import React, { useState } from 'react';
2
- import { View, Text, StyleSheet } from 'react-native';
1
+ import React, { useState, useEffect } from 'react';
2
+ import { View, Text, StyleSheet, Alert } from 'react-native';
3
3
  import { useNavigation } from '@react-navigation/native';
4
4
  import { useTheme } from '../context/ThemeContext';
5
5
  import { useOrientation } from '../hooks/useOrientation';
6
+ import { useIDM } from '../context/IDMConfigurationContext';
6
7
  import getStyles from '../styles/ScannerStyles';
7
8
  import Loader from '../components/common/Loader';
9
+ import { captureMRZ } from '../apis';
10
+ import { createFlowManager } from '../utils/flowManager';
8
11
 
9
12
  export default function MrzCapture() {
10
13
  const { theme } = useTheme();
14
+ const { idmConf, setIDMConf } = useIDM();
11
15
  const orientation = useOrientation();
12
16
  const styles = getStyles(theme, orientation);
13
17
  const navigation = useNavigation();
14
18
  const [loading, setLoading] = useState(false);
19
+ const [scannedData, setScannedData] = useState<string | null>(null);
15
20
 
16
- // Static implementation - simulate MRZ scanning
17
- React.useEffect(() => {
18
- const timer = setTimeout(() => {
21
+ // Get MRZ engine type from metadata
22
+ const getMRZEngine = () => {
23
+ const metadata = idmConf?.selectedCountryDetails?.selectedMetaData;
24
+ if (!metadata) return 'TD3';
25
+
26
+ const flowManager = createFlowManager(metadata);
27
+ return flowManager.getMRZEngine();
28
+ };
29
+
30
+ // Handle MRZ scan
31
+ const handleMRZScan = async (mrzCode: string) => {
32
+ if (loading || !mrzCode) return;
33
+
34
+ try {
19
35
  setLoading(true);
20
- setTimeout(() => {
21
- setLoading(false);
36
+
37
+ const mrzData = {
38
+ token: String(idmConf.verificationCode || ''),
39
+ code: mrzCode,
40
+ engine: getMRZEngine(),
41
+ };
42
+
43
+ // Call API to submit MRZ
44
+ const result = await captureMRZ(idmConf, mrzData);
45
+
46
+ if (result.status) {
47
+ // Update configuration with response
48
+ setIDMConf({
49
+ ...idmConf,
50
+ requestConfiguration: { ...idmConf.requestConfiguration, ...result },
51
+ });
52
+
53
+ // Navigate to ThankYou
22
54
  navigation.navigate('ThankYou' as never);
23
- }, 2000);
55
+ } else {
56
+ Alert.alert('Error', result.errorMessage || 'Failed to process MRZ');
57
+ setLoading(false);
58
+ }
59
+ } catch (error: any) {
60
+ console.error('Error processing MRZ:', error);
61
+ Alert.alert('Error', 'Failed to process MRZ');
62
+ setLoading(false);
63
+ }
64
+ };
65
+
66
+ // Simulate MRZ scanning (replace with actual MRZ scanner library)
67
+ useEffect(() => {
68
+ // TODO: Integrate actual MRZ scanning library (e.g., @react-native-ml-kit/text-recognition)
69
+ // For now, simulate scanning after 3 seconds
70
+ const timer = setTimeout(() => {
71
+ const simulatedMRZ =
72
+ 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10';
73
+ setScannedData(simulatedMRZ);
74
+ handleMRZScan(simulatedMRZ);
24
75
  }, 3000);
25
76
 
26
77
  return () => clearTimeout(timer);
27
- }, [navigation]);
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, []);
28
80
 
29
81
  return (
30
82
  <View style={styles.container}>
31
83
  <Text style={styles.topLabel}>Align the MRZ code in the box</Text>
32
84
 
33
- {/* Camera placeholder */}
85
+ {/* Camera placeholder - Replace with actual camera view */}
34
86
  <View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
35
87
  <Text
36
88
  style={{
@@ -40,8 +92,21 @@ export default function MrzCapture() {
40
92
  fontSize: 16,
41
93
  }}
42
94
  >
43
- MRZ Scanner (Static)
95
+ {loading ? 'Processing...' : `Scanning MRZ (${getMRZEngine()})...`}
44
96
  </Text>
97
+ {scannedData && (
98
+ <Text
99
+ style={{
100
+ color: '#0f0',
101
+ textAlign: 'center',
102
+ marginTop: 20,
103
+ fontSize: 10,
104
+ paddingHorizontal: 20,
105
+ }}
106
+ >
107
+ Detected MRZ
108
+ </Text>
109
+ )}
45
110
  </View>
46
111
 
47
112
  {/* MRZ overlay box */}
@@ -53,7 +118,7 @@ export default function MrzCapture() {
53
118
  width: 380,
54
119
  height: 78,
55
120
  borderWidth: 2,
56
- borderColor: '#00ff00',
121
+ borderColor: scannedData ? '#00ff00' : '#ffffff',
57
122
  backgroundColor: 'transparent',
58
123
  }}
59
124
  />
@@ -1,5 +1,5 @@
1
1
  import { useNavigation } from '@react-navigation/native';
2
- import React, { useState, useMemo, useCallback } from 'react';
2
+ import React, { useState, useMemo, useCallback, useEffect } from 'react';
3
3
  import {
4
4
  Alert,
5
5
  Pressable,
@@ -15,45 +15,7 @@ import { useTheme } from '../context/ThemeContext';
15
15
  import { useOrientation } from '../hooks/useOrientation';
16
16
  import { useIDM } from '../context/IDMConfigurationContext';
17
17
  import { useKeyboard } from '../context/KeyboardContext';
18
-
19
- // Static country data for demo
20
- const STATIC_COUNTRIES = [
21
- {
22
- label: 'United States',
23
- value: 'US',
24
- metadata: [
25
- { type: 'DL', id: '1' },
26
- { type: 'PP', id: '2' },
27
- { type: 'NI', id: '3' },
28
- ],
29
- },
30
- {
31
- label: 'Canada',
32
- value: 'CA',
33
- metadata: [
34
- { type: 'DL', id: '4' },
35
- { type: 'PP', id: '5' },
36
- ],
37
- },
38
- {
39
- label: 'United Kingdom',
40
- value: 'GB',
41
- metadata: [
42
- { type: 'DL', id: '6' },
43
- { type: 'PP', id: '7' },
44
- { type: 'NI', id: '8' },
45
- ],
46
- },
47
- ];
48
-
49
- // Document type labels mapping
50
- const DOCUMENT_LABELS: Record<string, string> = {
51
- DL: "Driver's License",
52
- PP: 'Passport',
53
- NI: 'National ID',
54
- PC: 'Passport Card',
55
- GC: 'Green Card',
56
- };
18
+ import { getDocumentLabel } from '../utils';
57
19
 
58
20
  export default function SelectDocuments() {
59
21
  const { theme } = useTheme();
@@ -62,27 +24,38 @@ export default function SelectDocuments() {
62
24
  const orientation = useOrientation();
63
25
  const styles = getStyles(theme, orientation);
64
26
  const navigation = useNavigation();
65
- const [filteredCountries, setFilteredCountries] = useState<any[]>(STATIC_COUNTRIES);
27
+
28
+ const [filteredCountries, setFilteredCountries] = useState<any[]>([]);
66
29
  const [search, setSearch] = useState('');
67
30
  const [selectedCountry, setSelectedCountry] = useState<string | null>(null);
68
31
  const [dropdownVisible, setDropdownVisible] = useState(false);
69
32
  const [selectedIdType, setSelectedIdType] = useState<string | null>(null);
70
33
  const [idTypeDropdownVisible, setIdTypeDropdownVisible] = useState(false);
71
34
 
35
+ // Initialize filtered countries from context
36
+ useEffect(() => {
37
+ if (idmConf.countryDetails) {
38
+ setFilteredCountries(idmConf.countryDetails);
39
+ }
40
+ }, [idmConf.countryDetails]);
41
+
72
42
  // Memoize ID card types based on selected country
73
43
  const idCardTypes = useMemo(() => {
74
- if (!selectedCountry) return [];
75
- const country = STATIC_COUNTRIES.find((c) => c.value === selectedCountry);
44
+ if (!selectedCountry || !idmConf.countryDetails) return [];
45
+
46
+ const country = idmConf.countryDetails.find((c) => String(c.value) === String(selectedCountry));
76
47
  return country?.metadata.map((doc) => ({
77
- label: DOCUMENT_LABELS[doc.type] || doc.type,
48
+ label: getDocumentLabel(doc.type),
78
49
  value: doc.type,
50
+ metadata: doc,
79
51
  })) || [];
80
- }, [selectedCountry]);
52
+ }, [selectedCountry, idmConf.countryDetails]);
81
53
 
82
54
  // Memoize selected country label
83
55
  const selectedCountryLabel = useMemo(() => {
84
- return STATIC_COUNTRIES.find((c) => c.value === selectedCountry)?.label;
85
- }, [selectedCountry]);
56
+ if (!idmConf.countryDetails) return null;
57
+ return idmConf.countryDetails.find((c) => String(c.value) === String(selectedCountry))?.label;
58
+ }, [selectedCountry, idmConf.countryDetails]);
86
59
 
87
60
  // Memoize selected ID type label
88
61
  const selectedIdTypeLabel = useMemo(() => {
@@ -104,23 +77,29 @@ export default function SelectDocuments() {
104
77
  setDropdownVisible(false);
105
78
  setSearch('');
106
79
  setSelectedIdType(null);
107
- setFilteredCountries(STATIC_COUNTRIES);
80
+
81
+ if (idmConf.countryDetails) {
82
+ setFilteredCountries(idmConf.countryDetails);
83
+ }
108
84
 
109
- const selectedCountryData = STATIC_COUNTRIES.find((c) => c.value === value);
85
+ const selectedCountryData = idmConf.countryDetails?.find((c) => String(c.value) === String(value));
110
86
  updateIDMConf({ selectedCountryDetails: selectedCountryData || null });
111
- }, [updateIDMConf]);
87
+ }, [idmConf.countryDetails, updateIDMConf]);
112
88
 
113
89
  const selectIdType = useCallback((value: string) => {
114
90
  setSelectedIdType(value);
115
- const selectedCountryData = STATIC_COUNTRIES.find((c) => c.value === selectedCountry);
91
+
92
+ const selectedCountryData = idmConf.countryDetails?.find((c) => String(c.value) === String(selectedCountry));
93
+ const selectedMetaData = selectedCountryData?.metadata?.find((c) => c.type === value);
94
+
116
95
  updateIDMConf({
117
96
  selectedCountryDetails: {
118
97
  ...selectedCountryData,
119
- selectedMetaData: selectedCountryData?.metadata?.find((c) => c.type === value),
98
+ selectedMetaData,
120
99
  },
121
100
  });
122
101
  setIdTypeDropdownVisible(false);
123
- }, [selectedCountry, updateIDMConf]);
102
+ }, [selectedCountry, idmConf.countryDetails, updateIDMConf]);
124
103
 
125
104
  const handleContinue = useCallback(() => {
126
105
  if (!selectedCountry || !selectedIdType) {
@@ -132,11 +111,13 @@ export default function SelectDocuments() {
132
111
 
133
112
  const handleSearch = useCallback((text: string) => {
134
113
  setSearch(text);
135
- const filtered = STATIC_COUNTRIES.filter((c) =>
114
+ if (!idmConf.countryDetails) return;
115
+
116
+ const filtered = idmConf.countryDetails.filter((c) =>
136
117
  c.label.toLowerCase().includes(text.toLowerCase())
137
118
  );
138
119
  setFilteredCountries(filtered);
139
- }, []);
120
+ }, [idmConf.countryDetails]);
140
121
 
141
122
  const closeDropdowns = useCallback(() => {
142
123
  setDropdownVisible(false);
@@ -179,7 +160,7 @@ export default function SelectDocuments() {
179
160
  {filteredCountries.map((item) => (
180
161
  <Pressable
181
162
  key={item.value}
182
- onPress={() => selectCountry(item.value)}
163
+ onPress={() => selectCountry(String(item.value))}
183
164
  style={styles.dropdownItem}
184
165
  >
185
166
  <Text style={styles.dopDownLable}>{item.label}</Text>
@@ -1,38 +1,134 @@
1
- import React, { useState, useCallback } from 'react';
1
+ import React, { useState, useCallback, useEffect, useRef } from 'react';
2
2
  import { useNavigation } from '@react-navigation/native';
3
- import { TouchableOpacity, View, StyleSheet, Text } from 'react-native';
3
+ import { TouchableOpacity, View, StyleSheet, Text, Alert } from 'react-native';
4
4
  import getStyles from '../styles/SelfieCaptureStyles';
5
5
  import { useTheme } from '../context/ThemeContext';
6
6
  import { useOrientation } from '../hooks/useOrientation';
7
+ import { useIDM } from '../context/IDMConfigurationContext';
7
8
  import Loader from '../components/common/Loader';
9
+ import { processImageToBase64, validateImagePath } from '../utils/imageProcessor';
8
10
 
9
11
  export default function SelfieCapture() {
10
12
  const { theme } = useTheme();
13
+ const { idmConf, setIDMConf } = useIDM();
11
14
  const orientation = useOrientation();
12
15
  const styles = getStyles(theme, orientation);
13
16
  const navigation = useNavigation();
14
17
  const [loading, setLoading] = useState(false);
18
+ const [hasPermission, setHasPermission] = useState(false);
19
+ const [device, setDevice] = useState<any>(null);
20
+ const cameraRef = useRef<any>(null);
15
21
 
16
- const takePhoto = useCallback(() => {
17
- setLoading(true);
18
-
19
- // Static implementation - simulate photo capture
20
- setTimeout(() => {
21
- setLoading(false);
22
- navigation.navigate('SelectDocuments' as never);
23
- }, 1000);
22
+ // Initialize camera
23
+ useEffect(() => {
24
+ const initializeCamera = async () => {
25
+ try {
26
+ const { Camera, useCameraDevice, useCameraPermission } = require('react-native-vision-camera');
27
+
28
+ const { hasPermission: hasPerm, requestPermission } = useCameraPermission();
29
+
30
+ if (!hasPerm) {
31
+ const granted = await requestPermission();
32
+ if (!granted) {
33
+ navigation.navigate('CameraPermission' as never);
34
+ return;
35
+ }
36
+ }
37
+
38
+ setHasPermission(true);
39
+
40
+ // Get front camera for selfie
41
+ const frontDevice = useCameraDevice('front');
42
+ if (!frontDevice) {
43
+ navigation.navigate('NoCameraFound' as never);
44
+ return;
45
+ }
46
+
47
+ setDevice(frontDevice);
48
+ } catch (error) {
49
+ console.warn('Camera initialization failed:', error);
50
+ setHasPermission(true);
51
+ }
52
+ };
53
+
54
+ initializeCamera();
24
55
  }, [navigation]);
25
56
 
57
+ const takePhoto = useCallback(async () => {
58
+ if (!cameraRef.current) {
59
+ // Fallback for testing without camera
60
+ Alert.alert('Camera Not Available', 'Camera is not available. Proceeding to next step.');
61
+ navigation.navigate('SelectDocuments' as never);
62
+ return;
63
+ }
64
+
65
+ try {
66
+ setLoading(true);
67
+
68
+ const photo = await cameraRef.current.takePhoto();
69
+
70
+ if (!validateImagePath(photo?.path)) {
71
+ Alert.alert('Error', 'Failed to capture selfie');
72
+ setLoading(false);
73
+ return;
74
+ }
75
+
76
+ // Process image to base64
77
+ const base64Image = await processImageToBase64(photo.path);
78
+
79
+ // TODO: Integrate with liveness detection API
80
+ // For now, just store the selfie and proceed
81
+ setIDMConf({
82
+ ...idmConf,
83
+ userDetails: {
84
+ ...idmConf.userDetails,
85
+ selfieImage: base64Image,
86
+ },
87
+ });
88
+
89
+ // Navigate to document selection
90
+ navigation.navigate('SelectDocuments' as never);
91
+ } catch (error: any) {
92
+ console.error('Error taking selfie:', error);
93
+ Alert.alert('Error', 'Failed to capture selfie');
94
+ } finally {
95
+ setLoading(false);
96
+ }
97
+ }, [idmConf, setIDMConf, navigation]);
98
+
99
+ // Render camera if available
100
+ const renderCamera = () => {
101
+ try {
102
+ const { Camera } = require('react-native-vision-camera');
103
+
104
+ if (hasPermission && device) {
105
+ return (
106
+ <Camera
107
+ ref={cameraRef}
108
+ style={StyleSheet.absoluteFill}
109
+ device={device}
110
+ isActive={true}
111
+ photo={true}
112
+ />
113
+ );
114
+ }
115
+ } catch (error) {
116
+ console.warn('Camera render failed:', error);
117
+ }
118
+
119
+ // Fallback placeholder
120
+ return (
121
+ <View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
122
+ <Text style={[styles.text, { color: '#fff', marginTop: 100 }]}>
123
+ Camera Preview
124
+ </Text>
125
+ </View>
126
+ );
127
+ };
128
+
26
129
  return (
27
130
  <View style={styles.container}>
28
- {/* Camera placeholder */}
29
- <View style={StyleSheet.absoluteFill}>
30
- <View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
31
- <Text style={[styles.text, { color: '#fff', marginTop: 100 }]}>
32
- Camera View (Static)
33
- </Text>
34
- </View>
35
- </View>
131
+ {renderCamera()}
36
132
 
37
133
  {/* Camera overlay - Add your overlay image to assets/images/photo-overlay.png */}
38
134
  {/* <Image