@test-web/react-native-sdk 1.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.
- package/README.md +58 -0
- package/android/build.gradle +50 -0
- package/android/src/main/AndroidManifest.xml +24 -0
- package/android/src/main/java/com/yourcompany/sdk/YourSDKModule.java +40 -0
- package/android/src/main/java/com/yourcompany/sdk/YourSDKPackage.java +24 -0
- package/ios/YourSDK-Bridging-Header.h +1 -0
- package/ios/YourSDK-Info.plist +30 -0
- package/ios/YourSDK.podspec +19 -0
- package/ios/YourSDKModule.h +5 -0
- package/ios/YourSDKModule.m +32 -0
- package/ios/YourSDKModule.swift +22 -0
- package/package.json +38 -0
- package/src/apis/index.ts +32 -0
- package/src/components/common/CustomOverlay.tsx +44 -0
- package/src/components/common/Footer.tsx +30 -0
- package/src/components/common/Header.tsx +29 -0
- package/src/components/common/Loader.tsx +23 -0
- package/src/components/index.tsx +6 -0
- package/src/components/ui/Button.tsx +41 -0
- package/src/components/ui/ThemedText.tsx +42 -0
- package/src/components/ui/index.tsx +2 -0
- package/src/context/IDMConfigurationContext.tsx +44 -0
- package/src/context/KeyboardContext.tsx +61 -0
- package/src/context/ThemeContext.tsx +34 -0
- package/src/context/themes.ts +134 -0
- package/src/hooks/useOrientation.ts +25 -0
- package/src/index.tsx +110 -0
- package/src/native/NativeModule.ts +10 -0
- package/src/screens/BackDocumentAdvice.tsx +52 -0
- package/src/screens/BarcodeAdvice.tsx +52 -0
- package/src/screens/BarcodeCapture.tsx +66 -0
- package/src/screens/CameraPermission.tsx +74 -0
- package/src/screens/DocumentCaptureBack.tsx +65 -0
- package/src/screens/DocumentCaptureFront.tsx +65 -0
- package/src/screens/FrontDocumentAdvice.tsx +52 -0
- package/src/screens/LocationPermission.tsx +74 -0
- package/src/screens/MrzAdvice.tsx +52 -0
- package/src/screens/MrzCapture.tsx +64 -0
- package/src/screens/NoCameraFound.tsx +64 -0
- package/src/screens/RetakeSelfie.tsx +56 -0
- package/src/screens/SelectDocuments.tsx +250 -0
- package/src/screens/SelfieAdvice.tsx +35 -0
- package/src/screens/SelfieCapture.tsx +56 -0
- package/src/screens/ThankYou.tsx +23 -0
- package/src/screens/VerifyIdentity.tsx +54 -0
- package/src/screens/index.tsx +17 -0
- package/src/styles/BarcodeAdviceStyles.ts +45 -0
- package/src/styles/DocumentAdviceStyles.ts +44 -0
- package/src/styles/DocumentCaptureStyles.ts +55 -0
- package/src/styles/PermissionStyle.ts +61 -0
- package/src/styles/RetakeStyles.ts +67 -0
- package/src/styles/ScannerStyles.ts +28 -0
- package/src/styles/SelectDocumentsStyles.ts +90 -0
- package/src/styles/SelfieAdviceStyles.ts +61 -0
- package/src/styles/SelfieCaptureStyles.ts +48 -0
- package/src/styles/ThankYouStyles.ts +31 -0
- package/src/styles/VerifyIdentityStyles.ts +86 -0
- package/src/types/IDMConf.ts +28 -0
- package/src/types/index.ts +12 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/metadata_new.json +1 -0
- package/src/utils/performance.ts +176 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
3
|
+
import { useNavigation } from '@react-navigation/native';
|
|
4
|
+
import getStyles from '../styles/DocumentCaptureStyles';
|
|
5
|
+
import { useTheme } from '../context/ThemeContext';
|
|
6
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
7
|
+
import Loader from '../components/common/Loader';
|
|
8
|
+
|
|
9
|
+
export default function DocumentCaptureFront() {
|
|
10
|
+
const { theme } = useTheme();
|
|
11
|
+
const orientation = useOrientation();
|
|
12
|
+
const styles = getStyles(theme, orientation);
|
|
13
|
+
const navigation = useNavigation();
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
|
|
16
|
+
const takePhoto = async () => {
|
|
17
|
+
try {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
// Static implementation - simulate photo capture
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
setLoading(false);
|
|
22
|
+
// Navigate to BackDocumentAdvice after capturing front
|
|
23
|
+
navigation.navigate('BackDocumentAdvice' as never);
|
|
24
|
+
}, 1000);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Error taking photo:', error);
|
|
27
|
+
setLoading(false);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<View style={styles.container}>
|
|
33
|
+
<Text style={styles.topLabel}>Document Front Side</Text>
|
|
34
|
+
|
|
35
|
+
{/* Camera placeholder */}
|
|
36
|
+
<View style={StyleSheet.absoluteFill}>
|
|
37
|
+
<View
|
|
38
|
+
style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}
|
|
39
|
+
></View>
|
|
40
|
+
</View>
|
|
41
|
+
|
|
42
|
+
{/* Card overlay placeholder */}
|
|
43
|
+
<View
|
|
44
|
+
style={[
|
|
45
|
+
styles.cardoverlay,
|
|
46
|
+
{
|
|
47
|
+
borderWidth: 2,
|
|
48
|
+
borderColor: '#fff',
|
|
49
|
+
backgroundColor: 'transparent',
|
|
50
|
+
},
|
|
51
|
+
]}
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<Text style={styles.bottomLabel}>
|
|
55
|
+
Position your document fully within the frame
|
|
56
|
+
</Text>
|
|
57
|
+
|
|
58
|
+
<View style={styles.buttonplace}>
|
|
59
|
+
<TouchableOpacity style={styles.captureButton} onPress={takePhoto} />
|
|
60
|
+
</View>
|
|
61
|
+
|
|
62
|
+
{loading && <Loader />}
|
|
63
|
+
</View>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native';
|
|
2
|
+
import { Image, ScrollView, View } from 'react-native';
|
|
3
|
+
import Button from '../components/ui/Button';
|
|
4
|
+
import ThemedText from '../components/ui/ThemedText';
|
|
5
|
+
import getStyles from '../styles/DocumentAdviceStyles';
|
|
6
|
+
import { useTheme } from '../context/ThemeContext';
|
|
7
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
export default function FrontDocumentAdvice() {
|
|
11
|
+
const { theme } = useTheme();
|
|
12
|
+
const orientation = useOrientation();
|
|
13
|
+
const navigation = useNavigation();
|
|
14
|
+
|
|
15
|
+
const styles = useMemo(
|
|
16
|
+
() => getStyles(theme, orientation),
|
|
17
|
+
[theme, orientation]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const handleRedirect = () => {
|
|
21
|
+
navigation.navigate('DocumentCaptureFront' as never);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ScrollView contentContainerStyle={styles.container}>
|
|
26
|
+
<ThemedText style={styles.maintitle} type="title">
|
|
27
|
+
Capture Your ID Front
|
|
28
|
+
</ThemedText>
|
|
29
|
+
|
|
30
|
+
{/* Placeholder for ID card image */}
|
|
31
|
+
<View
|
|
32
|
+
style={[
|
|
33
|
+
styles.idcard,
|
|
34
|
+
{
|
|
35
|
+
backgroundColor: '#f0f0f0',
|
|
36
|
+
justifyContent: 'center',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
},
|
|
39
|
+
]}
|
|
40
|
+
>
|
|
41
|
+
<ThemedText>ID Front Image</ThemedText>
|
|
42
|
+
</View>
|
|
43
|
+
|
|
44
|
+
<Button
|
|
45
|
+
title="Take a photo"
|
|
46
|
+
style={styles.buttonplace}
|
|
47
|
+
textStyle={styles.button}
|
|
48
|
+
onPress={handleRedirect}
|
|
49
|
+
/>
|
|
50
|
+
</ScrollView>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ScrollView, Text, View, PermissionsAndroid, Platform } from 'react-native';
|
|
4
|
+
import Button from '../components/ui/Button';
|
|
5
|
+
import ThemedText from '../components/ui/ThemedText';
|
|
6
|
+
import { useTheme } from '../context/ThemeContext';
|
|
7
|
+
import getStyles from '../styles/PermissionStyle';
|
|
8
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
9
|
+
|
|
10
|
+
export default function LocationPermission() {
|
|
11
|
+
const { theme } = useTheme();
|
|
12
|
+
const orientation = useOrientation();
|
|
13
|
+
const styles = getStyles(theme, orientation);
|
|
14
|
+
const navigation = useNavigation();
|
|
15
|
+
|
|
16
|
+
const requestLocationPermission = async () => {
|
|
17
|
+
if (Platform.OS === 'android') {
|
|
18
|
+
try {
|
|
19
|
+
const granted = await PermissionsAndroid.request(
|
|
20
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
21
|
+
{
|
|
22
|
+
title: 'Location Permission',
|
|
23
|
+
message: 'This app needs access to your location.',
|
|
24
|
+
buttonPositive: 'OK',
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
28
|
+
if (navigation.canGoBack()) {
|
|
29
|
+
navigation.goBack();
|
|
30
|
+
} else {
|
|
31
|
+
navigation.navigate('VerifyIdentity' as never);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.warn(err);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
// For iOS, navigate directly (implement iOS permission separately)
|
|
39
|
+
if (navigation.canGoBack()) {
|
|
40
|
+
navigation.goBack();
|
|
41
|
+
} else {
|
|
42
|
+
navigation.navigate('VerifyIdentity' as never);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ScrollView contentContainerStyle={styles.container}>
|
|
49
|
+
<ThemedText style={styles.maintitle} type="title">
|
|
50
|
+
Location Access Required
|
|
51
|
+
</ThemedText>
|
|
52
|
+
|
|
53
|
+
<View style={{ padding: 20 }}>
|
|
54
|
+
<Text
|
|
55
|
+
style={{
|
|
56
|
+
color: theme.colors.text,
|
|
57
|
+
textAlign: 'center',
|
|
58
|
+
marginBottom: 20,
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
This app needs access to your location to function correctly. Please
|
|
62
|
+
grant location permission.
|
|
63
|
+
</Text>
|
|
64
|
+
</View>
|
|
65
|
+
|
|
66
|
+
<Button
|
|
67
|
+
title="Grant Permission"
|
|
68
|
+
style={styles.buttonplace}
|
|
69
|
+
textStyle={styles.button}
|
|
70
|
+
onPress={requestLocationPermission}
|
|
71
|
+
/>
|
|
72
|
+
</ScrollView>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native';
|
|
2
|
+
import { ScrollView, View } from 'react-native';
|
|
3
|
+
import Button from '../components/ui/Button';
|
|
4
|
+
import ThemedText from '../components/ui/ThemedText';
|
|
5
|
+
import getStyles from '../styles/BarcodeAdviceStyles';
|
|
6
|
+
import { useTheme } from '../context/ThemeContext';
|
|
7
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
8
|
+
import { useMemo } from 'react';
|
|
9
|
+
|
|
10
|
+
export default function MrzAdvice() {
|
|
11
|
+
const { theme } = useTheme();
|
|
12
|
+
const orientation = useOrientation();
|
|
13
|
+
const navigation = useNavigation();
|
|
14
|
+
|
|
15
|
+
const styles = useMemo(
|
|
16
|
+
() => getStyles(theme, orientation),
|
|
17
|
+
[theme, orientation]
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const handleRedirect = () => {
|
|
21
|
+
navigation.navigate('MrzCapture' as never);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<ScrollView contentContainerStyle={styles.container}>
|
|
26
|
+
<ThemedText style={styles.maintitle} type="title">
|
|
27
|
+
Scan the MRZ of your ID
|
|
28
|
+
</ThemedText>
|
|
29
|
+
|
|
30
|
+
{/* Placeholder for MRZ image */}
|
|
31
|
+
<View
|
|
32
|
+
style={[
|
|
33
|
+
styles.idcard,
|
|
34
|
+
{
|
|
35
|
+
backgroundColor: '#f0f0f0',
|
|
36
|
+
justifyContent: 'center',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
},
|
|
39
|
+
]}
|
|
40
|
+
>
|
|
41
|
+
<ThemedText>MRZ Zone Image</ThemedText>
|
|
42
|
+
</View>
|
|
43
|
+
|
|
44
|
+
<Button
|
|
45
|
+
title="Scan"
|
|
46
|
+
style={styles.buttonplace}
|
|
47
|
+
textStyle={styles.button}
|
|
48
|
+
onPress={handleRedirect}
|
|
49
|
+
/>
|
|
50
|
+
</ScrollView>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { View, Text, StyleSheet } from 'react-native';
|
|
3
|
+
import { useNavigation } from '@react-navigation/native';
|
|
4
|
+
import { useTheme } from '../context/ThemeContext';
|
|
5
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
6
|
+
import getStyles from '../styles/ScannerStyles';
|
|
7
|
+
import Loader from '../components/common/Loader';
|
|
8
|
+
|
|
9
|
+
export default function MrzCapture() {
|
|
10
|
+
const { theme } = useTheme();
|
|
11
|
+
const orientation = useOrientation();
|
|
12
|
+
const styles = getStyles(theme, orientation);
|
|
13
|
+
const navigation = useNavigation();
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
|
|
16
|
+
// Static implementation - simulate MRZ scanning
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
setLoading(true);
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
setLoading(false);
|
|
22
|
+
navigation.navigate('ThankYou' as never);
|
|
23
|
+
}, 2000);
|
|
24
|
+
}, 3000);
|
|
25
|
+
|
|
26
|
+
return () => clearTimeout(timer);
|
|
27
|
+
}, [navigation]);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={styles.container}>
|
|
31
|
+
<Text style={styles.topLabel}>Align the MRZ code in the box</Text>
|
|
32
|
+
|
|
33
|
+
{/* Camera placeholder */}
|
|
34
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: '#000' }]}>
|
|
35
|
+
<Text
|
|
36
|
+
style={{
|
|
37
|
+
color: '#fff',
|
|
38
|
+
textAlign: 'center',
|
|
39
|
+
marginTop: 200,
|
|
40
|
+
fontSize: 16,
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
MRZ Scanner (Static)
|
|
44
|
+
</Text>
|
|
45
|
+
</View>
|
|
46
|
+
|
|
47
|
+
{/* MRZ overlay box */}
|
|
48
|
+
<View
|
|
49
|
+
style={{
|
|
50
|
+
position: 'absolute',
|
|
51
|
+
top: '40%',
|
|
52
|
+
alignSelf: 'center',
|
|
53
|
+
width: 380,
|
|
54
|
+
height: 78,
|
|
55
|
+
borderWidth: 2,
|
|
56
|
+
borderColor: '#00ff00',
|
|
57
|
+
backgroundColor: 'transparent',
|
|
58
|
+
}}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
{loading && <Loader />}
|
|
62
|
+
</View>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ScrollView, Text, View, Platform, StyleSheet } from 'react-native';
|
|
3
|
+
import { useTheme } from '../context/ThemeContext';
|
|
4
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
5
|
+
|
|
6
|
+
export default function NoCameraFound() {
|
|
7
|
+
const { theme } = useTheme();
|
|
8
|
+
const orientation = useOrientation();
|
|
9
|
+
const [hasCamera, setHasCamera] = React.useState(true);
|
|
10
|
+
|
|
11
|
+
React.useEffect(() => {
|
|
12
|
+
if (Platform.OS === 'android') {
|
|
13
|
+
const hasCameraOnAndroid = true;
|
|
14
|
+
setHasCamera(hasCameraOnAndroid);
|
|
15
|
+
} else {
|
|
16
|
+
setHasCamera(true);
|
|
17
|
+
}
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<ScrollView
|
|
22
|
+
contentContainerStyle={[
|
|
23
|
+
styles.container,
|
|
24
|
+
{ backgroundColor: theme.colors.background },
|
|
25
|
+
]}
|
|
26
|
+
>
|
|
27
|
+
<View style={{ padding: 20 }}>
|
|
28
|
+
{!hasCamera && (
|
|
29
|
+
<Text
|
|
30
|
+
style={{
|
|
31
|
+
color: theme.colors.text,
|
|
32
|
+
textAlign: 'center',
|
|
33
|
+
marginBottom: 20,
|
|
34
|
+
fontSize: 18,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
No camera found on this device. This feature requires a camera to
|
|
38
|
+
function.
|
|
39
|
+
</Text>
|
|
40
|
+
)}
|
|
41
|
+
{hasCamera && (
|
|
42
|
+
<Text
|
|
43
|
+
style={{
|
|
44
|
+
color: theme.colors.text,
|
|
45
|
+
textAlign: 'center',
|
|
46
|
+
marginBottom: 20,
|
|
47
|
+
fontSize: 18,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
Camera is available on this device.
|
|
51
|
+
</Text>
|
|
52
|
+
)}
|
|
53
|
+
</View>
|
|
54
|
+
</ScrollView>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const styles = StyleSheet.create({
|
|
59
|
+
container: {
|
|
60
|
+
flex: 1,
|
|
61
|
+
justifyContent: 'center',
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ScrollView, Text, View } from 'react-native';
|
|
4
|
+
import Button from '../components/ui/Button';
|
|
5
|
+
import { useTheme } from '../context/ThemeContext';
|
|
6
|
+
import getStyles from '../styles/RetakeStyles';
|
|
7
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
8
|
+
|
|
9
|
+
interface RouteParams {
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function RetakeSelfie() {
|
|
14
|
+
const { theme } = useTheme();
|
|
15
|
+
const orientation = useOrientation();
|
|
16
|
+
const styles = getStyles(theme, orientation);
|
|
17
|
+
const navigation = useNavigation();
|
|
18
|
+
const route = useRoute();
|
|
19
|
+
const params = route.params as RouteParams | undefined;
|
|
20
|
+
const errorMessage = params?.errorMessage;
|
|
21
|
+
|
|
22
|
+
const handleRetake = () => {
|
|
23
|
+
if (navigation.canGoBack()) {
|
|
24
|
+
navigation.goBack();
|
|
25
|
+
} else {
|
|
26
|
+
navigation.navigate('SelfieCapture' as never);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<ScrollView contentContainerStyle={styles.container}>
|
|
32
|
+
<Text style={styles.title}>
|
|
33
|
+
Sorry we were unable to register your selfie.
|
|
34
|
+
</Text>
|
|
35
|
+
<Text style={styles.subtitle}>Please try again</Text>
|
|
36
|
+
<Text style={styles.errorMessage}>
|
|
37
|
+
Error: {errorMessage || 'Failed to verify selfie. Please try again.'}
|
|
38
|
+
</Text>
|
|
39
|
+
<Text style={styles.guideTitle}>Guide for Perfect Selfie Capture</Text>
|
|
40
|
+
<View style={styles.guideList}>
|
|
41
|
+
<Text style={styles.bullet}>
|
|
42
|
+
• Please bring your face close to the camera.
|
|
43
|
+
</Text>
|
|
44
|
+
<Text style={styles.bullet}>
|
|
45
|
+
• Please ensure your face covers the frame.
|
|
46
|
+
</Text>
|
|
47
|
+
</View>
|
|
48
|
+
<Button
|
|
49
|
+
title="Retake"
|
|
50
|
+
style={styles.buttonplace}
|
|
51
|
+
textStyle={styles.button}
|
|
52
|
+
onPress={handleRetake}
|
|
53
|
+
/>
|
|
54
|
+
</ScrollView>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { useNavigation } from '@react-navigation/native';
|
|
2
|
+
import React, { useState, useMemo, useCallback } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Alert,
|
|
5
|
+
Pressable,
|
|
6
|
+
ScrollView,
|
|
7
|
+
Text,
|
|
8
|
+
TextInput,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import Button from '../components/ui/Button';
|
|
12
|
+
import ThemedText from '../components/ui/ThemedText';
|
|
13
|
+
import getStyles from '../styles/SelectDocumentsStyles';
|
|
14
|
+
import { useTheme } from '../context/ThemeContext';
|
|
15
|
+
import { useOrientation } from '../hooks/useOrientation';
|
|
16
|
+
import { useIDM } from '../context/IDMConfigurationContext';
|
|
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
|
+
};
|
|
57
|
+
|
|
58
|
+
export default function SelectDocuments() {
|
|
59
|
+
const { theme } = useTheme();
|
|
60
|
+
const { isKeyboardVisible } = useKeyboard();
|
|
61
|
+
const { idmConf, updateIDMConf } = useIDM();
|
|
62
|
+
const orientation = useOrientation();
|
|
63
|
+
const styles = getStyles(theme, orientation);
|
|
64
|
+
const navigation = useNavigation();
|
|
65
|
+
const [filteredCountries, setFilteredCountries] = useState<any[]>(STATIC_COUNTRIES);
|
|
66
|
+
const [search, setSearch] = useState('');
|
|
67
|
+
const [selectedCountry, setSelectedCountry] = useState<string | null>(null);
|
|
68
|
+
const [dropdownVisible, setDropdownVisible] = useState(false);
|
|
69
|
+
const [selectedIdType, setSelectedIdType] = useState<string | null>(null);
|
|
70
|
+
const [idTypeDropdownVisible, setIdTypeDropdownVisible] = useState(false);
|
|
71
|
+
|
|
72
|
+
// Memoize ID card types based on selected country
|
|
73
|
+
const idCardTypes = useMemo(() => {
|
|
74
|
+
if (!selectedCountry) return [];
|
|
75
|
+
const country = STATIC_COUNTRIES.find((c) => c.value === selectedCountry);
|
|
76
|
+
return country?.metadata.map((doc) => ({
|
|
77
|
+
label: DOCUMENT_LABELS[doc.type] || doc.type,
|
|
78
|
+
value: doc.type,
|
|
79
|
+
})) || [];
|
|
80
|
+
}, [selectedCountry]);
|
|
81
|
+
|
|
82
|
+
// Memoize selected country label
|
|
83
|
+
const selectedCountryLabel = useMemo(() => {
|
|
84
|
+
return STATIC_COUNTRIES.find((c) => c.value === selectedCountry)?.label;
|
|
85
|
+
}, [selectedCountry]);
|
|
86
|
+
|
|
87
|
+
// Memoize selected ID type label
|
|
88
|
+
const selectedIdTypeLabel = useMemo(() => {
|
|
89
|
+
return idCardTypes.find((i) => i.value === selectedIdType)?.label;
|
|
90
|
+
}, [idCardTypes, selectedIdType]);
|
|
91
|
+
|
|
92
|
+
const toggleDropdown = useCallback(() => {
|
|
93
|
+
setDropdownVisible((prev) => !prev);
|
|
94
|
+
setIdTypeDropdownVisible(false);
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const toggleIdTypeDropdown = useCallback(() => {
|
|
98
|
+
setIdTypeDropdownVisible((prev) => !prev);
|
|
99
|
+
setDropdownVisible(false);
|
|
100
|
+
}, []);
|
|
101
|
+
|
|
102
|
+
const selectCountry = useCallback((value: string) => {
|
|
103
|
+
setSelectedCountry(value);
|
|
104
|
+
setDropdownVisible(false);
|
|
105
|
+
setSearch('');
|
|
106
|
+
setSelectedIdType(null);
|
|
107
|
+
setFilteredCountries(STATIC_COUNTRIES);
|
|
108
|
+
|
|
109
|
+
const selectedCountryData = STATIC_COUNTRIES.find((c) => c.value === value);
|
|
110
|
+
updateIDMConf({ selectedCountryDetails: selectedCountryData || null });
|
|
111
|
+
}, [updateIDMConf]);
|
|
112
|
+
|
|
113
|
+
const selectIdType = useCallback((value: string) => {
|
|
114
|
+
setSelectedIdType(value);
|
|
115
|
+
const selectedCountryData = STATIC_COUNTRIES.find((c) => c.value === selectedCountry);
|
|
116
|
+
updateIDMConf({
|
|
117
|
+
selectedCountryDetails: {
|
|
118
|
+
...selectedCountryData,
|
|
119
|
+
selectedMetaData: selectedCountryData?.metadata?.find((c) => c.type === value),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
setIdTypeDropdownVisible(false);
|
|
123
|
+
}, [selectedCountry, updateIDMConf]);
|
|
124
|
+
|
|
125
|
+
const handleContinue = useCallback(() => {
|
|
126
|
+
if (!selectedCountry || !selectedIdType) {
|
|
127
|
+
Alert.alert('Missing Fields', 'Please select both country and ID card type.');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
navigation.navigate('FrontDocumentAdvice' as never);
|
|
131
|
+
}, [selectedCountry, selectedIdType, navigation]);
|
|
132
|
+
|
|
133
|
+
const handleSearch = useCallback((text: string) => {
|
|
134
|
+
setSearch(text);
|
|
135
|
+
const filtered = STATIC_COUNTRIES.filter((c) =>
|
|
136
|
+
c.label.toLowerCase().includes(text.toLowerCase())
|
|
137
|
+
);
|
|
138
|
+
setFilteredCountries(filtered);
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
141
|
+
const closeDropdowns = useCallback(() => {
|
|
142
|
+
setDropdownVisible(false);
|
|
143
|
+
setIdTypeDropdownVisible(false);
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<View style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
|
148
|
+
<Pressable style={{ flex: 1 }} onPress={closeDropdowns}>
|
|
149
|
+
<ScrollView
|
|
150
|
+
contentContainerStyle={styles.container}
|
|
151
|
+
keyboardShouldPersistTaps="handled"
|
|
152
|
+
>
|
|
153
|
+
<View>
|
|
154
|
+
<ThemedText style={styles.maintitle} type="title">
|
|
155
|
+
ID Issuer Country
|
|
156
|
+
</ThemedText>
|
|
157
|
+
|
|
158
|
+
<View style={{ marginHorizontal: 20, marginBottom: 20 }}>
|
|
159
|
+
<Pressable style={styles.dropdownTrigger} onPress={toggleDropdown}>
|
|
160
|
+
<Text
|
|
161
|
+
style={{
|
|
162
|
+
color: selectedCountry ? theme.colors.text : theme.colors.subtitle,
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
{selectedCountryLabel || 'Select a country'}
|
|
166
|
+
</Text>
|
|
167
|
+
<Text style={styles.downArrow}>▼</Text>
|
|
168
|
+
</Pressable>
|
|
169
|
+
{dropdownVisible && (
|
|
170
|
+
<View style={[styles.dropdownList, { maxHeight: 300 }]}>
|
|
171
|
+
<TextInput
|
|
172
|
+
value={search}
|
|
173
|
+
onChangeText={handleSearch}
|
|
174
|
+
placeholder="Search country..."
|
|
175
|
+
placeholderTextColor={theme.colors.subtitle}
|
|
176
|
+
style={styles.searchbox}
|
|
177
|
+
/>
|
|
178
|
+
<ScrollView nestedScrollEnabled>
|
|
179
|
+
{filteredCountries.map((item) => (
|
|
180
|
+
<Pressable
|
|
181
|
+
key={item.value}
|
|
182
|
+
onPress={() => selectCountry(item.value)}
|
|
183
|
+
style={styles.dropdownItem}
|
|
184
|
+
>
|
|
185
|
+
<Text style={styles.dopDownLable}>{item.label}</Text>
|
|
186
|
+
</Pressable>
|
|
187
|
+
))}
|
|
188
|
+
</ScrollView>
|
|
189
|
+
</View>
|
|
190
|
+
)}
|
|
191
|
+
</View>
|
|
192
|
+
|
|
193
|
+
<View>
|
|
194
|
+
<ThemedText style={styles.maintitle} type="title">
|
|
195
|
+
Select ID Card Type
|
|
196
|
+
</ThemedText>
|
|
197
|
+
|
|
198
|
+
<View style={{ marginHorizontal: 20, marginBottom: 20 }}>
|
|
199
|
+
<Pressable style={styles.dropdownTrigger} onPress={toggleIdTypeDropdown}>
|
|
200
|
+
<Text
|
|
201
|
+
style={{
|
|
202
|
+
color: selectedIdType ? theme.colors.text : theme.colors.subtitle,
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
{selectedIdTypeLabel || 'Select ID Card Type'}
|
|
206
|
+
</Text>
|
|
207
|
+
<Text style={styles.downArrow}>▼</Text>
|
|
208
|
+
</Pressable>
|
|
209
|
+
{idTypeDropdownVisible && (
|
|
210
|
+
<View style={[styles.dropdownList, { maxHeight: 200 }]}>
|
|
211
|
+
<ScrollView nestedScrollEnabled>
|
|
212
|
+
{idCardTypes.length > 0 ? (
|
|
213
|
+
idCardTypes.map((item) => (
|
|
214
|
+
<Pressable
|
|
215
|
+
key={item.value}
|
|
216
|
+
onPress={() => selectIdType(item.value)}
|
|
217
|
+
style={styles.dropdownItem}
|
|
218
|
+
>
|
|
219
|
+
<Text style={styles.dopDownLable}>{item.label}</Text>
|
|
220
|
+
</Pressable>
|
|
221
|
+
))
|
|
222
|
+
) : (
|
|
223
|
+
<View style={styles.dropdownItem}>
|
|
224
|
+
<Text style={styles.dopDownLable}>
|
|
225
|
+
{selectedCountry
|
|
226
|
+
? 'No ID types available for this country'
|
|
227
|
+
: 'Please select a country first'}
|
|
228
|
+
</Text>
|
|
229
|
+
</View>
|
|
230
|
+
)}
|
|
231
|
+
</ScrollView>
|
|
232
|
+
</View>
|
|
233
|
+
)}
|
|
234
|
+
</View>
|
|
235
|
+
</View>
|
|
236
|
+
</View>
|
|
237
|
+
|
|
238
|
+
{!isKeyboardVisible && (
|
|
239
|
+
<Button
|
|
240
|
+
title="Continue"
|
|
241
|
+
style={styles.buttonplace}
|
|
242
|
+
textStyle={styles.button}
|
|
243
|
+
onPress={handleContinue}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
246
|
+
</ScrollView>
|
|
247
|
+
</Pressable>
|
|
248
|
+
</View>
|
|
249
|
+
);
|
|
250
|
+
}
|