diff --git a/src/components/RadioButtonOption.tsx b/src/components/RadioButtonOption.tsx new file mode 100644 index 0000000..38dbc3b --- /dev/null +++ b/src/components/RadioButtonOption.tsx @@ -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 ( + onSelect(value)} + style={[ + styles.container, + isSelected && {backgroundColor: Colors.secondary30.color}, + ]}> + onSelect(value)} + color={Colors.neutral100.color} + uncheckedColor={Colors.secondary30.color} + /> + + {description === 'null' ? ( + + {label} + + ) : ( + + {label} + + )} + {description && description !== 'null' && ( + + {description} + + )} + + + ); +}; + +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; diff --git a/src/components/StepIndicator.tsx b/src/components/StepIndicator.tsx new file mode 100644 index 0000000..b566469 --- /dev/null +++ b/src/components/StepIndicator.tsx @@ -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 ( + + {[...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 ( + + + + + {stepNumber} + + + + {index < totalSteps - 1 && ( + + )} + + ); + })} + + ); +}; + +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; diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx index 4dcbd76..bd8e359 100644 --- a/src/components/TextInput.tsx +++ b/src/components/TextInput.tsx @@ -123,8 +123,9 @@ const TextInputComponent: React.FC = ({ placeholderTextColor={Colors.primary60.color} editable={false} value={formattedDate} - right={} + right={} multiline={false} + textColor='#48454E' /> {showPicker && ( @@ -166,6 +167,7 @@ const TextInputComponent: React.FC = ({ ) : null } multiline={isMultiline} + textColor='#48454E' /> {supportText && {supportText}} @@ -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, diff --git a/src/data/DropdownData/CivilStatus.tsx b/src/data/DropdownData/CivilStatus.tsx new file mode 100644 index 0000000..1c6ff46 --- /dev/null +++ b/src/data/DropdownData/CivilStatus.tsx @@ -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; + \ No newline at end of file diff --git a/src/navigation/RootStack.tsx b/src/navigation/RootStack.tsx index c17c557..79acdca 100644 --- a/src/navigation/RootStack.tsx +++ b/src/navigation/RootStack.tsx @@ -49,11 +49,9 @@ function RootStack() { component={NavigationRouteScreen} options={{headerShown: false}} /> - + + {() => console.log('Show dialog!')} />} + - void, + setSubStep: (step: number) => void, +) => { + if (step === 1) { + switch (subStep) { + case 1: + return ( + + Step 1.1 + + + + + ); } diff --git a/src/screens/navigationRoute/styles.tsx b/src/screens/navigationRoute/styles.tsx index 96c6ca3..7f297df 100644 --- a/src/screens/navigationRoute/styles.tsx +++ b/src/screens/navigationRoute/styles.tsx @@ -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; diff --git a/src/screens/profile/index.tsx b/src/screens/profile/index.tsx index e617dd7..14437dc 100644 --- a/src/screens/profile/index.tsx +++ b/src/screens/profile/index.tsx @@ -55,6 +55,7 @@ function ProfileScreen() { icon="pencil-outline" mode="contained" style={styles.sectionButtonStyle} + textColor={Colors.neutral100.color} onPress={() => navigation.navigate('EditProfile')}> Edit Profil @@ -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 diff --git a/src/screens/regularPassport/index.tsx b/src/screens/regularPassport/index.tsx index f66639b..23fee0d 100644 --- a/src/screens/regularPassport/index.tsx +++ b/src/screens/regularPassport/index.tsx @@ -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 ( + + + + Ambil/Upload Foto KTP Anda + + + Pastikan pencahayaan cukup, tulisan pada identitas terlihat + jelas, dan jangan gunakan foto dari Live Mode sebelum + melanjutkan. + + + + + + + + + ); + case 2: + return ( + + + + + + + + + + + + + + + + + + + + + + + + + ); + default: + return null; + } + } + + if (step === 2) { + switch (subStep) { + case 1: + return ( + + { + setStep(1); + setSubStep(2); + }} + style={({pressed}) => [ + styles.subStepButtonBackWrapper, + { + transform: [{scale: pressed ? 0.99 : 1}], + }, + ]}> + + Kembali + + + + Apakah Anda sudah pernah memiliki paspor? + + {hasHadPassportBeforeOptions.map(options => ( + { + setSelectedOption(value); + value === 'already' && setSubStep(2); + }} + /> + ))} + + + ); + case 2: + return ( + + + { + setSubStep(1); + }} + style={({pressed}) => [ + styles.subStepButtonBackWrapper, + { + transform: [{scale: pressed ? 0.99 : 1}], + }, + ]}> + + Kembali + + + + Apakah Anda sudah pernah memiliki paspor? + + {previousPassportConditionOptions.map(options => ( + { + setSelectedOption(value); + value === 'already' && setSubStep(2); + }} + /> + ))} + + + + + ); + case 3: + return ( + + Step 2.3 + + + + ); + default: + return null; + } + } + + switch (step) { + case 3: + return ( + + + + + ); + case 4: + return ( + + + + + ); + case 5: + return ( + + + + + ); + case 6: + return ( + + + + + ); + case 7: + return ( + + + + ); + default: + return null; + } +}; function RegularPassportScreen() { + const navigation = useNavigation(); + 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 ( - Regular Passport Screen + + + + navigation.goBack()} + /> + Pengajuan Paspor Regular + + {showApplicationStepsContent ? ( + + {stepTitles[step]} + + {renderApplicationStepsContent( + step, + subStep, + setStep, + setSubStep, + selectedOption, + setSelectedOption, + )} + + ) : ( + <> + + Kuesioner Layanan Permohonan + + + + Untuk siapa paspor ini? + + {options.map(option => ( + { + setSelectedOption(value); + value === 'child' + ? showDialog() + : setShowApplicationStepsContent(true); + }} + /> + ))} + + + + + Pemberitahuan + + + + 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. + + + + + + + + + + )} + ); } diff --git a/src/screens/regularPassport/styles.tsx b/src/screens/regularPassport/styles.tsx index 1f6a290..fd4a2b1 100644 --- a/src/screens/regularPassport/styles.tsx +++ b/src/screens/regularPassport/styles.tsx @@ -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, }, });