@test-web/react-native-sdk 1.0.0 → 2.0.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 +824 -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 +44 -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 +154 -24
  114. package/src/screens/DocumentCaptureBack.tsx +133 -24
  115. package/src/screens/DocumentCaptureFront.tsx +146 -24
  116. package/src/screens/MrzCapture.tsx +205 -16
  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,36 +1,204 @@
1
- import React, { useState } from 'react';
2
- import { View, Text, StyleSheet } from 'react-native';
1
+ import React, { useState, useEffect, useRef } 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);
20
+ const [isScanning, setIsScanning] = useState(true);
21
+ const [hasPermission, setHasPermission] = useState(false);
22
+ const [device, setDevice] = useState<any>(null);
23
+ const cameraRef = useRef<any>(null);
24
+ const frameProcessorRef = useRef<any>(null);
15
25
 
16
- // Static implementation - simulate MRZ scanning
17
- React.useEffect(() => {
18
- const timer = setTimeout(() => {
26
+ // Get MRZ engine type from metadata
27
+ const getMRZEngine = () => {
28
+ const metadata = idmConf?.selectedCountryDetails?.selectedMetaData;
29
+ if (!metadata) return 'TD3';
30
+
31
+ const flowManager = createFlowManager(metadata);
32
+ return flowManager.getMRZEngine();
33
+ };
34
+
35
+ // Initialize camera with ML Kit
36
+ useEffect(() => {
37
+ let isMounted = true;
38
+
39
+ const initializeCamera = async () => {
40
+ try {
41
+ const { Camera, useCameraDevice, useCameraPermission } = require('react-native-vision-camera');
42
+
43
+ const { hasPermission: hasPerm, requestPermission } = useCameraPermission();
44
+
45
+ if (!hasPerm) {
46
+ const granted = await requestPermission();
47
+ if (!granted) {
48
+ navigation.navigate('CameraPermission' as never);
49
+ return;
50
+ }
51
+ }
52
+
53
+ if (isMounted) {
54
+ setHasPermission(true);
55
+
56
+ const backDevice = useCameraDevice('back');
57
+ if (!backDevice) {
58
+ navigation.navigate('NoCameraFound' as never);
59
+ return;
60
+ }
61
+
62
+ setDevice(backDevice);
63
+ }
64
+ } catch (error) {
65
+ console.warn('Camera initialization failed:', error);
66
+ if (isMounted) {
67
+ simulateMRZScan();
68
+ }
69
+ }
70
+ };
71
+
72
+ initializeCamera();
73
+
74
+ return () => {
75
+ isMounted = false;
76
+ setIsScanning(false);
77
+ };
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, []);
80
+
81
+ // Process MRZ with ML Kit Text Recognition
82
+ const processMRZWithMLKit = async (frame: any) => {
83
+ if (!isScanning) return;
84
+
85
+ try {
86
+ const { recognizeText } = require('@react-native-ml-kit/text-recognition');
87
+
88
+ const result = await recognizeText(frame);
89
+
90
+ if (result && result.text) {
91
+ // Check if text contains MRZ pattern
92
+ const lines = result.text.split('\n');
93
+ const mrzLines = lines.filter((line: string) =>
94
+ line.length >= 30 && /^[A-Z0-9<]+$/.test(line)
95
+ );
96
+
97
+ if (mrzLines.length >= 2) {
98
+ const mrzCode = mrzLines.join('\n');
99
+ handleMRZScanned(mrzCode);
100
+ }
101
+ }
102
+ } catch (error) {
103
+ console.warn('ML Kit text recognition error:', error);
104
+ }
105
+ };
106
+
107
+ // Handle MRZ scan result
108
+ const handleMRZScanned = async (mrzCode: string) => {
109
+ if (loading || !mrzCode || !isScanning) return;
110
+
111
+ setIsScanning(false);
112
+ setScannedData(mrzCode);
113
+
114
+ try {
19
115
  setLoading(true);
20
- setTimeout(() => {
21
- setLoading(false);
116
+
117
+ const mrzData = {
118
+ token: String(idmConf.verificationCode || ''),
119
+ code: mrzCode,
120
+ engine: getMRZEngine(),
121
+ };
122
+
123
+ // Call API to submit MRZ
124
+ const result = await captureMRZ(idmConf, mrzData);
125
+
126
+ if (result.status) {
127
+ // Update configuration with response
128
+ setIDMConf({
129
+ ...idmConf,
130
+ requestConfiguration: { ...idmConf.requestConfiguration, ...result },
131
+ });
132
+
133
+ // Navigate to ThankYou
22
134
  navigation.navigate('ThankYou' as never);
23
- }, 2000);
135
+ } else {
136
+ Alert.alert('Error', result.errorMessage || 'Failed to process MRZ', [
137
+ {
138
+ text: 'Retry',
139
+ onPress: () => {
140
+ setIsScanning(true);
141
+ setScannedData(null);
142
+ setLoading(false);
143
+ },
144
+ },
145
+ ]);
146
+ }
147
+ } catch (error: any) {
148
+ console.error('Error processing MRZ:', error);
149
+ Alert.alert('Error', 'Failed to process MRZ', [
150
+ {
151
+ text: 'Retry',
152
+ onPress: () => {
153
+ setIsScanning(true);
154
+ setScannedData(null);
155
+ setLoading(false);
156
+ },
157
+ },
158
+ ]);
159
+ }
160
+ };
161
+
162
+ // Fallback simulation if ML Kit not available
163
+ const simulateMRZScan = () => {
164
+ const timer = setTimeout(() => {
165
+ if (isScanning) {
166
+ const simulatedMRZ =
167
+ 'P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<<\nL898902C36UTO7408122F1204159ZE184226B<<<<<10';
168
+ handleMRZScanned(simulatedMRZ);
169
+ }
24
170
  }, 3000);
25
171
 
26
172
  return () => clearTimeout(timer);
27
- }, [navigation]);
173
+ };
28
174
 
29
- return (
30
- <View style={styles.container}>
31
- <Text style={styles.topLabel}>Align the MRZ code in the box</Text>
175
+ // Render camera with frame processor
176
+ const renderCamera = () => {
177
+ try {
178
+ const { Camera } = require('react-native-vision-camera');
179
+
180
+ if (hasPermission && device) {
181
+ return (
182
+ <Camera
183
+ ref={cameraRef}
184
+ style={StyleSheet.absoluteFill}
185
+ device={device}
186
+ isActive={isScanning}
187
+ frameProcessor={(frame) => {
188
+ 'worklet';
189
+ if (isScanning) {
190
+ processMRZWithMLKit(frame);
191
+ }
192
+ }}
193
+ />
194
+ );
195
+ }
196
+ } catch (error) {
197
+ console.warn('Camera render failed:', error);
198
+ }
32
199
 
33
- {/* Camera placeholder */}
200
+ // Fallback UI
201
+ return (
34
202
  <View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
35
203
  <Text
36
204
  style={{
@@ -40,9 +208,30 @@ export default function MrzCapture() {
40
208
  fontSize: 16,
41
209
  }}
42
210
  >
43
- MRZ Scanner (Static)
211
+ {loading ? 'Processing...' : `Scanning MRZ (${getMRZEngine()})...`}
44
212
  </Text>
213
+ {scannedData && (
214
+ <Text
215
+ style={{
216
+ color: '#0f0',
217
+ textAlign: 'center',
218
+ marginTop: 20,
219
+ fontSize: 10,
220
+ paddingHorizontal: 20,
221
+ }}
222
+ >
223
+ Detected MRZ
224
+ </Text>
225
+ )}
45
226
  </View>
227
+ );
228
+ };
229
+
230
+ return (
231
+ <View style={styles.container}>
232
+ <Text style={styles.topLabel}>Align the MRZ code in the box</Text>
233
+
234
+ {renderCamera()}
46
235
 
47
236
  {/* MRZ overlay box */}
48
237
  <View
@@ -53,12 +242,12 @@ export default function MrzCapture() {
53
242
  width: 380,
54
243
  height: 78,
55
244
  borderWidth: 2,
56
- borderColor: '#00ff00',
245
+ borderColor: scannedData ? '#00ff00' : '#ffffff',
57
246
  backgroundColor: 'transparent',
58
247
  }}
59
248
  />
60
249
 
61
- {loading && <Loader />}
250
+ {loading && <Loader message="Processing MRZ..." />}
62
251
  </View>
63
252
  );
64
253
  }
@@ -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
@@ -1,14 +1,46 @@
1
- import React from 'react';
1
+ import React, { useEffect, useState } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import ThemedText from '../components/ui/ThemedText';
4
4
  import getStyles from '../styles/ThankYouStyles';
5
5
  import { useTheme } from '../context/ThemeContext';
6
6
  import { useOrientation } from '../hooks/useOrientation';
7
+ import { useIDM } from '../context/IDMConfigurationContext';
8
+ import Loader from '../components/common/Loader';
9
+ import { completeVerification } from '../apis';
7
10
 
8
11
  export default function ThankYou() {
9
12
  const { theme } = useTheme();
13
+ const { idmConf } = useIDM();
10
14
  const orientation = useOrientation();
11
15
  const styles = getStyles(theme, orientation);
16
+ const [loading, setLoading] = useState(false);
17
+ const [completed, setCompleted] = useState(false);
18
+
19
+ // Call complete verification API
20
+ useEffect(() => {
21
+ const finalizeVerification = async () => {
22
+ if (completed || !idmConf.verificationCode) return;
23
+
24
+ try {
25
+ setLoading(true);
26
+ const result = await completeVerification(idmConf);
27
+ console.log('Verification completed:', result);
28
+ setCompleted(true);
29
+ } catch (error: any) {
30
+ console.error('Error completing verification:', error);
31
+ // Don't show error to user, just log it
32
+ } finally {
33
+ setLoading(false);
34
+ }
35
+ };
36
+
37
+ // Call after a short delay to ensure all data is submitted
38
+ const timer = setTimeout(() => {
39
+ finalizeVerification();
40
+ }, 1000);
41
+
42
+ return () => clearTimeout(timer);
43
+ }, [idmConf, completed]);
12
44
 
13
45
  return (
14
46
  <View style={styles.container}>
@@ -18,6 +50,7 @@ export default function ThankYou() {
18
50
  <ThemedText style={styles.subtitle} type="title">
19
51
  Your information{'\n'}has been uploaded
20
52
  </ThemedText>
53
+ {loading && <Loader message="Finalizing verification..." />}
21
54
  </View>
22
55
  );
23
56
  }