From 0287887302bdb1a689e85787045cfeedbc199e3d Mon Sep 17 00:00:00 2001 From: Mochammad Adhi Buchori Date: Thu, 24 Apr 2025 07:42:55 +0700 Subject: [PATCH] Add Dialog Choose Payment Method to implement payment method feature. Then, add Billing Code and Other Method screens, followed by their several UI components --- assets/icons/receipt_text_check_outline.svg | 3 + src/components/Accordion.tsx | 24 +- .../dialog/DialogChoosePaymentMethod.tsx | 91 ++++++ src/navigation/RootStack.tsx | 14 +- src/navigation/type.ts | 2 + src/screens/applicationDetail/index.tsx | 298 ++++++++++-------- src/screens/applicationDetail/styles.tsx | 7 + src/screens/billingCode/index.tsx | 146 +++++++++ src/screens/billingCode/styles.tsx | 97 ++++++ src/screens/closeAccount/index.tsx | 2 +- src/screens/home/index.tsx | 20 +- src/screens/navigationRoute/index.tsx | 4 +- src/screens/otherMethod/index.tsx | 37 +++ src/screens/otherMethod/styles.tsx | 28 ++ 14 files changed, 633 insertions(+), 140 deletions(-) create mode 100644 assets/icons/receipt_text_check_outline.svg create mode 100644 src/components/dialog/DialogChoosePaymentMethod.tsx create mode 100644 src/screens/billingCode/index.tsx create mode 100644 src/screens/billingCode/styles.tsx create mode 100644 src/screens/otherMethod/index.tsx create mode 100644 src/screens/otherMethod/styles.tsx diff --git a/assets/icons/receipt_text_check_outline.svg b/assets/icons/receipt_text_check_outline.svg new file mode 100644 index 0000000..4ad8cca --- /dev/null +++ b/assets/icons/receipt_text_check_outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index f68c9a5..510f8d9 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -6,10 +6,15 @@ import FontFamily from '../../assets/styles/FontFamily'; type AccordionProps = { title: string; + titleRegular?: boolean; children: ReactNode; }; -const Accordion: React.FC = ({title, children}) => { +const Accordion: React.FC = ({ + title, + titleRegular = false, + children, +}) => { const [expanded, setExpanded] = useState(false); return ( @@ -22,7 +27,14 @@ const Accordion: React.FC = ({title, children}) => { justifyContent: 'space-between', })} onPress={() => setExpanded(!expanded)}> - {title} + + {title} + = ({title, children}) => { }; const styles = StyleSheet.create({ - accordionTitle: { + accordionTitleBold: { ...FontFamily.notoSansBold, includeFontPadding: false, fontSize: 14, color: Colors.secondary30.color, }, + accordionTitleRegular: { + ...FontFamily.notoSansRegular, + includeFontPadding: false, + fontSize: 14, + color: Colors.secondary30.color, + }, }); export default Accordion; diff --git a/src/components/dialog/DialogChoosePaymentMethod.tsx b/src/components/dialog/DialogChoosePaymentMethod.tsx new file mode 100644 index 0000000..ed7a0f4 --- /dev/null +++ b/src/components/dialog/DialogChoosePaymentMethod.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import {View, Text, StyleSheet, Pressable} from 'react-native'; +import {Portal, Dialog} 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 ReceiptTextCheckOutlineIcon from '../../../assets/icons/receipt_text_check_outline.svg'; + +type Props = { + visible: boolean; + onBillingCodePress: () => void; + onOtherMethodPress: () => void; +}; + +const DialogChoosePaymentMethod = (props: Props) => { + const {visible, onBillingCodePress, onOtherMethodPress} = props; + return ( + + + + Pilih metode pembayaran + + + [ + styles.dialogButtonContainer, + { + transform: [{scale: pressed ? 0.975 : 1}], + }, + ]}> + + Kode Biling + + [ + styles.dialogButtonContainer, + { + transform: [{scale: pressed ? 0.975 : 1}], + }, + ]}> + + Metode Lain + + + + + ); +}; + +const styles = StyleSheet.create({ + dialogContainer: { + backgroundColor: 'white', + elevation: 0, + shadowColor: 'transparent', + borderRadius: 20, + }, + dialogTitle: { + fontSize: 22, + color: Colors.secondary30.color, + }, + dialogContentContainer: { + marginHorizontal: 24, + marginBottom: 24, + gap: 32, + flexDirection: 'row', + justifyContent: 'center', + }, + dialogButtonContainer: { + backgroundColor: Colors.primary30.color, + padding: 16, + borderRadius: 12, + gap: 10, + alignItems: 'center', + }, + dialogTextButton: { + width: 75, + ...FontFamily.notoSansMedium, + fontSize: 14, + color: Colors.neutral100.color, + includeFontPadding: false, + textAlign: 'center', + }, +}); + +export default DialogChoosePaymentMethod; diff --git a/src/navigation/RootStack.tsx b/src/navigation/RootStack.tsx index 0068ea8..6d9c660 100644 --- a/src/navigation/RootStack.tsx +++ b/src/navigation/RootStack.tsx @@ -20,6 +20,8 @@ import ApplicationDetailScreen from '../screens/applicationDetail'; import PassportRequirementsScreen from '../screens/passportRequirements'; import ApplicationGuideScreen from '../screens/applicationGuide'; import SeeRequirementsScreen from '../screens/seeRequirements'; +import OtherMethodScreen from '../screens/otherMethod'; +import BillingCodeScreen from '../screens/billingCode'; const Stack = createNativeStackNavigator(); @@ -52,7 +54,7 @@ function RootStack() { options={{headerShown: false}} /> - {() => console.log('Show dialog!')} />} + {() => console.log('Show dialog!')} visible />} + + ); } diff --git a/src/navigation/type.ts b/src/navigation/type.ts index 1cee857..4627747 100644 --- a/src/navigation/type.ts +++ b/src/navigation/type.ts @@ -20,4 +20,6 @@ export type RootStackParamList = { ApplicationGuide: undefined; PassportRequirements: undefined; SeeRequirements: undefined; + OtherMethod: undefined; + BillingCode: undefined; }; diff --git a/src/screens/applicationDetail/index.tsx b/src/screens/applicationDetail/index.tsx index 107eca3..2eebe06 100644 --- a/src/screens/applicationDetail/index.tsx +++ b/src/screens/applicationDetail/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState} from 'react'; import {ScrollView, StatusBar, Text, View} from 'react-native'; import styles from './styles'; import {useNavigation, useRoute} from '@react-navigation/native'; @@ -7,18 +7,90 @@ import Colors from '../../../assets/styles/Colors'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import {NativeStackNavigationProp} from '@react-navigation/native-stack'; import {RootStackParamList} from '../../navigation/type'; -import {Button, Divider} from 'react-native-paper'; +import {Button, Divider, PaperProvider} from 'react-native-paper'; +import DialogChoosePaymentMethod from '../../components/dialog/DialogChoosePaymentMethod'; type ApplicationDetailScreenNavigationProp = NativeStackNavigationProp< RootStackParamList, 'ApplicationDetail' >; -const renderApplicantDetailContent = () => { +const RenderStatusSection = (showDialog: () => void) => { const route = useRoute(); const {data} = route.params as {data: PassportAppointmentData}; const navigation = useNavigation(); + if (data.status === 'Sudah Terbayar') { + return ( + <> + + Biaya + + + {data.applicationDetails.fee} + + + + + + + + ); + } + + if (data.status === 'Permohonan Kadaluarsa') { + return ( + + Biaya + + {data.applicationDetails.fee} + + + ); + } + + return ( + <> + + Biaya + + + {data.applicationDetails.fee} + + + + + + + ); +}; + +const RenderApplicantDetailContent = (showDialog: () => void) => { + const route = useRoute(); + const {data} = route.params as {data: PassportAppointmentData}; + return ( @@ -26,14 +98,18 @@ const renderApplicantDetailContent = () => { {data.applicantName} Kode Permohonan - + {data.applicantCode} @@ -78,65 +154,7 @@ const renderApplicantDetailContent = () => { - {data.status === 'Sudah Terbayar' ? ( - <> - - Biaya - - - {data.applicationDetails.fee} - - - - - - - - ) : data.status === 'Permohonan Kadaluarsa' ? ( - - Biaya - - {data.applicationDetails.fee} - - - ) : ( - <> - - Biaya - - - {data.applicationDetails.fee} - - - - - - - )} + {RenderStatusSection(showDialog)} ); }; @@ -199,77 +217,101 @@ function ApplicationDetailScreen() { const navigation = useNavigation(); + const [visible, setVisible] = useState(false); + + const showDialog = () => setVisible(true); + const hideDialog = () => setVisible(false); + return ( - - - navigation.goBack()} + + - Detail Permohonan - - - - - {renderStatusContent(data.status)} + + navigation.goBack()} + /> + Detail Permohonan - - Jadwal Kedatangan - - - - - {data.appointmentDate} - + + + + {renderStatusContent(data.status)} + + + Jadwal Kedatangan + + + + + {data.appointmentDate} + + + + + + {data.appointmentTime} + + + + + + {data.serviceUnit} + + - - - - {data.appointmentTime} - - - - - - {data.serviceUnit} - + + + + + Tanggal Pengajuan + + + {data.submissionDate} + + + + Kode Layanan + + {data.serviceCode} + + - - - - Tanggal Pengajuan - - {data.submissionDate} - - - - Kode Layanan - {data.serviceCode} - - - - {renderApplicantDetailContent()} - + {RenderApplicantDetailContent(showDialog)} + + {visible && ( + { + navigation.navigate('BillingCode'); + hideDialog(); + }} + onOtherMethodPress={() => { + navigation.navigate('OtherMethod'); + hideDialog(); + }} + /> + )} + ); } diff --git a/src/screens/applicationDetail/styles.tsx b/src/screens/applicationDetail/styles.tsx index 1761815..d5c529f 100644 --- a/src/screens/applicationDetail/styles.tsx +++ b/src/screens/applicationDetail/styles.tsx @@ -126,6 +126,13 @@ const styles = StyleSheet.create({ color: Colors.primary30.color, flex: 1.2, }, + applicantDetailTexDescName: { + textTransform: 'uppercase', + textAlign: 'right', + }, + applicantDetailTexDescCode: { + textAlign: 'right', + }, applicantDetailContentChildContainer: { padding: 16, borderWidth: 1, diff --git a/src/screens/billingCode/index.tsx b/src/screens/billingCode/index.tsx new file mode 100644 index 0000000..ae4f760 --- /dev/null +++ b/src/screens/billingCode/index.tsx @@ -0,0 +1,146 @@ +import {ScrollView, StatusBar, View} from 'react-native'; +import {Divider, Text} from 'react-native-paper'; +import styles from './styles'; +import {RootStackParamList} from '../../navigation/type'; +import {NativeStackNavigationProp} from '@react-navigation/native-stack'; +import {useNavigation} from '@react-navigation/native'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import Colors from '../../../assets/styles/Colors'; +import Accordion from '../../components/Accordion'; + +type BillingCodeScreenNavigationProp = NativeStackNavigationProp< + RootStackParamList, + 'BillingCode' +>; + +function BillingCodeScreen() { + const navigation = useNavigation(); + + return ( + + + + navigation.goBack()} + /> + Kode Billing + + + + + Kode Billing + + 12345678901234 + + + + + + + + + Unduh PDF Kode Billing Pembayaran Paspor + + + + + + Cara Pembayaran + + + 1. + + ATM + + + + + + Content + + + + + Content + + + + + Content + + + + + Content + + + + + + + 2. + + Mobile Banking dan Internet Banking + + + + + + Content + + + + + Content + + + + + Content + + + + + Content + + + + + Content + + + + + Content + + + + + + + + ); +} + +export default BillingCodeScreen; diff --git a/src/screens/billingCode/styles.tsx b/src/screens/billingCode/styles.tsx new file mode 100644 index 0000000..6a31241 --- /dev/null +++ b/src/screens/billingCode/styles.tsx @@ -0,0 +1,97 @@ +import {StyleSheet} from 'react-native'; +import Colors from '../../../assets/styles/Colors'; +import FontFamily from '../../../assets/styles/FontFamily'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Colors.neutral100.color, + }, + appBarTitle: { + color: Colors.secondary30.color, + ...FontFamily.notoSansRegular, + fontSize: 20, + marginStart: 16, + includeFontPadding: false, + }, + appBarIcon: { + marginLeft: 16, + }, + appBarContainer: { + height: 64, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: Colors.neutral100.color, + }, + billingCodeCardContainer: { + marginTop: 4, + marginHorizontal: 16, + marginBottom: 16, + borderWidth: 1, + borderColor: Colors.secondary70.color, + borderRadius: 8, + }, + billingCodeIconContentWrapper: { + flexDirection: 'row', + gap: 8, + alignItems: 'center', + }, + divider: { + backgroundColor: Colors.secondary70.color, + height: 1, + }, + billingCodeContentWrapper: { + padding: 16, + gap: 8, + }, + billingCodeTextTitle: { + ...FontFamily.notoSansMedium, + fontSize: 12, + includeFontPadding: false, + color: Colors.primary30.color, + }, + billingCodeTextNumber: { + ...FontFamily.notoSansExtraBold, + fontSize: 16, + includeFontPadding: false, + color: Colors.primary30.color, + }, + billingCodeTextDesc: { + ...FontFamily.notoSansMedium, + fontSize: 12, + includeFontPadding: false, + color: Colors.primary30.color, + }, + billingCodeDataContentContainer: { + marginHorizontal: 16, + gap: 16, + marginBottom: 16, + }, + paymentMethodContainer: { + gap: 12, + }, + paymentMethodTitle: { + includeFontPadding: false, + fontSize: 18, + ...FontFamily.notoSansExtraBold, + color: Colors.primary30.color, + }, + paymentMethodOptionTitleWrapper: { + gap: 6, + flexDirection: 'row', + }, + paymentMethodOptionTitle: { + color: Colors.primary30.color, + includeFontPadding: false, + fontSize: 16, + ...FontFamily.notoSansExtraBold, + }, + paymentMethodOptionTitleFlex: { + flex: 1, + }, + paymentMethodOptionAccordionContainer: { + marginStart: 24, + }, +}); + +export default styles; diff --git a/src/screens/closeAccount/index.tsx b/src/screens/closeAccount/index.tsx index e210cf7..aa01fa6 100644 --- a/src/screens/closeAccount/index.tsx +++ b/src/screens/closeAccount/index.tsx @@ -37,7 +37,7 @@ function CloseAccountScreen() { diff --git a/src/screens/home/index.tsx b/src/screens/home/index.tsx index e2d6d92..e1e9ecf 100644 --- a/src/screens/home/index.tsx +++ b/src/screens/home/index.tsx @@ -14,14 +14,14 @@ import Colors from '../../../assets/styles/Colors'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import {NativeStackNavigationProp} from '@react-navigation/native-stack'; import {RootStackParamList} from '../../navigation/type'; -import {useNavigation} from '@react-navigation/native'; +import {useFocusEffect, useNavigation} from '@react-navigation/native'; import RegularPassportIcon from '../../../assets/icons/regular_passport.svg'; import ExpressPassportIcon from '../../../assets/icons/express_passport.svg'; import GuidebookIcon from '../../../assets/icons/guidebook.svg'; import EazyPassportIcon from '../../../assets/icons/eazy_passport.svg'; import passportAppointmentData from '../../data/History/PassportAppointmentData'; import PassportAppointmentCard from '../../components/PassportAppointmentCard'; -import {useRef} from 'react'; +import {useCallback, useRef} from 'react'; type HomeScreenNavigationProp = NativeStackNavigationProp< RootStackParamList, @@ -59,7 +59,7 @@ const RenderBanner = () => { <> item.toString()} + keyExtractor={item => item.id.toString()} horizontal showsHorizontalScrollIndicator={false} snapToInterval={ITEM_WIDTH} @@ -253,15 +253,25 @@ const RenderContent = ({showDialog}: RenderContentProps) => { type HomeScreenProps = { readonly showDialog: () => void; + readonly visible: boolean; }; -function HomeScreen({showDialog}: HomeScreenProps) { +function HomeScreen({showDialog, visible}: HomeScreenProps) { const navigation = useNavigation(); + useFocusEffect( + useCallback(() => { + StatusBar.setBackgroundColor( + visible ? '#295E70' : Colors.secondary30.color, + ); + StatusBar.setBarStyle('light-content'); + }, [visible]), + ); + return ( diff --git a/src/screens/navigationRoute/index.tsx b/src/screens/navigationRoute/index.tsx index 09b6211..302eeef 100644 --- a/src/screens/navigationRoute/index.tsx +++ b/src/screens/navigationRoute/index.tsx @@ -12,7 +12,7 @@ import ProfileScreen from '../profile'; import styles from './styles'; import HomeScreen from '../home'; import HistoryScreen from '../history'; -import {StatusBar, View} from 'react-native'; +import {View} from 'react-native'; import {useState} from 'react'; import {NativeStackNavigationProp} from '@react-navigation/native-stack'; import {RootStackParamList} from '../../navigation/type'; @@ -54,7 +54,7 @@ function NavigationRouteScreen() { const renderScene = ({route}: {route: {key: string}}) => { switch (route.key) { case 'home': - return ; + return ; case 'history': return ; case 'profile': diff --git a/src/screens/otherMethod/index.tsx b/src/screens/otherMethod/index.tsx new file mode 100644 index 0000000..04ec7bd --- /dev/null +++ b/src/screens/otherMethod/index.tsx @@ -0,0 +1,37 @@ +import {StatusBar, View} from 'react-native'; +import {Text} from 'react-native-paper'; +import styles from './styles'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import Colors from '../../../assets/styles/Colors'; +import {useNavigation} from '@react-navigation/native'; +import {NativeStackNavigationProp} from '@react-navigation/native-stack'; +import {RootStackParamList} from '../../navigation/type'; + +type OtherMethodScreenNavigationProp = NativeStackNavigationProp< + RootStackParamList, + 'OtherMethod' +>; + +function OtherMethodScreen() { + const navigation = useNavigation(); + return ( + + + + navigation.goBack()} + /> + Metode Lain + + + ); +} + +export default OtherMethodScreen; diff --git a/src/screens/otherMethod/styles.tsx b/src/screens/otherMethod/styles.tsx new file mode 100644 index 0000000..60ad96c --- /dev/null +++ b/src/screens/otherMethod/styles.tsx @@ -0,0 +1,28 @@ +import {StyleSheet} from 'react-native'; +import Colors from '../../../assets/styles/Colors'; +import FontFamily from '../../../assets/styles/FontFamily'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: Colors.neutral100.color, + }, + appBarTitle: { + color: Colors.secondary30.color, + ...FontFamily.notoSansRegular, + fontSize: 20, + marginStart: 16, + includeFontPadding: false, + }, + appBarIcon: { + marginLeft: 16, + }, + appBarContainer: { + height: 64, + flexDirection: 'row', + alignItems: 'center', + backgroundColor: Colors.neutral100.color, + }, +}); + +export default styles;