Finalized all of the passport application flow feature except for step 6

This commit is contained in:
Mochammad Adhi Buchori
2025-04-25 17:27:12 +07:00
parent 2fa2b05918
commit ad67df1461
20 changed files with 1418 additions and 115 deletions

View File

@ -2,6 +2,7 @@ import * as React from 'react';
import {Image, Platform, Pressable, StyleSheet, Text, View} from 'react-native';
import {TextInput} from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialIcons';
import IconMaterialCommunity from 'react-native-vector-icons/MaterialCommunityIcons';
import Colors from '../../assets/styles/Colors';
import FontFamily from '../../assets/styles/FontFamily';
import DateTimePicker from '@react-native-community/datetimepicker';
@ -23,6 +24,8 @@ type DropdownCountryItem = {
interface TextInputComponentProps {
title?: string;
iconButton?: boolean;
onIconButtonPress?: () => void;
placeholder?: string;
isPassword?: boolean;
isRequired?: boolean;
@ -35,10 +38,15 @@ interface TextInputComponentProps {
supportText?: string;
containerHeight?: any;
isMultiline?: boolean;
isDropdownSearchLocation?: boolean;
handlePressSearchLocation?: () => void;
}
const TextInputComponent: React.FC<TextInputComponentProps> = ({
const TextInputComponent = (props: TextInputComponentProps) => {
const {
title,
iconButton = false,
onIconButtonPress,
placeholder,
isPassword = false,
isRequired = false,
@ -51,7 +59,10 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
supportText,
containerHeight,
isMultiline = false,
}) => {
isDropdownSearchLocation = false,
handlePressSearchLocation,
} = props;
const [secureText, setSecureText] = useState(isPassword);
const [selectedDate, setSelectedDate] = useState<Date | undefined>(undefined);
const [formattedDate, setFormattedDate] = useState<string>('');
@ -97,7 +108,10 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
const renderDropdownCountryItem = (item: any) => {
return (
<View style={styles.dropdownCountryItem}>
<Image source={{uri: item.image.uri}} style={styles.imageCountryStyle} />
<Image
source={{uri: item.image.uri}}
style={styles.imageCountryStyle}
/>
<Text style={styles.dropdownTextItem}>{item.label}</Text>
</View>
);
@ -145,6 +159,90 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
};
const renderInput = () => {
if (isDropdown) {
return (
<View>
<View style={styles.headerContainer}>
{title && (
<View style={styles.titleContainer}>
<Text style={styles.title}>{title}</Text>
{isRequired && <Text style={styles.required}>*</Text>}
</View>
)}
{iconButton && (
<Pressable
style={({pressed}) => ({
transform: [{scale: pressed ? 0.925 : 1}],
})}
onPress={onIconButtonPress}>
<IconMaterialCommunity
name="information"
color={Colors.primary30.color}
size={20}
/>
</Pressable>
)}
</View>
<Dropdown
style={[styles.dropdown, isDisabled && styles.outlineColorDisabled]}
placeholderStyle={styles.placeholderDropdownStyle}
selectedTextStyle={styles.selectedTextStyle}
iconStyle={styles.iconStyle}
data={dropdownItemData ?? []}
maxHeight={300}
labelField="label"
valueField="value"
placeholder={placeholder}
value={dropdownValue}
onChange={item => {
setDropdownValue(item.value);
}}
disable={isDisabled}
renderRightIcon={() => <Icon name="arrow-drop-down" size={20} />}
renderItem={renderDropdownItem}
/>
</View>
);
}
if (isDate) {
return (
<View>
<View style={styles.titleContainer}>
{title && <Text style={styles.title}>{title}</Text>}
{isRequired && <Text style={styles.required}>*</Text>}
</View>
<Pressable
onPress={() => !isDisabled && setShowPicker(true)}
style={({pressed}) => ({
transform: [{scale: pressed ? 0.99 : 1}],
})}>
<TextInput
mode="outlined"
placeholder={placeholder}
style={[inputStyle, isDisabled && styles.outlineColorDisabled]}
theme={{roundness: 12}}
placeholderTextColor={Colors.primary60.color}
editable={false}
value={formattedDate}
right={<TextInput.Icon icon="calendar" color="#48454E" />}
multiline={false}
textColor="#48454E"
disabled={isDisabled}
/>
</Pressable>
{showPicker && (
<DateTimePicker
value={selectedDate || new Date()}
mode="date"
display="calendar"
onChange={handleDateChange}
/>
)}
</View>
);
}
if (isDropdownCountry) {
return (
<View>
@ -183,7 +281,7 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
);
}
if (isDropdown) {
if (isDropdownSearchLocation) {
return (
<View>
{title && (
@ -192,36 +290,11 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
{isRequired && <Text style={styles.required}>*</Text>}
</View>
)}
<Dropdown
style={[styles.dropdown, isDisabled && styles.outlineColorDisabled]}
placeholderStyle={styles.placeholderDropdownStyle}
selectedTextStyle={styles.selectedTextStyle}
iconStyle={styles.iconStyle}
data={dropdownItemData ?? []}
maxHeight={300}
labelField="label"
valueField="value"
placeholder={placeholder}
value={dropdownValue}
onChange={item => {
setDropdownValue(item.value);
}}
disable={isDisabled}
renderRightIcon={() => <Icon name="arrow-drop-down" size={20} />}
renderItem={renderDropdownItem}
/>
</View>
);
}
if (isDate) {
return (
<View>
<View style={styles.titleContainer}>
{title && <Text style={styles.title}>{title}</Text>}
{isRequired && <Text style={styles.required}>*</Text>}
</View>
<Pressable onPress={() => !isDisabled && setShowPicker(true)}>
<Pressable
onPress={handlePressSearchLocation}
style={({pressed}) => ({
transform: [{scale: pressed ? 0.99 : 1}],
})}>
<TextInput
mode="outlined"
placeholder={placeholder}
@ -230,20 +303,12 @@ const TextInputComponent: React.FC<TextInputComponentProps> = ({
placeholderTextColor={Colors.primary60.color}
editable={false}
value={formattedDate}
right={<TextInput.Icon icon="calendar" color="#48454E" />}
right={<TextInput.Icon icon="menu-down" color="#48454E" size={20} style={{marginLeft: 24}}/>}
multiline={false}
textColor="#48454E"
disabled={isDisabled}
/>
</Pressable>
{showPicker && (
<DateTimePicker
value={selectedDate || new Date()}
mode="date"
display="calendar"
onChange={handleDateChange}
/>
)}
</View>
);
}
@ -290,6 +355,10 @@ const styles = StyleSheet.create({
backgroundColor: Colors.neutral100.color,
marginTop: 8,
},
headerContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
title: {
...FontFamily.notoSansBold,
fontSize: 12,

View File

@ -64,6 +64,7 @@ const styles = StyleSheet.create({
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
lineHeight: 22,
color: Colors.primary30.color,
},
buttonAgree: {

View File

@ -0,0 +1,69 @@
import {StyleSheet, Text, View} from 'react-native';
import {Dialog, Portal, Button} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
type Props = {
visible: boolean;
onClose: () => void;
};
const DialogCivilStatusDocumentsInfo = (props: Props) => {
const {visible, onClose} = props;
return (
<Portal>
<Dialog visible={visible} style={styles.dialogContainer}>
<View style={styles.dialogContentContainer}>
<Text style={styles.dialogDesc}>
Dokumen identitas diri harus memuat nama, tempat tanggal lahir, dan
nama orang tua.
</Text>
<View>
<Button
style={styles.buttonContinue}
mode="contained"
textColor={Colors.neutral100.color}
onPress={() => {
onClose();
}}>
Lanjut
</Button>
</View>
</View>
</Dialog>
</Portal>
);
};
const styles = StyleSheet.create({
dialogContainer: {
backgroundColor: 'white',
elevation: 0,
shadowColor: 'transparent',
borderRadius: 20,
},
dialogContentContainer: {
marginHorizontal: 24,
marginBottom: 24,
gap: 16,
},
dialogDesc: {
fontSize: 14,
lineHeight: 22,
...FontFamily.notoSansRegular,
includeFontPadding: false,
color: Colors.primary30.color,
},
dialogDescRed: {
...FontFamily.notoSansBold,
color: Colors.indicatorRed.color,
includeFontPadding: false,
},
buttonContinue: {
backgroundColor: Colors.primary30.color,
marginTop: 12,
},
});
export default DialogCivilStatusDocumentsInfo;

View File

@ -0,0 +1,89 @@
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
import {Portal, Dialog, Button} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
type Props = {
visible: boolean;
onClose: () => void;
onContinue: () => void;
};
const DialogFinalizationConfirmation = (props: Props) => {
const {visible, onClose, onContinue} = props;
return (
<Portal>
<Dialog visible={visible} style={styles.dialogContainer}>
<Dialog.Title style={styles.dialogTitle}>
Apakah data yang Anda input sudah benar?
</Dialog.Title>
<View style={styles.dialogContentContainer}>
<Text style={styles.dialogDesc}>
Permohonan Anda akan diajukan dan Anda tidak dapat mengubah kembali
data Anda.{' '}
<Text style={styles.dialogDescBold}>
Biaya yang sudah dibayarkan tidak bisa kembali apabila Anda
memberikan keterangan tidak benar.
</Text>
</Text>
<View>
<Button
style={styles.buttonContained}
mode="contained"
textColor={Colors.neutral100.color}
onPress={onContinue}>
Ya, lanjutkan
</Button>
<Button
style={styles.buttonOutlined}
mode="outlined"
textColor={Colors.primary30.color}
onPress={onClose}>
Tidak, saya ingin mengubah data
</Button>
</View>
</View>
</Dialog>
</Portal>
);
};
const styles = StyleSheet.create({
dialogContainer: {
backgroundColor: 'white',
elevation: 0,
shadowColor: 'transparent',
borderRadius: 20,
},
dialogTitle: {
fontSize: 22,
color: Colors.secondary30.color,
},
dialogDescBold: {
includeFontPadding: false,
...FontFamily.notoSansBold,
},
dialogContentContainer: {
marginHorizontal: 24,
marginBottom: 24,
gap: 16,
},
dialogDesc: {
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
lineHeight: 22,
color: Colors.primary30.color,
},
buttonContained: {
backgroundColor: Colors.primary30.color,
marginTop: 12,
},
buttonOutlined: {
borderColor: Colors.primary30.color,
marginTop: 12,
},
});
export default DialogFinalizationConfirmation;

View File

@ -101,6 +101,7 @@ const styles = StyleSheet.create({
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
lineHeight: 22,
color: Colors.primary30.color,
},
dialogBulletPointWrapper: {

View File

@ -8,7 +8,7 @@ type Props = {
onClose: () => void;
};
const DialogPassportInfo = (props: Props) => {
const DialogPassportConditionInfo = (props: Props) => {
const {visible, onClose} = props;
return (
@ -76,6 +76,7 @@ const styles = StyleSheet.create({
...FontFamily.notoSansRegular,
includeFontPadding: false,
color: Colors.primary30.color,
lineHeight: 22,
},
dialogDescRed: {
...FontFamily.notoSansBold,
@ -88,4 +89,4 @@ const styles = StyleSheet.create({
},
});
export default DialogPassportInfo;
export default DialogPassportConditionInfo;

View File

@ -0,0 +1,96 @@
import {StyleSheet, Text, View} from 'react-native';
import {Dialog, Portal, Button} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
type Props = {
visible: boolean;
onClose: () => void;
};
const DialogPassportTypeInfo = (props: Props) => {
const {visible, onClose} = props;
return (
<Portal>
<Dialog visible={visible} style={styles.dialogContainer}>
<Dialog.Title style={styles.dialogTitle}>
Jenis dan Manfaat Paspor
</Dialog.Title>
<View style={styles.dialogContentContainer}>
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
1. Paspor Biasa
</Text>
<Text style={styles.dialogDesc}>
Biaya pemrosesan lebih murah, tersedia di seluruh lokasi Kanim.
{'\n'}Biaya pembuatan: Rp350.000
</Text>
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
2. Paspor Elektronik
</Text>
<Text style={styles.dialogDesc}>
Terdapat chip pada cover paspor yang menyimpan data pemegang paspor
sehingga meningkatkan fitur keamanan, tersedia di 35 Kanim.{'\n'}
Biaya pembuatan: Rp650.000
</Text>
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
3. Paspor Elektronik Polikarbonat
</Text>
<Text style={styles.dialogDesc}>
Berbahan polikarbonat (PC) pada halaman data pemegang paspor
(halaman 2) yang memiliki fitur keamanan tinggi dan lebih kuat,
tersedia di Kanim Jakarta Selatan, Kanim Jakarta Barat, dan Kanim
Soekarno Hatta.{'\n'}Biaya pembuatan: Rp650.000
</Text>
<View>
<Button
style={styles.buttonContinue}
mode="contained"
textColor={Colors.neutral100.color}
onPress={() => {
onClose();
}}>
Lanjut
</Button>
</View>
</View>
</Dialog>
</Portal>
);
};
const styles = StyleSheet.create({
dialogContainer: {
backgroundColor: 'white',
elevation: 0,
shadowColor: 'transparent',
borderRadius: 20,
},
dialogContentContainer: {
marginHorizontal: 24,
marginBottom: 24,
gap: 16,
},
dialogTitle: {
fontSize: 22,
color: Colors.secondary30.color,
},
dialogDesc: {
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
color: Colors.primary30.color,
lineHeight: 22,
},
dialogDescRed: {
...FontFamily.notoSansBold,
color: Colors.indicatorRed.color,
includeFontPadding: false,
},
buttonContinue: {
backgroundColor: Colors.primary30.color,
marginTop: 12,
},
});
export default DialogPassportTypeInfo;

View File

@ -0,0 +1,73 @@
import {StyleSheet, Text, View} from 'react-native';
import {Dialog, Portal, Button} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
type Props = {
visible: boolean;
onSubmitSuccess: () => void;
};
const DialogSubmitSuccess = (props: Props) => {
const {visible, onSubmitSuccess} = props;
return (
<Portal>
<Dialog visible={visible} style={styles.dialogContainer}>
<Dialog.Title style={styles.dialogTitle}>
Pengajuan Permohonan Sukses!
</Dialog.Title>
<View style={styles.dialogContentContainer}>
<Text style={styles.dialogDesc}>
Permohonan telah diajukan, klik kartu pada beranda untuk melihat
kode layanan, kode QR, kode billing, serta tata cara pembayarannya.
</Text>
<View>
<Button
style={styles.buttonContinue}
mode="contained"
textColor={Colors.neutral100.color}
onPress={() => {
onSubmitSuccess();
}}>
Oke
</Button>
</View>
</View>
</Dialog>
</Portal>
);
};
const styles = StyleSheet.create({
dialogContainer: {
backgroundColor: 'white',
elevation: 0,
shadowColor: 'transparent',
borderRadius: 20,
},
dialogContentContainer: {
marginHorizontal: 24,
marginBottom: 24,
gap: 16,
},
dialogTitle: {
fontSize: 22,
color: Colors.secondary30.color,
...FontFamily.notoSansBold,
includeFontPadding: false,
},
dialogDesc: {
fontSize: 14,
...FontFamily.notoSansRegular,
lineHeight: 22,
includeFontPadding: false,
color: Colors.primary30.color,
},
buttonContinue: {
backgroundColor: Colors.primary30.color,
marginTop: 12,
},
});
export default DialogSubmitSuccess;

View File

@ -0,0 +1,321 @@
import React, {useState} from 'react';
import {
StyleSheet,
Text,
View,
Modal,
Pressable,
ScrollView,
} from 'react-native';
import {Portal, Button} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import FontFamily from '../../../assets/styles/FontFamily';
import TextInputComponent from '../TextInput';
import genderData from '../../data/DropdownData/GenderData';
type SheetEditDataProps = {
visible: boolean;
onClose: () => void;
showCivilStatusDocumentsInfoDialog: () => void;
selectedPassportOption: string;
};
interface DocumentUploadSectionProps {
title: string;
isRequired?: boolean;
isIcon?: boolean;
showDialogCivilStatusDocumentsInfo?: () => void;
}
const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
const {title, isRequired, isIcon, showDialogCivilStatusDocumentsInfo} = props;
const [uploadedFileName, setUploadedFileName] = useState<string | null>(null);
const handleUpload = () => {
let fileName = `${title.toLowerCase().replace(/ /g, '')}.jpg`;
if (
title === 'Akta kelahiran/ijazah/akta perkawinan/buku nikah/surat baptis'
) {
fileName = 'ijazah.jpg';
}
setUploadedFileName(fileName);
};
const handleDelete = () => setUploadedFileName(null);
return (
<View>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>
{title} {isRequired && <Text style={styles.required}>*</Text>}
</Text>
{isIcon && (
<Pressable
style={({pressed}) => ({
transform: [{scale: pressed ? 0.925 : 1}],
})}
onPress={showDialogCivilStatusDocumentsInfo}>
<Icon name="information" size={24} color={Colors.primary30.color} />
</Pressable>
)}
</View>
{!uploadedFileName ? (
<View style={styles.uploadButtonGroup}>
<Button
icon="camera-outline"
mode="contained"
style={styles.uploadButton}
textColor={Colors.neutral100.color}
labelStyle={{fontSize: 12}}
onPress={handleUpload}>
Foto Dokumen
</Button>
<Button
icon="tray-arrow-up"
mode="contained"
style={styles.uploadButton}
textColor={Colors.neutral100.color}
labelStyle={{fontSize: 12}}
onPress={handleUpload}>
Unggah Dokumen
</Button>
</View>
) : (
<View style={styles.uploadedContainer}>
<View style={styles.uploadedTextWrapper}>
<Text style={styles.uploadedTitle}>Berhasil dipilih</Text>
<Text style={styles.uploadedFilename}>{uploadedFileName}</Text>
</View>
<Icon
name="trash-can-outline"
size={24}
color={Colors.indicatorRed.color}
onPress={handleDelete}
/>
</View>
)}
</View>
);
};
const SheetEditData = (props: SheetEditDataProps) => {
const {
visible,
onClose,
showCivilStatusDocumentsInfoDialog,
selectedPassportOption,
} = props;
return (
<Portal>
<Modal visible={visible} transparent animationType="slide">
<Pressable style={styles.backdrop} onPress={onClose} />
<View style={styles.modalContainer}>
<ScrollView
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingBottom: 24}}>
<View style={styles.modalHeader}>
<Text style={styles.modalTitle}>Ubah Data Pemohon</Text>
<Pressable
style={({pressed}) => ({
transform: [{scale: pressed ? 0.925 : 1}],
})}
onPress={onClose}>
<Icon name="close" size={24} color={Colors.primary30.color} />
</Pressable>
</View>
<View style={styles.infoList}>
<View style={styles.infoItem}>
<Text style={styles.bullet}></Text>
<Text style={styles.infoText}>
Unggah dokumen hanya bisa berbentuk JPG.
</Text>
</View>
<View style={styles.infoItem}>
<Text style={styles.bullet}></Text>
<Text style={styles.infoText}>
Data yang bertanda <Text style={styles.required}>*</Text>{' '}
wajib diisi.
</Text>
</View>
</View>
<View style={{gap: 16}}>
<TextInputComponent
title="Nama Pemohon"
placeholder="Salwa Aisyah Adhani"
isRequired
isDisabled
/>
<View style={styles.row}>
<View style={styles.flex}>
<TextInputComponent
title="Tanggal Lahir"
placeholder="22/02/2002"
isRequired
isDate
isDisabled
/>
</View>
<View style={styles.flex}>
<TextInputComponent
title="Jenis Kelamin"
placeholder="Wanita"
isRequired
isDropdown
isDisabled
dropdownItemData={genderData}
/>
</View>
</View>
<TextInputComponent
title="NIK"
placeholder="3271234560009123456"
isRequired
isDisabled
/>
<DocumentUploadSection title="e-KTP" isRequired />
<DocumentUploadSection title="Kartu Keluarga" />
<DocumentUploadSection
title="Akta kelahiran/ijazah/akta perkawinan/buku nikah/surat baptis"
isIcon
showDialogCivilStatusDocumentsInfo={
showCivilStatusDocumentsInfoDialog
}
/>
{selectedPassportOption !== 'already' && (
<DocumentUploadSection title="Paspor Lama" isRequired />
)}
</View>
<View style={styles.saveButtonWrapper}>
<Button
style={styles.saveButton}
mode="contained"
textColor={Colors.neutral100.color}
onPress={onClose}>
Simpan
</Button>
</View>
</ScrollView>
</View>
</Modal>
</Portal>
);
};
const styles = StyleSheet.create({
backdrop: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.3)',
},
modalContainer: {
backgroundColor: 'white',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
paddingTop: 24,
paddingHorizontal: 16,
position: 'absolute',
bottom: 0,
width: '100%',
maxHeight: '90%',
},
modalHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
modalTitle: {
fontSize: 12,
color: Colors.primary30.color,
...FontFamily.notoSansBold,
},
infoList: {
marginVertical: 16,
},
infoItem: {
flexDirection: 'row',
alignItems: 'flex-start',
gap: 6,
},
bullet: {
fontSize: 10,
...FontFamily.notoSansBold,
color: Colors.primary10.color,
lineHeight: 20,
},
infoText: {
fontSize: 10,
...FontFamily.notoSansBold,
color: Colors.primary10.color,
lineHeight: 20,
flex: 1,
textAlign: 'justify',
},
required: {
color: Colors.indicatorRed.color,
},
row: {
flexDirection: 'row',
gap: 12,
},
flex: {
flex: 1,
},
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
},
sectionTitle: {
marginBottom: 8,
fontSize: 12,
color: Colors.primary30.color,
...FontFamily.notoSansBold,
flex: 1,
},
uploadButtonGroup: {
flexDirection: 'row',
gap: 16,
},
uploadButton: {
flex: 1,
backgroundColor: Colors.secondary30.color,
},
uploadedContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderWidth: 1,
borderColor: Colors.secondary50.color,
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 8,
},
uploadedTextWrapper: {
gap: 4,
},
uploadedTitle: {
color: Colors.primary30.color,
fontSize: 12,
...FontFamily.notoSansMedium,
},
uploadedFilename: {
color: Colors.primary40.color,
fontSize: 10,
...FontFamily.notoSansRegular,
},
saveButtonWrapper: {
marginTop: 24,
},
saveButton: {
backgroundColor: Colors.primary30.color,
},
});
export default SheetEditData;

View File

@ -0,0 +1,241 @@
import {
FlatList,
Modal,
Pressable,
ScrollView,
StyleSheet,
Text,
View,
} from 'react-native';
import {Button, Portal, TextInput} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import immigrationOfficesData from '../../data/Steps/ImmigrationOfficesData';
import {useState} from 'react';
type SheetSearchLocationProps = {
visible: boolean;
onClose: () => void;
};
const ItemSeparator = () => <View style={styles.flatllistGap} />;
const renderSearchInput = () => {
return (
<View>
<TextInput
mode="outlined"
style={styles.searchInput}
theme={{roundness: 12}}
placeholderTextColor={Colors.primary60.color}
activeOutlineColor={Colors.primary10.color}
placeholder="Cari lokasi"
textColor="#48454E"
contentStyle={{marginLeft: 48}}
left={
<TextInput.Icon
icon="magnify"
color="#48454E"
style={{marginLeft: 8}}
/>
}
/>
</View>
);
};
const renderCurrentLocation = (onPress: () => void) => {
return (
<Pressable
onPress={onPress}
style={({pressed}) => [
styles.currentLocationContainer,
{
transform: [{scale: pressed ? 0.985 : 1}],
},
]}>
<Icon name="crosshairs-gps" size={24} color={Colors.primary30.color} />
<View style={styles.currentLocationTextContainer}>
<Text style={styles.currentLocationTitle}>
Pakai lokasi saya saat ini
</Text>
<Text style={styles.currentLocationSubtitle}>
Jl. Raya Muchtar No. 8, Depok, 16511
</Text>
</View>
</Pressable>
);
};
const renderNearestLocationItem = ({
item,
onPress,
}: {
item: any;
onPress: () => void;
}) => {
return (
<Pressable
onPress={onPress}
style={({pressed}) => [
styles.nearestLocationContainer,
{
transform: [{scale: pressed ? 0.985 : 1}],
},
]}>
<View style={styles.nearestLocationInfoWrapper}>
<View style={styles.nearestLocationHeader}>
<Text style={styles.locationName}>{item.name}</Text>
<Text style={styles.locationDistance}>{item.distance}</Text>
</View>
<Text style={styles.locationAddress}>{item.address}</Text>
</View>
<View style={styles.buttonWrapper}>
<Button
mode="contained"
textColor={Colors.neutral100.color}
style={styles.selectButton}>
Pilih Kantor
</Button>
<Button
mode="outlined"
textColor={Colors.primary30.color}
style={styles.viewButton}>
Lihat Lokasi
</Button>
</View>
</Pressable>
);
};
const SheetSearchLocation = (props: SheetSearchLocationProps) => {
const {visible, onClose} = props;
const [showNearestLocations, setShowNearestLocations] = useState(false);
return (
<Portal>
<Modal visible={visible} transparent animationType="slide">
<Pressable style={styles.backdrop} onPress={onClose} />
<View style={styles.modalContainer}>
{renderSearchInput()}
{!showNearestLocations &&
renderCurrentLocation(() => setShowNearestLocations(true))}
{showNearestLocations && (
<FlatList
data={immigrationOfficesData}
keyExtractor={item => item.id}
renderItem={({item}) =>
renderNearestLocationItem({item, onPress: onClose})
}
contentContainerStyle={{marginVertical: 16, paddingBottom: 32}}
showsVerticalScrollIndicator={false}
ItemSeparatorComponent={ItemSeparator}
/>
)}
</View>
</Modal>
</Portal>
);
};
const styles = StyleSheet.create({
backdrop: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.3)',
},
modalContainer: {
backgroundColor: 'white',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
paddingTop: 24,
paddingHorizontal: 16,
position: 'absolute',
bottom: 0,
height: '90%',
width: '100%',
maxHeight: '90%',
},
searchInput: {
backgroundColor: Colors.neutral100.color,
fontSize: 13,
...FontFamily.notoSansRegular,
includeFontPadding: false,
},
currentLocationContainer: {
marginVertical: 16,
padding: 16,
flexDirection: 'row',
gap: 12,
alignItems: 'center',
borderWidth: 1,
borderRadius: 8,
borderColor: Colors.primary30.color,
},
currentLocationTextContainer: {
gap: 6,
},
currentLocationTitle: {
includeFontPadding: false,
fontSize: 14,
...FontFamily.notoSansMedium,
color: Colors.primary30.color,
},
currentLocationSubtitle: {
includeFontPadding: false,
fontSize: 12,
...FontFamily.notoSansRegular,
color: Colors.primary40.color,
},
nearestLocationContainer: {
padding: 16,
borderRadius: 8,
gap: 16,
borderWidth: 1,
borderColor: Colors.secondary30.color,
},
nearestLocationInfoWrapper: {
gap: 8,
},
nearestLocationHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
},
locationName: {
color: Colors.secondary30.color,
includeFontPadding: false,
...FontFamily.notoSansBold,
fontSize: 14,
flex: 1,
},
locationDistance: {
color: Colors.primary60.color,
includeFontPadding: false,
fontSize: 12,
...FontFamily.notoSansMedium,
},
locationAddress: {
color: Colors.primary40.color,
includeFontPadding: false,
fontSize: 12,
...FontFamily.notoSansRegular,
},
buttonWrapper: {
flexDirection: 'row',
gap: 12,
},
selectButton: {
backgroundColor: Colors.primary30.color,
flex: 1,
},
viewButton: {
borderColor: Colors.primary30.color,
flex: 1,
},
flatllistGap: {
height: 16,
},
});
export default SheetSearchLocation;

View File

@ -0,0 +1,96 @@
import {StyleSheet, Text, View} from 'react-native';
import {Dialog, Portal, Button} from 'react-native-paper';
import Colors from '../../../assets/styles/Colors';
import FontFamily from '../../../assets/styles/FontFamily';
type Props = {
visible: boolean;
onClose: () => void;
};
const SheetSelectDate = (props: Props) => {
const {visible, onClose} = props;
return (
<Portal>
<Dialog visible={visible} style={styles.dialogContainer}>
<Dialog.Title style={styles.dialogTitle}>
Jenis dan Manfaat Paspor
</Dialog.Title>
<View style={styles.dialogContentContainer}>
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
1. Paspor Biasa
</Text>
<Text style={styles.dialogDesc}>
Biaya pemrosesan lebih murah, tersedia di seluruh lokasi Kanim.
{'\n'}Biaya pembuatan: Rp350.000
</Text>
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
2. Paspor Elektronik
</Text>
<Text style={styles.dialogDesc}>
Terdapat chip pada cover paspor yang menyimpan data pemegang paspor
sehingga meningkatkan fitur keamanan, tersedia di 35 Kanim.{'\n'}
Biaya pembuatan: Rp650.000
</Text>
<Text style={[styles.dialogDesc, {...FontFamily.notoSansBold}]}>
3. Paspor Elektronik Polikarbonat
</Text>
<Text style={styles.dialogDesc}>
Berbahan polikarbonat (PC) pada halaman data pemegang paspor
(halaman 2) yang memiliki fitur keamanan tinggi dan lebih kuat,
tersedia di Kanim Jakarta Selatan, Kanim Jakarta Barat, dan Kanim
Soekarno Hatta.{'\n'}Biaya pembuatan: Rp650.000
</Text>
<View>
<Button
style={styles.buttonContinue}
mode="contained"
textColor={Colors.neutral100.color}
onPress={() => {
onClose();
}}>
Lanjut
</Button>
</View>
</View>
</Dialog>
</Portal>
);
};
const styles = StyleSheet.create({
dialogContainer: {
backgroundColor: 'white',
elevation: 0,
shadowColor: 'transparent',
borderRadius: 20,
},
dialogContentContainer: {
marginHorizontal: 24,
marginBottom: 24,
gap: 16,
},
dialogTitle: {
fontSize: 22,
color: Colors.secondary30.color,
},
dialogDesc: {
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
color: Colors.primary30.color,
lineHeight: 22,
},
dialogDescRed: {
...FontFamily.notoSansBold,
color: Colors.indicatorRed.color,
includeFontPadding: false,
},
buttonContinue: {
backgroundColor: Colors.primary30.color,
marginTop: 12,
},
});
export default SheetSelectDate;

View File

@ -0,0 +1,67 @@
const immigrationOfficesData = [
{
id: '1',
name: 'MAL PELAYANAN PUBLIK KOTA DEPOK',
address:
'JL. MARGONDA NO. 54, DEPOK, KEC. PANCORAN MAS, KOTA DEPOK, JAWA BARAT.',
distance: '1.5 Km',
},
{
id: '2',
name: 'KANTOR IMIGRASI DEPOK',
address:
'JALAN BOULEVARD RAYA, KOMPLEK PERKANTORAN PEMDA DEPOK, GRAND DEPOK CITY, KOTA DEPOK, JAWA BARAT.',
distance: '2.2 Km',
},
{
id: '3',
name: 'IMMIGRATION LOUNGE PESONA SQUARE',
address:
'MALL PESONA SQUARE LANTAI 3, JL. IR. H. JUANDA NO. 22A, BAKTI JAYA, KOTA DEPOK, JAWA BARAT.',
distance: '4.4 Km',
},
{
id: '4',
name: 'ULP DEPOK',
address: 'DEPOK TOWN SQUARE',
distance: '4.5 Km',
},
{
id: '5',
name: 'KANTOR IMIGRASI KABUPATEN BOGOR',
address: 'JL. RAYA KESATUAN NO. 45, BOGOR, JAWA BARAT.',
distance: '5.0 Km',
},
{
id: '6',
name: 'IMMIGRATION LOUNGE CIBINONG',
address: 'CIBINONG SQUARE, JALAN RAYA CIBINONG NO. 8, BOGOR, JAWA BARAT.',
distance: '6.3 Km',
},
{
id: '7',
name: 'KANTOR IMIGRASI KOTA BOGOR',
address: 'JL. PELABUHAN NO. 10, BOGOR, JAWA BARAT.',
distance: '7.1 Km',
},
{
id: '8',
name: 'MAL PELAYANAN PUBLIK KOTA BOGOR',
address: 'JL. SUKABUMI NO. 12, BOGOR, JAWA BARAT.',
distance: '7.8 Km',
},
{
id: '9',
name: 'KANTOR IMIGRASI CILEUNGSI',
address: 'JALAN RAYA CILEUNGSI NO. 17, CILEUNGSI, JAWA BARAT.',
distance: '8.4 Km',
},
{
id: '10',
name: 'IMMIGRATION LOUNGE PUNCAK',
address: 'PUNCAK RESORT, JALAN RAYA PUNCAK NO. 30, BOGOR, JAWA BARAT.',
distance: '9.2 Km',
},
];
export default immigrationOfficesData;

View File

@ -29,6 +29,7 @@ const styles = StyleSheet.create({
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
lineHeight: 22,
color: Colors.primary30.color,
},
buttonContinue: {

View File

@ -1,12 +1,5 @@
import React, {useEffect, useState} from 'react';
import {
BackHandler,
Pressable,
ScrollView,
StatusBar,
Text,
View,
} from 'react-native';
import {BackHandler, StatusBar, Text, View} from 'react-native';
import styles from './styles';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import {useNavigation} from '@react-navigation/native';
@ -14,28 +7,19 @@ 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, PaperProvider} from 'react-native-paper';
import {PaperProvider} from 'react-native-paper';
import StepIndicator from '../../components/StepIndicator';
import TextInputComponent from '../../components/TextInput';
import DialogApplicationPassport from '../../components/dialog/DialogApplicationPassport';
import DialogDontHaveYetPassport from '../../components/dialog/DialogDontHaveYetPassport';
import DialogPassportInfo from '../../components/dialog/DialogPassportInfo';
import DialogLostOrDamagedPassport from '../../components/dialog/DialogLostOrDamagedPassport';
import familyRelationshipData from '../../data/DropdownData/FamilyRelationshipData';
import FontFamily from '../../../assets/styles/FontFamily';
import passportAppointmentData from '../../data/History/PassportAppointmentData';
import Step7Completion from './steps/Step7Completion/Step7Completion';
import Step6Processing from './steps/Step6Processing/Step6Processing';
import Step5Verification from './steps/Step5Verification/Step5Verification';
import Step3Payment from './steps/Step3Payment/Step3Payment';
// Data
import arrivalDateGuidelinesData from '../../data/Steps/ArrivalDateGuidelinesData';
// Options Data
import passportForOptions from '../../data/Options/PassportForOptions';
import durationAbroadOptions from '../../data/Options/DurationAbroadOptions';
import destinationFamilyContactOptions from '../../data/Options/DestinationFamilyContactOptions';
import Step4DataConfirmationSubStep2 from './steps/Step4DataConfirmation/Step4DataConfirmationSubStep2';
import Step4DataConfirmationSubStep1 from './steps/Step4DataConfirmation/Step4DataConfirmationSubStep1';
import Step1PersonalInfoSubStep1 from './steps/Step1PersonalInfo/Step1PersonalInfoSubStep1';
@ -52,6 +36,13 @@ import Step2SupportingDocsSubStep8 from './steps/Step2SupportingDocs/Step2Suppor
import Step2SupportingDocsSubStep9 from './steps/Step2SupportingDocs/Step2SupportingDocsSubStep9';
import Step2SupportingDocsSubStep10 from './steps/Step2SupportingDocs/Step2SupportingDocsSubStep10';
import Step2SupportingDocsSubStep11 from './steps/Step2SupportingDocs/Step2SupportingDocsSubStep11';
import DialogCivilStatusDocumentsInfo from '../../components/dialog/DialogCivilStatusDocumentsInfo';
import DialogSubmitSuccess from '../../components/dialog/DialogSubmitSuccess';
import DialogFinalizationConfirmation from '../../components/dialog/DialogFinalizationConfirmation';
import DialogPassportConditionInfo from '../../components/dialog/DialogPassportConditionInfo';
import DialogPassportTypeInfo from '../../components/dialog/DialogPassportTypeInfo';
import SheetEditData from '../../components/sheet/SheetEditData';
import SheetSearchLocation from '../../components/sheet/SheetSearchLocation';
type RegularPassportScreenNavigationProp = NativeStackNavigationProp<
RootStackParamList,
@ -72,6 +63,13 @@ type RenderApplicationStepsContentProps = {
showDontHaveYetDialog: () => void;
showPassportInfoDialog: () => void;
showLostOrDamagedPassportDialog: () => void;
showCivilStatusDocumentsInfoDialog: () => void;
showSubmitSuccessDialog: () => void;
setLastCompletedSteps: () => void;
showFinalizationConfirmationDialog: () => void;
showPassportTypeInfoDialog: () => void;
showEditDataSheet: () => void;
showSearchLocationSheet: () => void;
};
const RenderApplicationStepsContent = (
@ -91,6 +89,13 @@ const RenderApplicationStepsContent = (
showDontHaveYetDialog,
showPassportInfoDialog,
showLostOrDamagedPassportDialog,
showCivilStatusDocumentsInfoDialog,
showSubmitSuccessDialog,
setLastCompletedSteps,
showFinalizationConfirmationDialog,
showPassportTypeInfoDialog,
showEditDataSheet,
showSearchLocationSheet,
} = props;
if (step === 1) {
@ -208,6 +213,9 @@ const RenderApplicationStepsContent = (
setStep={setStep}
setSubStep={setSubStep}
selectedPassportOption={selectedPassportOption}
showCivilStatusDocumentsInfoDialog={
showCivilStatusDocumentsInfoDialog
}
/>
);
case 5:
@ -216,17 +224,26 @@ const RenderApplicationStepsContent = (
setStep={setStep}
setSubStep={setSubStep}
passportAppointmentData={passportAppointmentData}
showEditDataSheet={showEditDataSheet}
/>
);
case 6:
return (
<Step6Processing
setStep={setStep}
arrivalDateGuidelines={arrivalDateGuidelinesData}
showFinalizationConfirmationDialog={
showFinalizationConfirmationDialog
}
showPassportTypeInfoDialog={showPassportTypeInfoDialog}
showSearchLocationSheet={showSearchLocationSheet}
/>
);
case 7:
return <Step7Completion setStep={setStep} />;
return (
<Step7Completion
showSubmitSuccessDialog={showSubmitSuccessDialog}
setLastCompletedSteps={setLastCompletedSteps}
/>
);
default:
return null;
}
@ -288,6 +305,9 @@ function RegularPassportScreen() {
useState(false);
const [step, setStep] = useState(1);
const [subStep, setSubStep] = useState(1);
const [completedSteps, setCompletedSteps] = useState<number[]>(
[...Array(step - 1)].map((_, i) => i + 1),
);
// Dialog visibility states
const [visible, setVisible] = useState(false);
@ -299,6 +319,23 @@ function RegularPassportScreen() {
visibleLostOrDamagedPassportDialog,
setVisibleLostOrDamagedPassportDialog,
] = useState(false);
const [
visibleCivilStatusDocumentsInfoDialog,
setVisibleCivilStatusDocumentsInfoDialog,
] = useState(false);
const [visibleSubmitSuccessDialog, setVisibleSubmitSuccessDialog] =
useState(false);
const [
visibleFinalizationConfirmationDialog,
setVisibleFinalizationConfirmationDialog,
] = useState(false);
const [visiblePassportTypeInfoDialog, setVisiblePassportTypeInfoDialog] =
useState(false);
// Sheet visibility states
const [visibleEditDataSheet, setVisibleEditDataSheet] = useState(false);
const [visibleSearchLocationSheet, setVisibleSearchLocationSheet] =
useState(false);
// Dialog visibility function
const showDialog = () => setVisible(true);
@ -310,12 +347,35 @@ function RegularPassportScreen() {
const showPassportInfoDialog = () => setVisiblePassportInfoDialog(true);
const hidePassportInfoDialog = () => setVisiblePassportInfoDialog(false);
const showVisibleLostOrDamagedPassportDialog = () =>
const showLostOrDamagedPassportDialog = () =>
setVisibleLostOrDamagedPassportDialog(true);
const hideVisibleLostOrDamagedPassportDialog = () =>
const hideLostOrDamagedPassportDialog = () =>
setVisibleLostOrDamagedPassportDialog(false);
const completedSteps = [...Array(step - 1)].map((_, i) => i + 1);
const showCivilStatusDocumentsInfoDialog = () =>
setVisibleCivilStatusDocumentsInfoDialog(true);
const hideCivilStatusDocumentsInfoDialog = () =>
setVisibleCivilStatusDocumentsInfoDialog(false);
const showSubmitSuccessDialog = () => setVisibleSubmitSuccessDialog(true);
const hideSubmitSuccessDialog = () => setVisibleSubmitSuccessDialog(false);
const showFinalizationConfirmationDialog = () =>
setVisibleFinalizationConfirmationDialog(true);
const hideFinalizationConfirmationDialog = () =>
setVisibleFinalizationConfirmationDialog(false);
const showPassportTypeInfoDialog = () =>
setVisiblePassportTypeInfoDialog(true);
const hidePassportTypeInfoDialog = () =>
setVisiblePassportTypeInfoDialog(false);
// Sheet visibility function
const showEditDataSheet = () => setVisibleEditDataSheet(true);
const hideEditDataSheet = () => setVisibleEditDataSheet(false);
const showSearchLocationSheet = () => setVisibleSearchLocationSheet(true);
const hideSearchLocationSheet = () => setVisibleSearchLocationSheet(false);
const stepTitles: {[key: number]: string} = {
1: 'Informasi Pribadi',
@ -344,6 +404,24 @@ function RegularPassportScreen() {
}
}, [showApplicationStepsContent]);
useEffect(() => {
if (step > 1) {
const updatedCompletedSteps = [...Array(step - 1)].map((_, i) => i + 1);
setCompletedSteps(updatedCompletedSteps);
} else {
setCompletedSteps([]);
}
}, [step]);
const setLastCompletedSteps = () => {
setCompletedSteps(prevCompletedSteps => {
if (!prevCompletedSteps.includes(7)) {
return [...prevCompletedSteps, 7];
}
return prevCompletedSteps;
});
};
// Render steps or questionnaire
const renderApplicationStepsContent = showApplicationStepsContent ? (
<>
@ -367,9 +445,18 @@ function RegularPassportScreen() {
setCheckedOption={setCheckedOption}
showDontHaveYetDialog={showDontHaveYetDialog}
showPassportInfoDialog={showPassportInfoDialog}
showLostOrDamagedPassportDialog={
showVisibleLostOrDamagedPassportDialog
showLostOrDamagedPassportDialog={showLostOrDamagedPassportDialog}
showCivilStatusDocumentsInfoDialog={
showCivilStatusDocumentsInfoDialog
}
showSubmitSuccessDialog={showSubmitSuccessDialog}
setLastCompletedSteps={setLastCompletedSteps}
showFinalizationConfirmationDialog={
showFinalizationConfirmationDialog
}
showPassportTypeInfoDialog={showPassportTypeInfoDialog}
showEditDataSheet={showEditDataSheet}
showSearchLocationSheet={showSearchLocationSheet}
/>
</View>
@ -382,7 +469,7 @@ function RegularPassportScreen() {
)}
{visiblePassportInfoDialog && (
<DialogPassportInfo
<DialogPassportConditionInfo
visible={visiblePassportInfoDialog}
onClose={hidePassportInfoDialog}
/>
@ -398,10 +485,63 @@ function RegularPassportScreen() {
setShowApplicationStepsContent(false);
setStep(1);
setSubStep(1);
hideVisibleLostOrDamagedPassportDialog();
hideLostOrDamagedPassportDialog();
}}
/>
)}
{visibleCivilStatusDocumentsInfoDialog && (
<DialogCivilStatusDocumentsInfo
visible={visibleCivilStatusDocumentsInfoDialog}
onClose={hideCivilStatusDocumentsInfoDialog}
/>
)}
{visibleSubmitSuccessDialog && (
<DialogSubmitSuccess
visible={visibleSubmitSuccessDialog}
onSubmitSuccess={() => {
navigation.goBack(), hideSubmitSuccessDialog();
}}
/>
)}
{visibleFinalizationConfirmationDialog && (
<DialogFinalizationConfirmation
visible={visibleFinalizationConfirmationDialog}
onClose={hideFinalizationConfirmationDialog}
onContinue={() => {
setStep(7);
hideFinalizationConfirmationDialog();
}}
/>
)}
{visiblePassportTypeInfoDialog && (
<DialogPassportTypeInfo
visible={visiblePassportTypeInfoDialog}
onClose={hidePassportTypeInfoDialog}
/>
)}
{visibleEditDataSheet && (
<SheetEditData
visible={visibleEditDataSheet}
onClose={hideEditDataSheet}
showCivilStatusDocumentsInfoDialog={() => {
hideEditDataSheet();
showCivilStatusDocumentsInfoDialog();
}}
selectedPassportOption={selectedPassportOption}
/>
)}
{visibleSearchLocationSheet && (
<SheetSearchLocation
visible={visibleSearchLocationSheet}
onClose={hideSearchLocationSheet}
/>
)}
</>
) : (
<Questionnaire
@ -420,8 +560,14 @@ function RegularPassportScreen() {
visible ||
visibleDontHaveYetDialog ||
visiblePassportInfoDialog ||
visibleLostOrDamagedPassportDialog
visibleLostOrDamagedPassportDialog ||
visibleCivilStatusDocumentsInfoDialog ||
visibleSubmitSuccessDialog ||
visibleFinalizationConfirmationDialog ||
visiblePassportTypeInfoDialog
? '#295E70'
: visibleEditDataSheet || visibleSearchLocationSheet
? '#185769'
: Colors.secondary30.color
}
barStyle="light-content"

View File

@ -16,12 +16,14 @@ interface DocumentUploadSectionProps {
title: string;
isRequired?: boolean;
isIcon?: boolean;
showDialogCivilStatusDocumentsInfo?: () => void;
}
interface Step3PaymentProps {
setStep: (step: number) => void;
setSubStep: (subStep: number) => void;
selectedPassportOption: string;
showCivilStatusDocumentsInfoDialog: () => void;
}
const BackButton = (props: BackButtonProps) => {
@ -43,7 +45,7 @@ const BackButton = (props: BackButtonProps) => {
};
const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
const {title, isRequired, isIcon} = props;
const {title, isRequired, isIcon, showDialogCivilStatusDocumentsInfo} = props;
const [uploadedFileName, setUploadedFileName] = useState<string | null>(null);
const handleUpload = (p0: string) => {
@ -72,7 +74,13 @@ const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
)}
</Text>
{isIcon && (
<Pressable
style={({pressed}) => ({
transform: [{scale: pressed ? 0.925 : 1}],
})}
onPress={showDialogCivilStatusDocumentsInfo}>
<Icon name="information" size={24} color={Colors.primary30.color} />
</Pressable>
)}
</View>
@ -125,8 +133,12 @@ const DocumentUploadSection = (props: DocumentUploadSectionProps) => {
};
const Step3Payment = (props: Step3PaymentProps) => {
const {setStep, setSubStep, selectedPassportOption} = props;
console.log('selectedPassportOption', selectedPassportOption);
const {
setStep,
setSubStep,
selectedPassportOption,
showCivilStatusDocumentsInfoDialog,
} = props;
return (
<ScrollView>
<View style={styles.subStepContainer}>
@ -228,6 +240,9 @@ const Step3Payment = (props: Step3PaymentProps) => {
<DocumentUploadSection
title="Akta kelahiran/ijazah/akta perkawinan/buku nikah/surat baptis"
isIcon
showDialogCivilStatusDocumentsInfo={
showCivilStatusDocumentsInfoDialog
}
/>
{selectedPassportOption !== 'already' && (
<DocumentUploadSection title="Paspor Lama" isRequired />

View File

@ -1,5 +1,5 @@
import React from 'react';
import {View, Text} from 'react-native';
import {View, Text, Pressable} from 'react-native';
import {Button} from 'react-native-paper';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import Colors from '../../../../../assets/styles/Colors';
@ -9,13 +9,12 @@ type Step5VerificationProps = {
setStep: (step: number) => void;
setSubStep: (subStep: number) => void;
passportAppointmentData: any[];
showEditDataSheet: () => void;
};
const Step5Content = ({
setStep,
setSubStep,
passportAppointmentData,
}: Step5VerificationProps) => {
const Step5Content = (props: Step5VerificationProps) => {
const {setStep, setSubStep, passportAppointmentData, showEditDataSheet} =
props;
const lastAppointment =
passportAppointmentData[passportAppointmentData.length - 1];
@ -46,11 +45,19 @@ const Step5Content = ({
size={24}
color={Colors.indicatorRed.color}
/>
<Pressable
onPress={showEditDataSheet}
style={({pressed}) => [
{
transform: [{scale: pressed ? 0.925 : 1}],
},
]}>
<Icon
name="square-edit-outline"
size={24}
color={Colors.primary30.color}
/>
</Pressable>
</View>
</View>
<View style={styles.applicantDetailContentChildContainer}>

View File

@ -5,16 +5,17 @@ import TextInputComponent from '../../../../components/TextInput';
import styles from '../styles';
import Colors from '../../../../../assets/styles/Colors';
import FontFamily from '../../../../../assets/styles/FontFamily';
import arrivalDateGuidelinesData from '../../../../data/Steps/ArrivalDateGuidelinesData';
type Step6ProcessingProps = {
setStep: (step: number) => void;
arrivalDateGuidelines: string[];
showFinalizationConfirmationDialog: () => void;
showPassportTypeInfoDialog: () => void;
showSearchLocationSheet: () => void;
};
const Step6Processing = ({
setStep,
arrivalDateGuidelines,
}: Step6ProcessingProps) => {
const Step6Processing = (props: Step6ProcessingProps) => {
const {showFinalizationConfirmationDialog, showPassportTypeInfoDialog, showSearchLocationSheet} =
props;
return (
<ScrollView>
<View style={styles.subStepContainer}>
@ -29,18 +30,21 @@ const Step6Processing = ({
</View>
<View style={[styles.subStepTextInputContainer, {marginVertical: 16}]}>
{/* Trigger Search Location Bottom Sheet */}
<TextInputComponent
title="Lokasi Kantor Imigrasi"
placeholder="Pilih lokasi kantor imigrasi"
isRequired
isDropdown
isDropdownSearchLocation
handlePressSearchLocation={showSearchLocationSheet}
/>
{/* TODO: Add button information */}
<TextInputComponent
title="Jenis Paspor"
iconButton
placeholder="Pilih satu jenis paspor"
isRequired
isDropdown
onIconButtonPress={showPassportTypeInfoDialog}
/>
</View>
@ -58,7 +62,7 @@ const Step6Processing = ({
</View>
<View>
{arrivalDateGuidelines.map((item, index) => (
{arrivalDateGuidelinesData.map((item, index) => (
<View key={index} style={styles.subStepListTextRowContainer}>
<Text style={[styles.subStepDesc, FontFamily.notoSansBold]}>
@ -88,7 +92,7 @@ const Step6Processing = ({
mode="contained"
style={[styles.subStepButtonContained, {marginTop: 24}]}
textColor={Colors.neutral100.color}
onPress={() => setStep(7)}>
onPress={showFinalizationConfirmationDialog}>
Lanjut
</Button>
</View>

View File

@ -9,10 +9,12 @@ import Accordion from '../../../../components/Accordion';
import termsAndConditionsData from '../../../../data/Steps/TermsAndContionsData';
type Step7CompletionProps = {
setStep: (step: number) => void;
showSubmitSuccessDialog: () => void;
setLastCompletedSteps: () => void;
};
const Step7Completion = ({setStep}: Step7CompletionProps) => {
const Step7Completion = (props: Step7CompletionProps) => {
const {showSubmitSuccessDialog, setLastCompletedSteps} = props;
const lastAppointment =
passportAppointmentData[passportAppointmentData.length - 1];
@ -192,7 +194,10 @@ const Step7Completion = ({setStep}: Step7CompletionProps) => {
mode="contained"
style={styles.subStepButtonContained}
textColor={Colors.neutral100.color}
onPress={() => setStep(6)}>
onPress={() => {
showSubmitSuccessDialog();
setLastCompletedSteps();
}}>
Kembali ke Halaman Utama
</Button>
</View>

View File

@ -71,6 +71,7 @@ const styles = StyleSheet.create({
fontSize: 14,
...FontFamily.notoSansRegular,
includeFontPadding: false,
lineHeight: 22,
color: Colors.primary30.color,
},
buttonAgree: {