@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.
- package/README.md +543 -26
- package/assets/images/Chrome-logo.svg +1 -0
- package/assets/images/Firefox-logo.png +0 -0
- package/assets/images/IDM-logo.jpg +0 -0
- package/assets/images/MRZOverlay.png +0 -0
- package/assets/images/Safari-logo.png +0 -0
- package/assets/images/aadhar.png +0 -0
- package/assets/images/camera-bg.png +0 -0
- package/assets/images/card-overlay-back.png +0 -0
- package/assets/images/card-overlay.png +0 -0
- package/assets/images/card-scan-back-icon.jpg +0 -0
- package/assets/images/card-scan-front-icon.png +0 -0
- package/assets/images/card-scan-icon-aadhaar-1.png +0 -0
- package/assets/images/card-scan-icon-aadhaar-back.png +0 -0
- package/assets/images/card-scan-icon-aadhaar-scan-qr.png +0 -0
- package/assets/images/card-scan-icon-aadhaar.png +0 -0
- package/assets/images/card-scan-icon-can-pr.png +0 -0
- package/assets/images/card-scan-icon-default-back.png +0 -0
- package/assets/images/card-scan-icon-dl.png +0 -0
- package/assets/images/card-scan-icon-greencard-back.jpg +0 -0
- package/assets/images/card-scan-icon-greencard.jpg +0 -0
- package/assets/images/card-scan-icon-hc.png +0 -0
- package/assets/images/card-scan-icon-ni-argentina-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-argentina-old.png +0 -0
- package/assets/images/card-scan-icon-ni-argentina.jpg +0 -0
- package/assets/images/card-scan-icon-ni-barcode.jpg +0 -0
- package/assets/images/card-scan-icon-ni-brazil-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-brazil.jpg +0 -0
- package/assets/images/card-scan-icon-ni-dominican-republic-back.png +0 -0
- package/assets/images/card-scan-icon-ni-dominican-republic-front.png +0 -0
- package/assets/images/card-scan-icon-ni-dominican-republic-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-dominicanaRepublic-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-france-back.png +0 -0
- package/assets/images/card-scan-icon-ni-france-front.png +0 -0
- package/assets/images/card-scan-icon-ni-france-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-germany-back.jpg +0 -0
- package/assets/images/card-scan-icon-ni-germany.jpg +0 -0
- package/assets/images/card-scan-icon-ni-paraguay-back.png +0 -0
- package/assets/images/card-scan-icon-ni-paraguay-front.png +0 -0
- package/assets/images/card-scan-icon-ni-paraguay-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-uae-back.png +0 -0
- package/assets/images/card-scan-icon-ni-uae-front.png +0 -0
- package/assets/images/card-scan-icon-ni-uae-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-uganda-front.png +0 -0
- package/assets/images/card-scan-icon-ni-uganda-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni-ukrain-back.png +0 -0
- package/assets/images/card-scan-icon-ni-ukrain-front.png +0 -0
- package/assets/images/card-scan-icon-ni-ukrain-scan-mrz.png +0 -0
- package/assets/images/card-scan-icon-ni.png +0 -0
- package/assets/images/card-scan-icon-old.jpg +0 -0
- package/assets/images/card-scan-icon-pan.png +0 -0
- package/assets/images/card-scan-icon-passport-card-back.jpg +0 -0
- package/assets/images/card-scan-icon-passport-card.jpg +0 -0
- package/assets/images/card-scan-icon-passport-old.png +0 -0
- package/assets/images/card-scan-icon-passport.png +0 -0
- package/assets/images/card-scan-icon-pr.png +0 -0
- package/assets/images/card-scan-icon.jpg +0 -0
- package/assets/images/check.png +0 -0
- package/assets/images/chrome-animation-GPS-permissions-setting.gif +0 -0
- package/assets/images/chrome-animation-camera-permissions-setting.gif +0 -0
- package/assets/images/denied.png +0 -0
- package/assets/images/dl.png +0 -0
- package/assets/images/driver-license.png +0 -0
- package/assets/images/firefox-animation-permissions-setting.gif +0 -0
- package/assets/images/flashlight_on.png +0 -0
- package/assets/images/gallery.png +0 -0
- package/assets/images/greencard.png +0 -0
- package/assets/images/header.jpg +0 -0
- package/assets/images/health-card.png +0 -0
- package/assets/images/ic_camera_front_white_36px.svg +4 -0
- package/assets/images/ic_camera_rear_white_36px.svg +4 -0
- package/assets/images/ic_fullscreen_exit_white_48px.svg +4 -0
- package/assets/images/ic_fullscreen_white_48px.svg +4 -0
- package/assets/images/ic_photo_camera_white_48px.svg +5 -0
- package/assets/images/id-card.png +0 -0
- package/assets/images/idcardimg.png +0 -0
- package/assets/images/idmval-barcode.png +0 -0
- package/assets/images/information.png +0 -0
- package/assets/images/loader.gif +0 -0
- package/assets/images/loading.svg +1 -0
- package/assets/images/logo.jpg +0 -0
- package/assets/images/logo.png +0 -0
- package/assets/images/mrz-back.png +0 -0
- package/assets/images/mrz-ni.png +0 -0
- package/assets/images/mrz.png +0 -0
- package/assets/images/mrz1.png +0 -0
- package/assets/images/mrz_old.png +0 -0
- package/assets/images/mrz_small.png +0 -0
- package/assets/images/national-id.png +0 -0
- package/assets/images/nationalID.png +0 -0
- package/assets/images/no-wifi.png +0 -0
- package/assets/images/passport-card.png +0 -0
- package/assets/images/passport.png +0 -0
- package/assets/images/permit-card.png +0 -0
- package/assets/images/photo-overlay.png +0 -0
- package/assets/images/placeholder.jpg +0 -0
- package/assets/images/qr-code.png +0 -0
- package/assets/images/right-checkmark.jpg +0 -0
- package/assets/images/selfie.jpg +0 -0
- package/assets/images/showing-sec.png +0 -0
- package/assets/images/spinner.gif +0 -0
- package/assets/images/splash-icon.png +0 -0
- package/assets/images/take-selfie.jpg +0 -0
- package/assets/images/torch_off.png +0 -0
- package/assets/images/warning-icon.jpg +0 -0
- package/assets/images/warning-stick.jpg +0 -0
- package/assets/images/wrong-checkmark.jpg +0 -0
- package/package.json +35 -8
- package/src/apis/index.ts +338 -17
- package/src/components/common/Loader.tsx +16 -2
- package/src/config/apiConfig.ts +6 -0
- package/src/index.tsx +123 -7
- package/src/screens/BarcodeCapture.tsx +64 -12
- package/src/screens/DocumentCaptureBack.tsx +133 -24
- package/src/screens/DocumentCaptureFront.tsx +146 -24
- package/src/screens/MrzCapture.tsx +77 -12
- package/src/screens/SelectDocuments.tsx +37 -56
- package/src/screens/SelfieCapture.tsx +114 -18
- package/src/screens/ThankYou.tsx +34 -1
- package/src/services/getUserData.ts +111 -0
- package/src/types/IDMConf.ts +81 -7
- package/src/utils/base64.ts +25 -0
- package/src/utils/flowManager.ts +138 -0
- package/src/utils/imageProcessor.ts +96 -0
- package/src/utils/index.ts +18 -0
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
|
-
import { View, ActivityIndicator, StyleSheet } from 'react-native';
|
|
2
|
+
import { View, ActivityIndicator, StyleSheet, Text } from 'react-native';
|
|
3
3
|
import { useTheme } from '../../context/ThemeContext';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface LoaderProps {
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function Loader({ message }: LoaderProps) {
|
|
6
10
|
const { theme } = useTheme();
|
|
7
11
|
|
|
8
12
|
return (
|
|
9
13
|
<View style={[styles.container, { backgroundColor: theme.colors.background }]}>
|
|
10
14
|
<ActivityIndicator size="large" color={theme.colors.primary} />
|
|
15
|
+
{message && (
|
|
16
|
+
<Text style={[styles.message, { color: theme.colors.text }]}>
|
|
17
|
+
{message}
|
|
18
|
+
</Text>
|
|
19
|
+
)}
|
|
11
20
|
</View>
|
|
12
21
|
);
|
|
13
22
|
}
|
|
@@ -18,6 +27,11 @@ const styles = StyleSheet.create({
|
|
|
18
27
|
justifyContent: 'center',
|
|
19
28
|
alignItems: 'center',
|
|
20
29
|
},
|
|
30
|
+
message: {
|
|
31
|
+
marginTop: 16,
|
|
32
|
+
fontSize: 16,
|
|
33
|
+
textAlign: 'center',
|
|
34
|
+
},
|
|
21
35
|
});
|
|
22
36
|
|
|
23
37
|
export default memo(Loader);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const SANDBOX_BASE_URL = 'https://sandbox-api.idmerit.com';
|
|
2
|
+
export const PRODUCTION_BASE_URL = 'https://api.idmerit.com';
|
|
3
|
+
|
|
4
|
+
export const getBaseURL = (environment: 'sandbox' | 'production'): string => {
|
|
5
|
+
return environment?.toLowerCase() === 'sandbox' ? SANDBOX_BASE_URL : PRODUCTION_BASE_URL;
|
|
6
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useMemo, useCallback } from 'react';
|
|
1
|
+
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
|
2
2
|
import { NavigationContainer, useNavigationContainerRef } from '@react-navigation/native';
|
|
3
3
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
4
4
|
import BarcodeCapture from './screens/BarcodeCapture';
|
|
@@ -21,10 +21,18 @@ import MrzAdvice from './screens/MrzAdvice';
|
|
|
21
21
|
import { ThemeProvider } from './context/ThemeContext';
|
|
22
22
|
import { themes } from './context/themes';
|
|
23
23
|
import { IDMConf } from './types/IDMConf';
|
|
24
|
-
import { IDMProvider } from './context/IDMConfigurationContext';
|
|
24
|
+
import { IDMProvider, useIDM } from './context/IDMConfigurationContext';
|
|
25
25
|
import { KeyboardProvider, useKeyboard } from './context/KeyboardContext';
|
|
26
26
|
import Footer from './components/common/Footer';
|
|
27
27
|
import Header from './components/common/Header';
|
|
28
|
+
import Loader from './components/common/Loader';
|
|
29
|
+
import {
|
|
30
|
+
getUserDataAPI,
|
|
31
|
+
generateAccessToken,
|
|
32
|
+
generateRequest,
|
|
33
|
+
getConfiguration,
|
|
34
|
+
getCountries,
|
|
35
|
+
} from './apis';
|
|
28
36
|
|
|
29
37
|
const Stack = createNativeStackNavigator();
|
|
30
38
|
|
|
@@ -41,23 +49,26 @@ const HIDE_HEADER_FOOTER_ROUTES = new Set([
|
|
|
41
49
|
]);
|
|
42
50
|
|
|
43
51
|
interface IDMScanProps {
|
|
44
|
-
idmConf
|
|
52
|
+
idmConf: IDMConf;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
|
-
export default function IDMScan({ idmConf
|
|
55
|
+
export default function IDMScan({ idmConf }: IDMScanProps) {
|
|
48
56
|
return (
|
|
49
57
|
<IDMProvider initialConf={idmConf}>
|
|
50
58
|
<KeyboardProvider>
|
|
51
|
-
<IDMScanContent
|
|
59
|
+
<IDMScanContent />
|
|
52
60
|
</KeyboardProvider>
|
|
53
61
|
</IDMProvider>
|
|
54
62
|
);
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
function IDMScanContent(
|
|
65
|
+
function IDMScanContent() {
|
|
58
66
|
const { isKeyboardVisible } = useKeyboard();
|
|
67
|
+
const { idmConf, setIDMConf } = useIDM();
|
|
59
68
|
const navigationRef = useNavigationContainerRef();
|
|
60
69
|
const [currentRoute, setCurrentRoute] = useState<string | undefined>();
|
|
70
|
+
const [loading, setLoading] = useState(true);
|
|
71
|
+
const [error, setError] = useState<string | null>(null);
|
|
61
72
|
|
|
62
73
|
// Memoize theme calculation
|
|
63
74
|
const theme = useMemo(
|
|
@@ -80,11 +91,116 @@ function IDMScanContent({ idmConf }: { idmConf: IDMConf }) {
|
|
|
80
91
|
setCurrentRoute(route?.name);
|
|
81
92
|
}, [navigationRef]);
|
|
82
93
|
|
|
94
|
+
// Dynamic initialization flow
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const initializeSDK = async () => {
|
|
97
|
+
try {
|
|
98
|
+
setLoading(true);
|
|
99
|
+
setError(null);
|
|
100
|
+
|
|
101
|
+
// Step 1: Get user data if not provided
|
|
102
|
+
if (!idmConf.userDetails) {
|
|
103
|
+
console.log('Fetching user data...');
|
|
104
|
+
const userData = await getUserDataAPI();
|
|
105
|
+
const updatedConf = { ...idmConf, userDetails: userData };
|
|
106
|
+
setIDMConf(updatedConf);
|
|
107
|
+
|
|
108
|
+
// Step 2: Generate access token if not provided
|
|
109
|
+
if (!idmConf.accessToken) {
|
|
110
|
+
console.log('Generating access token...');
|
|
111
|
+
const token = await generateAccessToken(updatedConf);
|
|
112
|
+
const confWithToken = { ...updatedConf, accessToken: token };
|
|
113
|
+
setIDMConf(confWithToken);
|
|
114
|
+
|
|
115
|
+
// Step 3: Generate verification request
|
|
116
|
+
console.log('Generating verification request...');
|
|
117
|
+
const requestResult = await generateRequest(confWithToken, idmConf.requestData);
|
|
118
|
+
const confWithVerifyCode = {
|
|
119
|
+
...confWithToken,
|
|
120
|
+
verificationCode: requestResult.verificationCode,
|
|
121
|
+
};
|
|
122
|
+
setIDMConf(confWithVerifyCode);
|
|
123
|
+
|
|
124
|
+
// Step 4: Get configuration
|
|
125
|
+
console.log('Fetching configuration...');
|
|
126
|
+
const fetchedConfig = await getConfiguration(
|
|
127
|
+
confWithVerifyCode,
|
|
128
|
+
requestResult.verificationCode
|
|
129
|
+
);
|
|
130
|
+
setIDMConf({
|
|
131
|
+
...confWithVerifyCode,
|
|
132
|
+
configuration: fetchedConfig,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (err: any) {
|
|
137
|
+
console.error('SDK initialization error:', err);
|
|
138
|
+
setError(err.message || 'Failed to initialize SDK');
|
|
139
|
+
} finally {
|
|
140
|
+
setLoading(false);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
initializeSDK();
|
|
145
|
+
}, []); // Run once on mount
|
|
146
|
+
|
|
147
|
+
// Fetch countries when access token and verification code are available
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const fetchCountriesData = async () => {
|
|
150
|
+
if (idmConf.accessToken && idmConf.verificationCode && !idmConf.countryDetails) {
|
|
151
|
+
try {
|
|
152
|
+
console.log('Fetching countries...');
|
|
153
|
+
const response = await getCountries(idmConf);
|
|
154
|
+
const countriesArray = Array.isArray(response) ? response : response?.data || [];
|
|
155
|
+
|
|
156
|
+
const mapped = countriesArray.map((singleCountry: any) => ({
|
|
157
|
+
label: singleCountry.country,
|
|
158
|
+
value: singleCountry.index,
|
|
159
|
+
metadata: singleCountry.metadata || [],
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
setIDMConf({
|
|
163
|
+
...idmConf,
|
|
164
|
+
countryDetails: mapped,
|
|
165
|
+
});
|
|
166
|
+
} catch (err: any) {
|
|
167
|
+
console.error('Error fetching countries:', err);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
fetchCountriesData();
|
|
173
|
+
}, [idmConf.accessToken, idmConf.verificationCode]);
|
|
174
|
+
|
|
175
|
+
// Determine initial route based on permissions
|
|
176
|
+
const initialRouteName = useMemo(() => {
|
|
177
|
+
if (idmConf.userDetails?.permissionGranted) {
|
|
178
|
+
return 'VerifyIdentity';
|
|
179
|
+
}
|
|
180
|
+
return 'LocationPermission';
|
|
181
|
+
}, [idmConf.userDetails?.permissionGranted]);
|
|
182
|
+
|
|
183
|
+
if (loading) {
|
|
184
|
+
return (
|
|
185
|
+
<ThemeProvider theme={theme}>
|
|
186
|
+
<Loader />
|
|
187
|
+
</ThemeProvider>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (error) {
|
|
192
|
+
return (
|
|
193
|
+
<ThemeProvider theme={theme}>
|
|
194
|
+
<Loader message={`Error: ${error}`} />
|
|
195
|
+
</ThemeProvider>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
83
199
|
return (
|
|
84
200
|
<ThemeProvider theme={theme}>
|
|
85
201
|
{shouldShowHeaderFooter && <Header />}
|
|
86
202
|
<NavigationContainer ref={navigationRef} onStateChange={handleStateChange}>
|
|
87
|
-
<Stack.Navigator initialRouteName=
|
|
203
|
+
<Stack.Navigator initialRouteName={initialRouteName} screenOptions={SCREEN_OPTIONS}>
|
|
88
204
|
<Stack.Screen name="VerifyIdentity" component={VerifyIdentity} />
|
|
89
205
|
<Stack.Screen name="SelfieAdvice" component={SelfieAdvice} />
|
|
90
206
|
<Stack.Screen name="SelfieCapture" component={SelfieCapture} />
|
|
@@ -1,30 +1,70 @@
|
|
|
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 { captureBarcode } from '../apis';
|
|
8
10
|
|
|
9
11
|
export default function BarcodeCapture() {
|
|
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 [scannedData, setScannedData] = useState<string | null>(null);
|
|
15
19
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
// Handle barcode scan
|
|
21
|
+
const handleBarcodeScan = async (barcodeText: string) => {
|
|
22
|
+
if (loading || !barcodeText) return;
|
|
23
|
+
|
|
24
|
+
try {
|
|
19
25
|
setLoading(true);
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
|
|
27
|
+
const barcodeData = {
|
|
28
|
+
token: String(idmConf.verificationCode || ''),
|
|
29
|
+
text: barcodeText,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Call API to submit barcode
|
|
33
|
+
const result = await captureBarcode(idmConf, barcodeData);
|
|
34
|
+
|
|
35
|
+
if (result.status) {
|
|
36
|
+
// Update configuration with response
|
|
37
|
+
setIDMConf({
|
|
38
|
+
...idmConf,
|
|
39
|
+
requestConfiguration: { ...idmConf.requestConfiguration, ...result },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Navigate to ThankYou
|
|
22
43
|
navigation.navigate('ThankYou' as never);
|
|
23
|
-
}
|
|
44
|
+
} else {
|
|
45
|
+
Alert.alert('Error', result.errorMessage || 'Failed to process barcode');
|
|
46
|
+
setLoading(false);
|
|
47
|
+
}
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
console.error('Error processing barcode:', error);
|
|
50
|
+
Alert.alert('Error', 'Failed to process barcode');
|
|
51
|
+
setLoading(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Simulate barcode scanning (replace with actual barcode scanner library)
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
// TODO: Integrate actual barcode scanning library (e.g., react-native-vision-camera with barcode plugin)
|
|
58
|
+
// For now, simulate scanning after 3 seconds
|
|
59
|
+
const timer = setTimeout(() => {
|
|
60
|
+
const simulatedBarcode = 'SIMULATED_BARCODE_DATA_' + Date.now();
|
|
61
|
+
setScannedData(simulatedBarcode);
|
|
62
|
+
handleBarcodeScan(simulatedBarcode);
|
|
24
63
|
}, 3000);
|
|
25
64
|
|
|
26
65
|
return () => clearTimeout(timer);
|
|
27
|
-
|
|
66
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
67
|
+
}, []);
|
|
28
68
|
|
|
29
69
|
return (
|
|
30
70
|
<View style={styles.container}>
|
|
@@ -32,7 +72,7 @@ export default function BarcodeCapture() {
|
|
|
32
72
|
Fill the box with the barcode{'\n'}until it turns green
|
|
33
73
|
</Text>
|
|
34
74
|
|
|
35
|
-
{/* Camera placeholder */}
|
|
75
|
+
{/* Camera placeholder - Replace with actual camera view */}
|
|
36
76
|
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
37
77
|
<Text
|
|
38
78
|
style={{
|
|
@@ -42,8 +82,20 @@ export default function BarcodeCapture() {
|
|
|
42
82
|
fontSize: 16,
|
|
43
83
|
}}
|
|
44
84
|
>
|
|
45
|
-
|
|
85
|
+
{loading ? 'Processing...' : 'Scanning for barcode...'}
|
|
46
86
|
</Text>
|
|
87
|
+
{scannedData && (
|
|
88
|
+
<Text
|
|
89
|
+
style={{
|
|
90
|
+
color: '#0f0',
|
|
91
|
+
textAlign: 'center',
|
|
92
|
+
marginTop: 20,
|
|
93
|
+
fontSize: 12,
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
Detected: {scannedData.substring(0, 30)}...
|
|
97
|
+
</Text>
|
|
98
|
+
)}
|
|
47
99
|
</View>
|
|
48
100
|
|
|
49
101
|
{/* Barcode overlay box */}
|
|
@@ -55,7 +107,7 @@ export default function BarcodeCapture() {
|
|
|
55
107
|
width: 350,
|
|
56
108
|
height: 100,
|
|
57
109
|
borderWidth: 2,
|
|
58
|
-
borderColor: '#00ff00',
|
|
110
|
+
borderColor: scannedData ? '#00ff00' : '#ffffff',
|
|
59
111
|
backgroundColor: 'transparent',
|
|
60
112
|
}}
|
|
61
113
|
/>
|
|
@@ -1,54 +1,163 @@
|
|
|
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 { captureDocumentBack } from '../apis';
|
|
10
|
+
import { processImageToBase64, validateImagePath } from '../utils/imageProcessor';
|
|
11
|
+
import { createFlowManager } from '../utils/flowManager';
|
|
8
12
|
|
|
9
13
|
export default function DocumentCaptureBack() {
|
|
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
|
+
const { Camera, useCameraDevice, useCameraPermission } = require('react-native-vision-camera');
|
|
29
|
+
|
|
30
|
+
const { hasPermission: hasPerm, requestPermission } = useCameraPermission();
|
|
31
|
+
|
|
32
|
+
if (!hasPerm) {
|
|
33
|
+
const granted = await requestPermission();
|
|
34
|
+
if (!granted) {
|
|
35
|
+
navigation.navigate('CameraPermission' as never);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setHasPermission(true);
|
|
41
|
+
|
|
42
|
+
const backDevice = useCameraDevice('back');
|
|
43
|
+
if (!backDevice) {
|
|
44
|
+
navigation.navigate('NoCameraFound' as never);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setDevice(backDevice);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn('Camera initialization failed:', error);
|
|
51
|
+
setHasPermission(true);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
initializeCamera();
|
|
56
|
+
}, [navigation]);
|
|
57
|
+
|
|
58
|
+
// Determine next navigation step based on metadata
|
|
59
|
+
const handleNextStep = () => {
|
|
60
|
+
const metaData = idmConf?.selectedCountryDetails?.selectedMetaData;
|
|
61
|
+
|
|
62
|
+
if (!metaData) {
|
|
63
|
+
navigation.navigate('ThankYou' as never);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const flowManager = createFlowManager(metaData);
|
|
68
|
+
const nextScreen = flowManager.getNextScreenAfterBack();
|
|
69
|
+
navigation.navigate(nextScreen as never);
|
|
70
|
+
};
|
|
15
71
|
|
|
16
72
|
const takePhoto = async () => {
|
|
73
|
+
if (!cameraRef.current) {
|
|
74
|
+
Alert.alert('Camera Not Available', 'Camera is not available in this environment.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
17
78
|
try {
|
|
18
79
|
setLoading(true);
|
|
19
|
-
|
|
20
|
-
|
|
80
|
+
|
|
81
|
+
const photo = await cameraRef.current.takePhoto();
|
|
82
|
+
|
|
83
|
+
if (!validateImagePath(photo?.path)) {
|
|
84
|
+
Alert.alert('Error', 'Failed to capture image');
|
|
21
85
|
setLoading(false);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Process image to base64
|
|
90
|
+
const base64Image = await processImageToBase64(photo.path);
|
|
91
|
+
|
|
92
|
+
// Prepare photo data for API
|
|
93
|
+
const photoData = {
|
|
94
|
+
token: String(idmConf.verificationCode || ''),
|
|
95
|
+
file: base64Image,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Call API to capture document back
|
|
99
|
+
const result = await captureDocumentBack(idmConf, photoData);
|
|
100
|
+
|
|
101
|
+
if (result.status) {
|
|
102
|
+
// Update configuration with response
|
|
103
|
+
setIDMConf({
|
|
104
|
+
...idmConf,
|
|
105
|
+
requestConfiguration: { ...idmConf.requestConfiguration, ...result },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Navigate to next step based on metadata
|
|
109
|
+
handleNextStep();
|
|
110
|
+
} else {
|
|
111
|
+
navigation.navigate('RetakeSelfie' as never, {
|
|
112
|
+
errorMessage: result.errorMessage || 'Failed to process document',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} catch (error: any) {
|
|
26
116
|
console.error('Error taking photo:', error);
|
|
117
|
+
Alert.alert('Error', 'Failed to capture or process the image.');
|
|
118
|
+
} finally {
|
|
27
119
|
setLoading(false);
|
|
28
120
|
}
|
|
29
121
|
};
|
|
30
122
|
|
|
123
|
+
const renderCamera = () => {
|
|
124
|
+
try {
|
|
125
|
+
const { Camera } = require('react-native-vision-camera');
|
|
126
|
+
|
|
127
|
+
if (hasPermission && device) {
|
|
128
|
+
return (
|
|
129
|
+
<Camera
|
|
130
|
+
ref={cameraRef}
|
|
131
|
+
style={StyleSheet.absoluteFill}
|
|
132
|
+
device={device}
|
|
133
|
+
isActive={true}
|
|
134
|
+
photo={true}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.warn('Camera render failed:', error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
144
|
+
<Text style={{ color: '#fff', textAlign: 'center', marginTop: 100 }}>
|
|
145
|
+
Camera Preview
|
|
146
|
+
</Text>
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
31
151
|
return (
|
|
32
152
|
<View style={styles.container}>
|
|
33
153
|
<Text style={styles.topLabel}>Document Back Side</Text>
|
|
34
154
|
|
|
35
|
-
{
|
|
36
|
-
<View style={StyleSheet.absoluteFill}>
|
|
37
|
-
<View
|
|
38
|
-
style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}
|
|
39
|
-
></View>
|
|
40
|
-
</View>
|
|
155
|
+
{renderCamera()}
|
|
41
156
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
style={
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
borderWidth: 2,
|
|
48
|
-
borderColor: '#fff',
|
|
49
|
-
backgroundColor: 'transparent',
|
|
50
|
-
},
|
|
51
|
-
]}
|
|
157
|
+
<Image
|
|
158
|
+
source={require('../../assets/images/card-overlay-back.png')}
|
|
159
|
+
style={styles.cardoverlay}
|
|
160
|
+
resizeMode="cover"
|
|
52
161
|
/>
|
|
53
162
|
|
|
54
163
|
<Text style={styles.bottomLabel}>
|