Add passport application flow until step 2 'Kuesioner Layanan Permohonan'

This commit is contained in:
Mochammad Adhi Buchori
2025-04-22 23:35:34 +07:00
parent 38f23f0781
commit 62412cedc9
14 changed files with 1142 additions and 49 deletions

View 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;

View 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;

View File

@ -123,8 +123,9 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
placeholderTextColor={Colors.primary60.color} placeholderTextColor={Colors.primary60.color}
editable={false} editable={false}
value={formattedDate} value={formattedDate}
right={<TextInput.Icon icon="calendar" />} right={<TextInput.Icon icon="calendar" color='#48454E' />}
multiline={false} multiline={false}
textColor='#48454E'
/> />
</Pressable> </Pressable>
{showPicker && ( {showPicker && (
@ -166,6 +167,7 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
) : null ) : null
} }
multiline={isMultiline} multiline={isMultiline}
textColor='#48454E'
/> />
{supportText && <Text style={[styles.supportText]}>{supportText}</Text>} {supportText && <Text style={[styles.supportText]}>{supportText}</Text>}
</View> </View>
@ -196,6 +198,7 @@ const styles = StyleSheet.create({
placeholderText: { placeholderText: {
fontSize: 13, fontSize: 13,
...FontFamily.notoSansRegular, ...FontFamily.notoSansRegular,
includeFontPadding: false,
}, },
dropdown: { dropdown: {
marginTop: 8, marginTop: 8,
@ -205,16 +208,18 @@ const styles = StyleSheet.create({
paddingStart: 16, paddingStart: 16,
paddingEnd: 8, paddingEnd: 8,
borderWidth: 1, borderWidth: 1,
borderColor: Colors.primary40.color, borderColor: '#98989A',
}, },
placeholderDropdownStyle: { placeholderDropdownStyle: {
fontSize: 13, fontSize: 13,
...FontFamily.notoSansRegular, ...FontFamily.notoSansRegular,
includeFontPadding: false,
color: Colors.primary60.color, color: Colors.primary60.color,
}, },
selectedTextStyle: { selectedTextStyle: {
fontSize: 13, fontSize: 13,
...FontFamily.notoSansRegular, ...FontFamily.notoSansRegular,
includeFontPadding: false,
}, },
iconStyle: { iconStyle: {
width: 20, width: 20,

View 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;

View File

@ -49,11 +49,9 @@ function RootStack() {
component={NavigationRouteScreen} component={NavigationRouteScreen}
options={{headerShown: false}} options={{headerShown: false}}
/> />
<Stack.Screen <Stack.Screen name="Home" options={{headerShown: false}}>
name="Home" {() => <HomeScreen showDialog={() => console.log('Show dialog!')} />}
component={HomeScreen} </Stack.Screen>
options={{headerShown: false}}
/>
<Stack.Screen <Stack.Screen
name="History" name="History"
component={HistoryScreen} component={HistoryScreen}

View File

@ -37,10 +37,9 @@ function CloseAccountScreen() {
<PaperProvider> <PaperProvider>
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
<StatusBar <StatusBar
backgroundColor={visible ? '#ADADAF' : Colors.neutral100.color} backgroundColor={visible ? Colors.primary30.color : Colors.primary30.color}
barStyle={visible ? 'light-content' : 'dark-content'} barStyle={visible ? 'light-content' : 'dark-content'}
/> />
<View> <View>
<View style={styles.appBarContainer}> <View style={styles.appBarContainer}>
<Icon <Icon

View File

@ -76,6 +76,7 @@ const styles = StyleSheet.create({
borderRadius: 20, borderRadius: 20,
}, },
dialogTitle: { dialogTitle: {
fontSize: 22,
color: Colors.indicatorRed.color, color: Colors.indicatorRed.color,
}, },
dialogButtonContainer: { dialogButtonContainer: {

View File

@ -1,11 +1,160 @@
import React from 'react'; import React, {useState} from 'react';
import {Text, View} from 'react-native'; import {Button, Text, View} from 'react-native';
import styles from './styles'; 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() { 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 ( return (
<View style={styles.container}> <View style={{flex: 1}}>
<Text>Express Passport Screen</Text> <View>
<Text>{stepTitles[step]}</Text>
</View>
<StepIndicator
currentStep={step}
totalSteps={7}
completedSteps={completedSteps}
/>
{renderContent(step, subStep, setStep, setSubStep)}
</View> </View>
); );
} }

View File

@ -36,7 +36,11 @@ const data = [1, 2, 3, 4];
const ItemSeparator = () => <View style={styles.flatllistGap} />; const ItemSeparator = () => <View style={styles.flatllistGap} />;
const RenderContent = () => { type RenderContentProps = {
showDialog: () => void;
};
const RenderContent = ({showDialog}: RenderContentProps) => {
const navigation = useNavigation<HomeScreenNavigationProp>(); const navigation = useNavigation<HomeScreenNavigationProp>();
const scrollX = useRef(new Animated.Value(0)).current; const scrollX = useRef(new Animated.Value(0)).current;
@ -131,7 +135,7 @@ const RenderContent = () => {
<View style={styles.serviceOptionWrapper}> <View style={styles.serviceOptionWrapper}>
<View style={styles.serviceOptionContainer}> <View style={styles.serviceOptionContainer}>
<Pressable <Pressable
onPress={() => navigation.navigate('RegularPassport')} onPress={showDialog}
style={({pressed}) => [ style={({pressed}) => [
styles.serviceIcon, styles.serviceIcon,
{ {
@ -224,13 +228,18 @@ const RenderContent = () => {
); );
}; };
function HomeScreen() { type HomeScreenProps = {
showDialog: () => void;
};
function HomeScreen({showDialog}: HomeScreenProps) {
const navigation = useNavigation<HomeScreenNavigationProp>(); const navigation = useNavigation<HomeScreenNavigationProp>();
return ( return (
<View style={styles.container}> <View style={styles.container}>
<StatusBar <StatusBar
backgroundColor={Colors.secondary30.color} backgroundColor={Colors.secondary30.color}
barStyle="light-content" barStyle={'light-content'}
/> />
<View style={styles.appBarContainer}> <View style={styles.appBarContainer}>
<Text style={styles.appBarTitle}>Halo, X!</Text> <Text style={styles.appBarTitle}>Halo, X!</Text>
@ -241,7 +250,10 @@ function HomeScreen() {
onPress={() => navigation.navigate('Notification')} onPress={() => navigation.navigate('Notification')}
/> />
</View> </View>
<FlatList data={[{}]} renderItem={() => <RenderContent />} /> <FlatList
data={[{}]}
renderItem={() => <RenderContent showDialog={showDialog} />}
/>
</View> </View>
); );
} }

View File

@ -1,18 +1,34 @@
import * as React from 'react'; 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 Colors from '../../../assets/styles/Colors';
import ProfileScreen from '../profile'; import ProfileScreen from '../profile';
import styles from './styles'; import styles from './styles';
import HomeScreen from '../home'; import HomeScreen from '../home';
import HistoryScreen from '../history'; 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 />; type NavigationRouteScreenNavigationProp = NativeStackNavigationProp<
const HistoryRoute = () => <HistoryScreen />; RootStackParamList,
const ProfileRoute = () => <ProfileScreen />; 'NavigationRoute'
>;
function NavigationRouteScreen() { function NavigationRouteScreen() {
const [index, setIndex] = React.useState(0); const navigation = useNavigation<NavigationRouteScreenNavigationProp>();
const [routes] = React.useState([ const [visible, setVisible] = useState(false);
const [index, setIndex] = useState(0);
const [routes] = useState([
{ {
key: 'home', key: 'home',
title: 'Home', title: 'Home',
@ -32,29 +48,70 @@ function NavigationRouteScreen() {
}, },
]); ]);
const renderScene = BottomNavigation.SceneMap({ const showDialog = () => setVisible(true);
home: HomeRoute, const hideDialog = () => setVisible(false);
history: HistoryRoute,
profile: ProfileRoute, 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 ( return (
<BottomNavigation <PaperProvider>
navigationState={{index, routes}} <BottomNavigation
onIndexChange={setIndex} navigationState={{index, routes}}
renderScene={renderScene} onIndexChange={setIndex}
activeColor={Colors.primary30.color} renderScene={renderScene}
inactiveColor={Colors.neutral100.color} activeColor={Colors.primary30.color}
barStyle={{backgroundColor: Colors.primary30.color}} inactiveColor={Colors.neutral100.color}
theme={{ barStyle={{backgroundColor: Colors.primary30.color}}
colors: { theme={{
secondaryContainer: Colors.neutral100.color, colors: {
}, secondaryContainer: Colors.neutral100.color,
}} },
renderLabel={({route}) => ( }}
<Text style={styles.bottomNavLabel}>{route.title}</Text> renderLabel={({route}) => (
)} <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>
); );
} }

View File

@ -1,5 +1,6 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import Colors from '../../../assets/styles/Colors'; import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
bottomNavLabel: { bottomNavLabel: {
@ -9,6 +10,31 @@ const styles = StyleSheet.create({
fontSize: 12, fontSize: 12,
position: 'absolute', 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; export default styles;

View File

@ -55,6 +55,7 @@ function ProfileScreen() {
icon="pencil-outline" icon="pencil-outline"
mode="contained" mode="contained"
style={styles.sectionButtonStyle} style={styles.sectionButtonStyle}
textColor={Colors.neutral100.color}
onPress={() => navigation.navigate('EditProfile')}> onPress={() => navigation.navigate('EditProfile')}>
Edit Profil Edit Profil
</Button> </Button>
@ -62,6 +63,7 @@ function ProfileScreen() {
icon="lock-outline" icon="lock-outline"
mode="contained" mode="contained"
style={styles.sectionButtonStyle} style={styles.sectionButtonStyle}
textColor={Colors.neutral100.color}
onPress={() => navigation.navigate('SetPassword')}> onPress={() => navigation.navigate('SetPassword')}>
Atur Kata Sandi Atur Kata Sandi
</Button> </Button>

View File

@ -1,11 +1,493 @@
import React from 'react'; import React, {useEffect, useState} from 'react';
import {Text, View} from 'react-native'; import {
BackHandler,
Pressable,
ScrollView,
StatusBar,
Text,
View,
} from 'react-native';
import styles from './styles'; 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() { 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 ( return (
<View style={styles.container}> <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> </View>
); );
} }

View File

@ -1,12 +1,164 @@
import {StyleSheet} from 'react-native'; import {StyleSheet} from 'react-native';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, 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', 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', 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', 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,
}, },
}); });