Add passport application flow until step 2 'Kuesioner Layanan Permohonan'
This commit is contained in:
102
src/components/RadioButtonOption.tsx
Normal file
102
src/components/RadioButtonOption.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View, Pressable} from 'react-native';
|
||||
import Colors from '../../assets/styles/Colors';
|
||||
import {RadioButton} from 'react-native-paper';
|
||||
import FontFamily from '../../assets/styles/FontFamily';
|
||||
|
||||
interface RadioButtonOptionProps {
|
||||
label: string;
|
||||
description: string;
|
||||
value: string;
|
||||
selectedValue: string;
|
||||
onSelect: (value: string) => void;
|
||||
}
|
||||
|
||||
const RadioButtonOptionComponent = ({
|
||||
label,
|
||||
description,
|
||||
value,
|
||||
selectedValue,
|
||||
onSelect,
|
||||
}: RadioButtonOptionProps) => {
|
||||
const isSelected = selectedValue === value;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => onSelect(value)}
|
||||
style={[
|
||||
styles.container,
|
||||
isSelected && {backgroundColor: Colors.secondary30.color},
|
||||
]}>
|
||||
<RadioButton
|
||||
value={value}
|
||||
status={isSelected ? 'checked' : 'unchecked'}
|
||||
onPress={() => onSelect(value)}
|
||||
color={Colors.neutral100.color}
|
||||
uncheckedColor={Colors.secondary30.color}
|
||||
/>
|
||||
<View style={styles.textContainer}>
|
||||
{description === 'null' ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.textTitle,
|
||||
isSelected && {color: Colors.neutral100.color},
|
||||
{...FontFamily.notoSansRegular},
|
||||
]}>
|
||||
{label}
|
||||
</Text>
|
||||
) : (
|
||||
<Text
|
||||
style={[
|
||||
styles.textTitle,
|
||||
isSelected && {color: Colors.neutral100.color},
|
||||
]}>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
{description && description !== 'null' && (
|
||||
<Text
|
||||
style={[
|
||||
styles.textDesc,
|
||||
isSelected && {color: Colors.neutral100.color},
|
||||
]}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
paddingStart: 12,
|
||||
paddingEnd: 16,
|
||||
borderWidth: 1,
|
||||
gap: 8,
|
||||
borderRadius: 8,
|
||||
borderColor: Colors.primary70.color,
|
||||
},
|
||||
textContainer: {
|
||||
gap: 6,
|
||||
flex: 1,
|
||||
},
|
||||
textTitle: {
|
||||
...FontFamily.notoSansBold,
|
||||
fontSize: 14,
|
||||
color: Colors.primary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
textDesc: {
|
||||
...FontFamily.notoSansMedium,
|
||||
color: Colors.primary30.color,
|
||||
fontSize: 10.5,
|
||||
includeFontPadding: false,
|
||||
textAlign: 'justify',
|
||||
},
|
||||
});
|
||||
|
||||
export default RadioButtonOptionComponent;
|
99
src/components/StepIndicator.tsx
Normal file
99
src/components/StepIndicator.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import Colors from '../../assets/styles/Colors';
|
||||
import FontFamily from '../../assets/styles/FontFamily';
|
||||
|
||||
const StepIndicator = ({currentStep, totalSteps, completedSteps}: any) => {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{[...Array(totalSteps)].map((_, index) => {
|
||||
const stepNumber = index + 1;
|
||||
const isCompleted = completedSteps.includes(stepNumber);
|
||||
const isCurrent = currentStep === stepNumber;
|
||||
|
||||
const backgroundColor = isCompleted
|
||||
? Colors.secondary30.color
|
||||
: Colors.neutral100.color;
|
||||
|
||||
const textColor = isCompleted
|
||||
? Colors.neutral100.color
|
||||
: isCurrent
|
||||
? Colors.secondary30.color
|
||||
: Colors.secondary50.color;
|
||||
|
||||
const textStyle = isCompleted
|
||||
? FontFamily.notoSansBold
|
||||
: isCurrent
|
||||
? FontFamily.notoSansRegular
|
||||
: FontFamily.notoSansRegular;
|
||||
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<View style={{alignItems: 'center'}}>
|
||||
<View
|
||||
style={[
|
||||
styles.stepIndicatorContainer,
|
||||
{
|
||||
backgroundColor: backgroundColor,
|
||||
borderColor: isCompleted
|
||||
? Colors.secondary30.color
|
||||
: Colors.neutral100.color,
|
||||
},
|
||||
]}>
|
||||
<Text
|
||||
style={{
|
||||
includeFontPadding: false,
|
||||
fontSize: 12,
|
||||
color: textColor,
|
||||
...textStyle,
|
||||
}}>
|
||||
{stepNumber}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
{index < totalSteps - 1 && (
|
||||
<View
|
||||
style={[
|
||||
styles.stepIndicatorLine,
|
||||
{
|
||||
backgroundColor: isCompleted
|
||||
? Colors.secondary30.color
|
||||
: Colors.neutral100.color,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
backgroundColor: Colors.secondary70.color,
|
||||
},
|
||||
stepIndicatorContainer: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
borderWidth: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
stepIndicatorText: {
|
||||
includeFontPadding: false,
|
||||
fontSize: 14,
|
||||
},
|
||||
stepIndicatorLine: {
|
||||
flex: 1,
|
||||
height: 2,
|
||||
}
|
||||
});
|
||||
|
||||
export default StepIndicator;
|
@ -123,8 +123,9 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
placeholderTextColor={Colors.primary60.color}
|
||||
editable={false}
|
||||
value={formattedDate}
|
||||
right={<TextInput.Icon icon="calendar" />}
|
||||
right={<TextInput.Icon icon="calendar" color='#48454E' />}
|
||||
multiline={false}
|
||||
textColor='#48454E'
|
||||
/>
|
||||
</Pressable>
|
||||
{showPicker && (
|
||||
@ -166,6 +167,7 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
) : null
|
||||
}
|
||||
multiline={isMultiline}
|
||||
textColor='#48454E'
|
||||
/>
|
||||
{supportText && <Text style={[styles.supportText]}>{supportText}</Text>}
|
||||
</View>
|
||||
@ -196,6 +198,7 @@ const styles = StyleSheet.create({
|
||||
placeholderText: {
|
||||
fontSize: 13,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
dropdown: {
|
||||
marginTop: 8,
|
||||
@ -205,16 +208,18 @@ const styles = StyleSheet.create({
|
||||
paddingStart: 16,
|
||||
paddingEnd: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.primary40.color,
|
||||
borderColor: '#98989A',
|
||||
},
|
||||
placeholderDropdownStyle: {
|
||||
fontSize: 13,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
color: Colors.primary60.color,
|
||||
},
|
||||
selectedTextStyle: {
|
||||
fontSize: 13,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
iconStyle: {
|
||||
width: 20,
|
||||
|
9
src/data/DropdownData/CivilStatus.tsx
Normal file
9
src/data/DropdownData/CivilStatus.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
const civilStatusData = [
|
||||
{label: 'Cerai Hidup', value: '1'},
|
||||
{label: 'Cerai Mati', value: '2'},
|
||||
{label: 'Kawin', value: '3'},
|
||||
{label: 'Belum Kawin', value: '4'},
|
||||
];
|
||||
|
||||
export default civilStatusData;
|
||||
|
@ -49,11 +49,9 @@ function RootStack() {
|
||||
component={NavigationRouteScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="Home"
|
||||
component={HomeScreen}
|
||||
options={{headerShown: false}}
|
||||
/>
|
||||
<Stack.Screen name="Home" options={{headerShown: false}}>
|
||||
{() => <HomeScreen showDialog={() => console.log('Show dialog!')} />}
|
||||
</Stack.Screen>
|
||||
<Stack.Screen
|
||||
name="History"
|
||||
component={HistoryScreen}
|
||||
|
@ -37,10 +37,9 @@ function CloseAccountScreen() {
|
||||
<PaperProvider>
|
||||
<View style={styles.contentContainer}>
|
||||
<StatusBar
|
||||
backgroundColor={visible ? '#ADADAF' : Colors.neutral100.color}
|
||||
backgroundColor={visible ? Colors.primary30.color : Colors.primary30.color}
|
||||
barStyle={visible ? 'light-content' : 'dark-content'}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Icon
|
||||
|
@ -76,6 +76,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 20,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 22,
|
||||
color: Colors.indicatorRed.color,
|
||||
},
|
||||
dialogButtonContainer: {
|
||||
|
@ -1,11 +1,160 @@
|
||||
import React from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
import React, {useState} from 'react';
|
||||
import {Button, Text, View} from 'react-native';
|
||||
import styles from './styles';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import StepIndicator from '../../components/StepIndicator';
|
||||
|
||||
const renderContent = (
|
||||
step: number,
|
||||
subStep: number,
|
||||
setStep: (step: number) => void,
|
||||
setSubStep: (step: number) => void,
|
||||
) => {
|
||||
if (step === 1) {
|
||||
switch (subStep) {
|
||||
case 1:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 1.1</Text>
|
||||
<Button title="Next" onPress={() => setSubStep(2)} />
|
||||
</View>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 1.2</Text>
|
||||
<Button title="Next" onPress={() => setSubStep(3)} />
|
||||
<Button title="Back" onPress={() => setSubStep(1)} />
|
||||
</View>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 1.3</Text>
|
||||
<Button
|
||||
title="Next"
|
||||
onPress={() => {
|
||||
setStep(2);
|
||||
setSubStep(1);
|
||||
}}
|
||||
/>
|
||||
<Button title="Back" onPress={() => setSubStep(2)} />
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (step === 2) {
|
||||
switch (subStep) {
|
||||
case 1:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 2.1</Text>
|
||||
<Button title="Next" onPress={() => setSubStep(2)} />
|
||||
<Button
|
||||
title="Back"
|
||||
onPress={() => {
|
||||
setStep(1);
|
||||
setSubStep(3);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 2.2</Text>
|
||||
<Button title="Next" onPress={() => setSubStep(3)} />
|
||||
<Button title="Back" onPress={() => setSubStep(1)} />
|
||||
</View>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 2.3</Text>
|
||||
<Button
|
||||
title="Next"
|
||||
onPress={() => {
|
||||
setStep(3);
|
||||
setSubStep(1);
|
||||
}}
|
||||
/>
|
||||
<Button title="Back" onPress={() => setSubStep(2)} />
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 3:
|
||||
return (
|
||||
<View>
|
||||
<Button title="Next" onPress={() => setStep(4)} />
|
||||
<Button title="Back" onPress={() => setStep(2)} />
|
||||
</View>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<View>
|
||||
<Button title="Next" onPress={() => setStep(5)} />
|
||||
<Button title="Back" onPress={() => setStep(3)} />
|
||||
</View>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<View>
|
||||
<Button title="Next" onPress={() => setStep(6)} />
|
||||
<Button title="Back" onPress={() => setStep(4)} />
|
||||
</View>
|
||||
);
|
||||
case 6:
|
||||
return (
|
||||
<View>
|
||||
<Button title="Next" onPress={() => setStep(7)} />
|
||||
<Button title="Back" onPress={() => setStep(5)} />
|
||||
</View>
|
||||
);
|
||||
case 7:
|
||||
return (
|
||||
<View>
|
||||
<Button title="Back" onPress={() => setStep(6)} />
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
function ExpressPassportScreen() {
|
||||
const [step, setStep] = useState(1);
|
||||
const [subStep, setSubStep] = useState(1);
|
||||
|
||||
const completedSteps = [...Array(step - 1)].map((_, i) => i + 1);
|
||||
const stepTitles: {[key: number]: string} = {
|
||||
1: 'Informasi Pribadi',
|
||||
2: 'Dokumen Pendukung',
|
||||
3: 'Pembayaran',
|
||||
4: 'Konfirmasi Data',
|
||||
5: 'Verifikasi',
|
||||
6: 'Pemrosesan',
|
||||
7: 'Selesai',
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Express Passport Screen</Text>
|
||||
<View style={{flex: 1}}>
|
||||
<View>
|
||||
<Text>{stepTitles[step]}</Text>
|
||||
</View>
|
||||
<StepIndicator
|
||||
currentStep={step}
|
||||
totalSteps={7}
|
||||
completedSteps={completedSteps}
|
||||
/>
|
||||
{renderContent(step, subStep, setStep, setSubStep)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -36,7 +36,11 @@ const data = [1, 2, 3, 4];
|
||||
|
||||
const ItemSeparator = () => <View style={styles.flatllistGap} />;
|
||||
|
||||
const RenderContent = () => {
|
||||
type RenderContentProps = {
|
||||
showDialog: () => void;
|
||||
};
|
||||
|
||||
const RenderContent = ({showDialog}: RenderContentProps) => {
|
||||
const navigation = useNavigation<HomeScreenNavigationProp>();
|
||||
const scrollX = useRef(new Animated.Value(0)).current;
|
||||
|
||||
@ -131,7 +135,7 @@ const RenderContent = () => {
|
||||
<View style={styles.serviceOptionWrapper}>
|
||||
<View style={styles.serviceOptionContainer}>
|
||||
<Pressable
|
||||
onPress={() => navigation.navigate('RegularPassport')}
|
||||
onPress={showDialog}
|
||||
style={({pressed}) => [
|
||||
styles.serviceIcon,
|
||||
{
|
||||
@ -224,13 +228,18 @@ const RenderContent = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function HomeScreen() {
|
||||
type HomeScreenProps = {
|
||||
showDialog: () => void;
|
||||
};
|
||||
|
||||
function HomeScreen({showDialog}: HomeScreenProps) {
|
||||
const navigation = useNavigation<HomeScreenNavigationProp>();
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar
|
||||
backgroundColor={Colors.secondary30.color}
|
||||
barStyle="light-content"
|
||||
barStyle={'light-content'}
|
||||
/>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Text style={styles.appBarTitle}>Halo, X!</Text>
|
||||
@ -241,7 +250,10 @@ function HomeScreen() {
|
||||
onPress={() => navigation.navigate('Notification')}
|
||||
/>
|
||||
</View>
|
||||
<FlatList data={[{}]} renderItem={() => <RenderContent />} />
|
||||
<FlatList
|
||||
data={[{}]}
|
||||
renderItem={() => <RenderContent showDialog={showDialog} />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -1,18 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import {BottomNavigation, Text} from 'react-native-paper';
|
||||
import {
|
||||
BottomNavigation,
|
||||
Button,
|
||||
Dialog,
|
||||
PaperProvider,
|
||||
Portal,
|
||||
Text,
|
||||
} from 'react-native-paper';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import ProfileScreen from '../profile';
|
||||
import styles from './styles';
|
||||
import HomeScreen from '../home';
|
||||
import HistoryScreen from '../history';
|
||||
import {StatusBar, View} from 'react-native';
|
||||
import {useState} from 'react';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
const HomeRoute = () => <HomeScreen />;
|
||||
const HistoryRoute = () => <HistoryScreen />;
|
||||
const ProfileRoute = () => <ProfileScreen />;
|
||||
type NavigationRouteScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'NavigationRoute'
|
||||
>;
|
||||
|
||||
function NavigationRouteScreen() {
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const [routes] = React.useState([
|
||||
const navigation = useNavigation<NavigationRouteScreenNavigationProp>();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [index, setIndex] = useState(0);
|
||||
const [routes] = useState([
|
||||
{
|
||||
key: 'home',
|
||||
title: 'Home',
|
||||
@ -32,13 +48,24 @@ function NavigationRouteScreen() {
|
||||
},
|
||||
]);
|
||||
|
||||
const renderScene = BottomNavigation.SceneMap({
|
||||
home: HomeRoute,
|
||||
history: HistoryRoute,
|
||||
profile: ProfileRoute,
|
||||
});
|
||||
const showDialog = () => setVisible(true);
|
||||
const hideDialog = () => setVisible(false);
|
||||
|
||||
const renderScene = ({route}: {route: {key: string}}) => {
|
||||
switch (route.key) {
|
||||
case 'home':
|
||||
return <HomeScreen showDialog={showDialog} />;
|
||||
case 'history':
|
||||
return <HistoryScreen />;
|
||||
case 'profile':
|
||||
return <ProfileScreen />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PaperProvider>
|
||||
<BottomNavigation
|
||||
navigationState={{index, routes}}
|
||||
onIndexChange={setIndex}
|
||||
@ -55,6 +82,36 @@ function NavigationRouteScreen() {
|
||||
<Text style={styles.bottomNavLabel}>{route.title}</Text>
|
||||
)}
|
||||
/>
|
||||
<Portal>
|
||||
<Dialog visible={visible} style={styles.dialogContainer}>
|
||||
<Dialog.Title style={styles.dialogTitle}>Peringatan</Dialog.Title>
|
||||
<View style={styles.dialogContentContainer}>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Silakan melakukan pengisian kuesioner.
|
||||
</Text>
|
||||
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
|
||||
Pastikan data dan jawaban yang Anda berikan benar.
|
||||
</Text>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Pemberian keterangan yang tidak benar merupakan pelanggaran
|
||||
keimigrasian sebagaimana ketentuan Pasal 126 huruf c UU No. 6
|
||||
tahun 2011 tentang Keimigrasian dan akan mengakibatkan permohonan
|
||||
paspor Anda ditolak dan pembayaran tidak dapat dikembalikan.
|
||||
</Text>
|
||||
<Button
|
||||
style={styles.buttonContinue}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => {
|
||||
hideDialog();
|
||||
navigation.navigate('RegularPassport');
|
||||
}}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</View>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
</PaperProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
bottomNavLabel: {
|
||||
@ -9,6 +10,31 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
position: 'absolute',
|
||||
},
|
||||
dialogContainer: {
|
||||
backgroundColor: 'white',
|
||||
elevation: 0,
|
||||
shadowColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 22,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
dialogContentContainer: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
gap: 16,
|
||||
},
|
||||
dialogDesc: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
color: Colors.primary30.color,
|
||||
},
|
||||
buttonContinue: {
|
||||
backgroundColor: Colors.primary30.color,
|
||||
marginTop: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
@ -55,6 +55,7 @@ function ProfileScreen() {
|
||||
icon="pencil-outline"
|
||||
mode="contained"
|
||||
style={styles.sectionButtonStyle}
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => navigation.navigate('EditProfile')}>
|
||||
Edit Profil
|
||||
</Button>
|
||||
@ -62,6 +63,7 @@ function ProfileScreen() {
|
||||
icon="lock-outline"
|
||||
mode="contained"
|
||||
style={styles.sectionButtonStyle}
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => navigation.navigate('SetPassword')}>
|
||||
Atur Kata Sandi
|
||||
</Button>
|
||||
|
@ -1,11 +1,493 @@
|
||||
import React from 'react';
|
||||
import {Text, View} from 'react-native';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
BackHandler,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
Text,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import styles from './styles';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import {useNavigation} from '@react-navigation/native';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import RadioButtonOptionComponent from '../../components/RadioButtonOption';
|
||||
import {RootStackParamList} from '../../navigation/type';
|
||||
import {NativeStackNavigationProp} from '@react-navigation/native-stack';
|
||||
import {Button, Dialog, PaperProvider, Portal} from 'react-native-paper';
|
||||
import StepIndicator from '../../components/StepIndicator';
|
||||
import TextInputComponent from '../../components/TextInput';
|
||||
import genderData from '../../data/DropdownData/GenderData';
|
||||
import civilStatusData from '../../data/DropdownData/CivilStatus';
|
||||
|
||||
type RegularPassportScreenNavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
'RegularPassport'
|
||||
>;
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: 'Dewasa',
|
||||
description: 'WNI berusia di atas 17 tahun atau sudah menikah',
|
||||
value: 'adult',
|
||||
},
|
||||
{
|
||||
label: 'Anak',
|
||||
description: 'WNI berusia di bawah 17 tahun dan belum menikah',
|
||||
value: 'child',
|
||||
},
|
||||
];
|
||||
|
||||
const hasHadPassportBeforeOptions = [
|
||||
{
|
||||
label: 'Belum',
|
||||
description:
|
||||
'Belum pernah memiliki paspor atau belum pernah mengajukan permohonan paspor',
|
||||
value: 'not_yet',
|
||||
},
|
||||
{
|
||||
label: 'Sudah',
|
||||
description: '',
|
||||
value: 'already',
|
||||
},
|
||||
];
|
||||
|
||||
const previousPassportConditionOptions = [
|
||||
{
|
||||
label: 'Habis masa berlaku',
|
||||
description: 'null',
|
||||
value: 'expired',
|
||||
},
|
||||
{
|
||||
label: 'Penuh/Halaman Penuh',
|
||||
description: 'null',
|
||||
value: 'full_pages',
|
||||
},
|
||||
{
|
||||
label: 'Hilang',
|
||||
description: 'null',
|
||||
value: 'lost',
|
||||
},
|
||||
{
|
||||
label: 'Rusak',
|
||||
description: 'null',
|
||||
value: 'damaged',
|
||||
},
|
||||
{
|
||||
label: 'Hilang karena keadaan kahar',
|
||||
description: 'null',
|
||||
value: 'lost_due_to_force_majeure',
|
||||
},
|
||||
{
|
||||
label: 'Rusak karena keadaan kahar',
|
||||
description: 'null',
|
||||
value: 'damaged_due_to_force_majeure',
|
||||
},
|
||||
];
|
||||
|
||||
const renderApplicationStepsContent = (
|
||||
step: number,
|
||||
subStep: number,
|
||||
setStep: (step: number) => void,
|
||||
setSubStep: (step: number) => void,
|
||||
selectedOption: string,
|
||||
setSelectedOption: (val: string) => void,
|
||||
) => {
|
||||
if (step === 1) {
|
||||
switch (subStep) {
|
||||
case 1:
|
||||
return (
|
||||
<View style={styles.subStepContainer}>
|
||||
<View style={styles.subStepTextWrapper}>
|
||||
<Text style={styles.subStepTitle}>
|
||||
Ambil/Upload Foto KTP Anda
|
||||
</Text>
|
||||
<Text style={styles.subStepDesc}>
|
||||
Pastikan pencahayaan cukup, tulisan pada identitas terlihat
|
||||
jelas, dan jangan gunakan foto dari Live Mode sebelum
|
||||
melanjutkan.
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.nationalIdImage} />
|
||||
<View style={styles.subStepButtonContainer}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => setSubStep(2)}
|
||||
style={styles.subStepButtonActive}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Pilih Foto
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => {}}
|
||||
style={styles.subStepButtonInActive}
|
||||
textColor={Colors.primary30.color}>
|
||||
Ulangi
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.subStepContainer}>
|
||||
<View style={styles.nationalIdImageContainer}>
|
||||
<View style={styles.nationalIdImageCropped} />
|
||||
</View>
|
||||
<View
|
||||
style={[styles.subStepTextInputContainer, {marginBottom: 24}]}>
|
||||
<TextInputComponent
|
||||
title="Nama Lengkap Pemohon"
|
||||
placeholder="Nama Lengkap Anda"
|
||||
isRequired
|
||||
/>
|
||||
<TextInputComponent
|
||||
title="NIK"
|
||||
placeholder="Nama NIK Anda"
|
||||
isRequired
|
||||
/>
|
||||
<View style={styles.subStepTextInputRowContainer}>
|
||||
<View style={styles.subStepTextInputFlex}>
|
||||
<TextInputComponent
|
||||
title="Tanggal Lahir"
|
||||
placeholder="DD/MM/YYYY"
|
||||
isRequired
|
||||
isDate
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.subStepTextInputFlex}>
|
||||
<TextInputComponent
|
||||
title="Jenis Kelamin"
|
||||
placeholder="Jenis Kelamin"
|
||||
isRequired
|
||||
isDropdown
|
||||
dropdownItemData={genderData}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<TextInputComponent
|
||||
title="Status Sipil"
|
||||
placeholder="Pilih Status Sipil Anda"
|
||||
isRequired
|
||||
isDropdown
|
||||
dropdownItemData={civilStatusData}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.subStepButtonContainer}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setStep(2);
|
||||
setSubStep(1);
|
||||
}}
|
||||
style={styles.subStepButtonActive}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Lanjut
|
||||
</Button>
|
||||
<Button
|
||||
mode="outlined"
|
||||
onPress={() => setSubStep(1)}
|
||||
style={styles.subStepButtonInActive}
|
||||
textColor={Colors.primary30.color}>
|
||||
Kembali
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (step === 2) {
|
||||
switch (subStep) {
|
||||
case 1:
|
||||
return (
|
||||
<View style={styles.subStepContainer}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setStep(1);
|
||||
setSubStep(2);
|
||||
}}
|
||||
style={({pressed}) => [
|
||||
styles.subStepButtonBackWrapper,
|
||||
{
|
||||
transform: [{scale: pressed ? 0.99 : 1}],
|
||||
},
|
||||
]}>
|
||||
<Icon name="chevron-left" size={24} />
|
||||
<Text style={styles.subStepButtonBackText}>Kembali</Text>
|
||||
</Pressable>
|
||||
<View style={styles.subStepQuestionnaireOptionContainer}>
|
||||
<Text style={styles.questionnaireData}>
|
||||
Apakah Anda sudah pernah memiliki paspor?
|
||||
</Text>
|
||||
{hasHadPassportBeforeOptions.map(options => (
|
||||
<RadioButtonOptionComponent
|
||||
key={options.value}
|
||||
label={options.label}
|
||||
description={options.description}
|
||||
value={options.value}
|
||||
selectedValue={selectedOption}
|
||||
onSelect={value => {
|
||||
setSelectedOption(value);
|
||||
value === 'already' && setSubStep(2);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<ScrollView>
|
||||
<View style={styles.subStepContainer}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setSubStep(1);
|
||||
}}
|
||||
style={({pressed}) => [
|
||||
styles.subStepButtonBackWrapper,
|
||||
{
|
||||
transform: [{scale: pressed ? 0.99 : 1}],
|
||||
},
|
||||
]}>
|
||||
<Icon name="chevron-left" size={24} />
|
||||
<Text style={styles.subStepButtonBackText}>Kembali</Text>
|
||||
</Pressable>
|
||||
<View style={styles.subStepQuestionnaireOptionContainer}>
|
||||
<Text style={styles.questionnaireData}>
|
||||
Apakah Anda sudah pernah memiliki paspor?
|
||||
</Text>
|
||||
{previousPassportConditionOptions.map(options => (
|
||||
<RadioButtonOptionComponent
|
||||
key={options.value}
|
||||
label={options.label}
|
||||
description={options.description}
|
||||
value={options.value}
|
||||
selectedValue={selectedOption}
|
||||
onSelect={value => {
|
||||
setSelectedOption(value);
|
||||
value === 'already' && setSubStep(2);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setSubStep(3);
|
||||
}}
|
||||
style={styles.subStepButtonActive}
|
||||
textColor={Colors.neutral100.color}>
|
||||
Lanjut
|
||||
</Button>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<View>
|
||||
<Text>Step 2.3</Text>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setStep(3);
|
||||
setSubStep(1);
|
||||
}}>
|
||||
Next
|
||||
</Button>
|
||||
<Button onPress={() => setSubStep(2)}>Back</Button>
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 3:
|
||||
return (
|
||||
<View>
|
||||
<Button mode="contained" onPress={() => setStep(4)}>
|
||||
Next
|
||||
</Button>
|
||||
<Button onPress={() => setStep(2)}>Back</Button>
|
||||
</View>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<View>
|
||||
<Button mode="contained" onPress={() => setStep(5)}>
|
||||
Next
|
||||
</Button>
|
||||
<Button onPress={() => setStep(3)}>Back</Button>
|
||||
</View>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<View>
|
||||
<Button mode="contained" onPress={() => setStep(6)}>
|
||||
Next
|
||||
</Button>
|
||||
<Button onPress={() => setStep(4)}>Back</Button>
|
||||
</View>
|
||||
);
|
||||
case 6:
|
||||
return (
|
||||
<View>
|
||||
<Button mode="contained" onPress={() => setStep(7)}>
|
||||
Next
|
||||
</Button>
|
||||
<Button onPress={() => setStep(5)}>Back</Button>
|
||||
</View>
|
||||
);
|
||||
case 7:
|
||||
return (
|
||||
<View>
|
||||
<Button onPress={() => setStep(6)}>Back</Button>
|
||||
</View>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
function RegularPassportScreen() {
|
||||
const navigation = useNavigation<RegularPassportScreenNavigationProp>();
|
||||
const [selectedOption, setSelectedOption] = useState('');
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [showApplicationStepsContent, setShowApplicationStepsContent] =
|
||||
useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const [subStep, setSubStep] = useState(1);
|
||||
|
||||
const showDialog = () => setVisible(true);
|
||||
const hideDialog = () => setVisible(false);
|
||||
const completedSteps = [...Array(step - 1)].map((_, i) => i + 1);
|
||||
|
||||
const stepTitles: {[key: number]: string} = {
|
||||
1: 'Informasi Pribadi',
|
||||
2: 'Dokumen Pendukung',
|
||||
3: 'Pembayaran',
|
||||
4: 'Konfirmasi Data',
|
||||
5: 'Verifikasi',
|
||||
6: 'Pemrosesan',
|
||||
7: 'Selesai',
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (showApplicationStepsContent) {
|
||||
const backAction = () => {
|
||||
setShowApplicationStepsContent(false);
|
||||
return true;
|
||||
};
|
||||
|
||||
const backHandler = BackHandler.addEventListener(
|
||||
'hardwareBackPress',
|
||||
backAction,
|
||||
);
|
||||
|
||||
return () => backHandler.remove();
|
||||
}
|
||||
}, [showApplicationStepsContent]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Regular Passport Screen</Text>
|
||||
<PaperProvider>
|
||||
<StatusBar
|
||||
backgroundColor={visible ? '#295E70' : Colors.secondary30.color}
|
||||
barStyle="light-content"
|
||||
/>
|
||||
<View style={styles.appBarContainer}>
|
||||
<Icon
|
||||
name="arrow-left"
|
||||
size={24}
|
||||
style={styles.appBarIcon}
|
||||
color={Colors.neutral100.color}
|
||||
onPress={() => navigation.goBack()}
|
||||
/>
|
||||
<Text style={styles.appBarTitle}>Pengajuan Paspor Regular</Text>
|
||||
</View>
|
||||
{showApplicationStepsContent ? (
|
||||
<View style={styles.applicationStepsContainer}>
|
||||
<Text style={styles.stepTitle}>{stepTitles[step]}</Text>
|
||||
<StepIndicator
|
||||
currentStep={step}
|
||||
totalSteps={7}
|
||||
completedSteps={completedSteps}
|
||||
/>
|
||||
{renderApplicationStepsContent(
|
||||
step,
|
||||
subStep,
|
||||
setStep,
|
||||
setSubStep,
|
||||
selectedOption,
|
||||
setSelectedOption,
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.questionnaireTitle}>
|
||||
Kuesioner Layanan Permohonan
|
||||
</Text>
|
||||
<View style={styles.questionnaireOptionContainer}>
|
||||
<Text style={styles.questionnaireData}>
|
||||
Untuk siapa paspor ini?
|
||||
</Text>
|
||||
{options.map(option => (
|
||||
<RadioButtonOptionComponent
|
||||
key={option.value}
|
||||
label={option.label}
|
||||
description={option.description}
|
||||
value={option.value}
|
||||
selectedValue={selectedOption}
|
||||
onSelect={value => {
|
||||
setSelectedOption(value);
|
||||
value === 'child'
|
||||
? showDialog()
|
||||
: setShowApplicationStepsContent(true);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
<Portal>
|
||||
<Dialog visible={visible} style={styles.dialogContainer}>
|
||||
<Dialog.Title style={styles.dialogTitle}>
|
||||
Pemberitahuan
|
||||
</Dialog.Title>
|
||||
<View style={styles.dialogContentContainer}>
|
||||
<Text style={styles.dialogDesc}>
|
||||
Permohonan paspor anak diwajibkan untuk didampingi oleh
|
||||
orang tua/wali yang sah pada saat datang ke Kantor Imigrasi
|
||||
untuk pelaksanaan wawancara dan pengambilan foto sidik jari.
|
||||
</Text>
|
||||
<View>
|
||||
<Button
|
||||
style={styles.buttonAgree}
|
||||
mode="contained"
|
||||
textColor={Colors.neutral100.color}
|
||||
onPress={() => {
|
||||
hideDialog();
|
||||
setShowApplicationStepsContent(true);
|
||||
}}>
|
||||
Lanjut
|
||||
</Button>
|
||||
<Button
|
||||
style={styles.buttonDontAgree}
|
||||
mode="outlined"
|
||||
textColor={Colors.primary30.color}
|
||||
onPress={() => hideDialog()}>
|
||||
Kembali
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
</>
|
||||
)}
|
||||
</PaperProvider>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -1,12 +1,164 @@
|
||||
import {StyleSheet} from 'react-native';
|
||||
import Colors from '../../../assets/styles/Colors';
|
||||
import FontFamily from '../../../assets/styles/FontFamily';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignContent: 'center',
|
||||
backgroundColor: Colors.neutral100.color,
|
||||
},
|
||||
appBarTitle: {
|
||||
color: Colors.neutral100.color,
|
||||
...FontFamily.notoSansRegular,
|
||||
fontSize: 16,
|
||||
marginStart: 16,
|
||||
},
|
||||
appBarIcon: {
|
||||
marginLeft: 16,
|
||||
},
|
||||
appBarContainer: {
|
||||
height: 64,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: Colors.secondary30.color,
|
||||
},
|
||||
questionnaireTitle: {
|
||||
...FontFamily.notoSansExtraBold,
|
||||
fontSize: 18,
|
||||
marginTop: 24,
|
||||
marginBottom: 8,
|
||||
marginHorizontal: 16,
|
||||
color: Colors.secondary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
questionnaireOptionContainer: {
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.secondary50.color,
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
margin: 16,
|
||||
gap: 16,
|
||||
},
|
||||
questionnaireData: {
|
||||
...FontFamily.notoSansBold,
|
||||
fontSize: 14,
|
||||
color: Colors.primary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
dialogContainer: {
|
||||
backgroundColor: 'white',
|
||||
elevation: 0,
|
||||
shadowColor: 'transparent',
|
||||
borderRadius: 20,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 22,
|
||||
color: Colors.secondary30.color,
|
||||
},
|
||||
dialogContentContainer: {
|
||||
marginHorizontal: 24,
|
||||
marginBottom: 24,
|
||||
gap: 16,
|
||||
},
|
||||
dialogDesc: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
color: Colors.primary30.color,
|
||||
},
|
||||
buttonAgree: {
|
||||
backgroundColor: Colors.primary30.color,
|
||||
marginTop: 12,
|
||||
},
|
||||
buttonDontAgree: {
|
||||
borderColor: Colors.primary30.color,
|
||||
marginTop: 12,
|
||||
},
|
||||
applicationStepsContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
stepTitle: {
|
||||
fontSize: 14,
|
||||
...FontFamily.notoSansBold,
|
||||
includeFontPadding: false,
|
||||
color: Colors.secondary30.color,
|
||||
paddingTop: 16,
|
||||
paddingHorizontal: 16,
|
||||
backgroundColor: Colors.secondary70.color,
|
||||
},
|
||||
subStepContainer: {
|
||||
backgroundColor: Colors.neutral100.color,
|
||||
padding: 16,
|
||||
},
|
||||
nationalIdImage: {
|
||||
marginVertical: 16,
|
||||
height: 350,
|
||||
backgroundColor: Colors.primary70.color,
|
||||
borderRadius: 20,
|
||||
},
|
||||
subStepButtonContainer: {
|
||||
gap: 16,
|
||||
},
|
||||
subStepButtonActive: {
|
||||
backgroundColor: Colors.primary30.color,
|
||||
},
|
||||
subStepButtonInActive: {
|
||||
borderColor: Colors.primary30.color,
|
||||
},
|
||||
subStepTextWrapper: {
|
||||
gap: 10,
|
||||
},
|
||||
subStepTitle: {
|
||||
fontSize: 16,
|
||||
...FontFamily.notoSansExtraBold,
|
||||
color: Colors.secondary30.color,
|
||||
includeFontPadding: false,
|
||||
},
|
||||
subStepDesc: {
|
||||
includeFontPadding: false,
|
||||
fontSize: 12,
|
||||
color: Colors.primary10.color,
|
||||
...FontFamily.notoSansRegular,
|
||||
lineHeight: 20,
|
||||
},
|
||||
nationalIdImageContainer: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
nationalIdImageCropped: {
|
||||
marginBottom: 16,
|
||||
width: 225,
|
||||
height: 140,
|
||||
backgroundColor: Colors.primary70.color,
|
||||
borderRadius: 20,
|
||||
},
|
||||
subStepTextInputContainer: {
|
||||
gap: 16,
|
||||
},
|
||||
subStepTextInputRowContainer: {
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
gap: 12,
|
||||
},
|
||||
subStepTextInputFlex: {
|
||||
flex: 1,
|
||||
},
|
||||
subStepButtonBackWrapper: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
},
|
||||
subStepButtonBackText: {
|
||||
...FontFamily.notoSansRegular,
|
||||
includeFontPadding: false,
|
||||
fontSize: 10,
|
||||
},
|
||||
subStepQuestionnaireOptionContainer: {
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.secondary50.color,
|
||||
borderRadius: 8,
|
||||
padding: 16,
|
||||
marginVertical: 16,
|
||||
gap: 16,
|
||||
},
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user