diff --git a/src/components/DecisionModal.tsx b/src/components/DecisionModal.tsx index 8cf23cb6d2b29..587eab6ccf158 100644 --- a/src/components/DecisionModal.tsx +++ b/src/components/DecisionModal.tsx @@ -50,6 +50,9 @@ type DecisionModalProps = { /** Whether modal is visible */ isVisible: boolean; + + /** Whether to handle browser navigation back to close the modal */ + shouldHandleNavigationBack?: boolean; }; function DecisionModal({ @@ -67,6 +70,7 @@ function DecisionModal({ isFirstOptionSuccess = true, isSecondOptionSuccess = false, isSecondOptionDanger = false, + shouldHandleNavigationBack, }: DecisionModalProps) { const styles = useThemeStyles(); @@ -77,6 +81,7 @@ function DecisionModal({ type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM} innerContainerStyle={styles.pv0} onModalHide={onModalHide} + shouldHandleNavigationBack={shouldHandleNavigationBack} > @@ -112,4 +117,5 @@ function DecisionModal({ ); } +export type {DecisionModalProps}; export default DecisionModal; diff --git a/src/components/Modal/Global/DecisionModalWrapper.tsx b/src/components/Modal/Global/DecisionModalWrapper.tsx new file mode 100644 index 0000000000000..c650379b79654 --- /dev/null +++ b/src/components/Modal/Global/DecisionModalWrapper.tsx @@ -0,0 +1,50 @@ +import React, {useState} from 'react'; +import type {DecisionModalProps} from '@components/DecisionModal'; +import DecisionModal from '@components/DecisionModal'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import {ModalActions} from './ModalContext'; +import type {ModalProps} from './ModalContext'; + +type DecisionModalWrapperProps = ModalProps & Omit; + +function DecisionModalWrapper({closeModal, onModalHide, ...props}: DecisionModalWrapperProps) { + const [isVisible, setIsVisible] = useState(true); + const [closeAction, setCloseAction] = useState(ModalActions.CLOSE); + // We need to use isSmallScreenWidth here because the DecisionModal breaks in RHP with shouldUseNarrowLayout. + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + + const handleFirstOption = () => { + setCloseAction(ModalActions.CONFIRM); + setIsVisible(false); + }; + + const handleSecondOption = () => { + setCloseAction(ModalActions.CLOSE); + setIsVisible(false); + }; + + const handleModalHide = () => { + if (isVisible) { + return; + } + closeModal({action: closeAction}); + onModalHide?.(); + }; + + return ( + + ); +} + +export default DecisionModalWrapper; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index bd69324e2edcd..7f000776a9a17 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -14,6 +14,7 @@ import useConfirmModal from '@hooks/useConfirmModal'; import useConfirmPendingRTERAndProceed from '@hooks/useConfirmPendingRTERAndProceed'; import {useCurrencyListActions} from '@hooks/useCurrencyList'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useDecisionModal from '@hooks/useDecisionModal'; import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useDeleteTransactions from '@hooks/useDeleteTransactions'; import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations'; @@ -158,7 +159,6 @@ import ActivityIndicator from './ActivityIndicator'; import Button from './Button'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {ButtonWithDropdownMenuRef, DropdownOption} from './ButtonWithDropdownMenu/types'; -import DecisionModal from './DecisionModal'; import {useDelegateNoAccessActions, useDelegateNoAccessState} from './DelegateNoAccessModalProvider'; import Header from './Header'; import HeaderLoadingBar from './HeaderLoadingBar'; @@ -363,7 +363,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa }, [isExported, reportActions]); const transactionViolations = useTransactionViolations(transaction?.transactionID); - const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); const [isPDFModalVisible, setIsPDFModalVisible] = useState(false); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: isTrackIntentUserSelector}); @@ -384,6 +383,24 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa const [exportModalStatus, setExportModalStatus] = useState(null); const {showConfirmModal} = useConfirmModal(); + const {showDecisionModal} = useDecisionModal(); + + const showOfflineModal = () => { + showDecisionModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.offlinePrompt'), + secondOptionText: translate('common.buttonConfirm'), + }); + }; + + const showDownloadErrorModal = () => { + showDecisionModal({ + title: translate('common.downloadFailedTitle'), + prompt: translate('common.downloadFailedDescription'), + secondOptionText: translate('common.buttonConfirm'), + }); + }; + const {isPaidAnimationRunning, isApprovedAnimationRunning, isSubmittingAnimationRunning, startAnimation, stopAnimation, startApprovedAnimation, startSubmittingAnimation} = usePaymentAnimations(); const styles = useThemeStyles(); @@ -516,7 +533,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa const [isDuplicateActive, temporarilyDisableDuplicateAction] = useThrottledButtonState(handleDuplicateReset); - const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); const [isHoldEducationalModalVisible, setIsHoldEducationalModalVisible] = useState(false); const [rejectModalAction, setRejectModalAction] = useState { @@ -547,7 +562,11 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa const beginExportWithTemplate = useCallback( (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => { if (isOffline) { - setOfflineModalVisible(true); + showDecisionModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.offlinePrompt'), + secondOptionText: translate('common.buttonConfirm'), + }); return; } @@ -570,7 +589,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa policyID, }); }, - [isOffline, moneyRequestReport, showExportProgressModal, clearSelectedTransactions], + [isOffline, moneyRequestReport, showExportProgressModal, clearSelectedTransactions, showDecisionModal, translate], ); const isOnSearch = route.name.toLowerCase().startsWith('search'); @@ -583,8 +602,8 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa reportActions, allTransactionsLength: transactions.length, session, - onExportFailed: () => setIsDownloadErrorModalVisible(true), - onExportOffline: () => setOfflineModalVisible(true), + onExportFailed: showDownloadErrorModal, + onExportOffline: showOfflineModal, policy, beginExportWithTemplate: (templateName, templateType, transactionIDList, policyID) => beginExportWithTemplate(templateName, templateType, transactionIDList, policyID), isOnSearch, @@ -1152,7 +1171,11 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa return; } if (isOffline) { - setOfflineModalVisible(true); + showDecisionModal({ + title: translate('common.youAppearToBeOffline'), + prompt: translate('common.offlinePrompt'), + secondOptionText: translate('common.buttonConfirm'), + }); return; } exportReportToCSV( @@ -1161,7 +1184,11 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa transactionIDList: transactionIDs, }, () => { - setDownloadErrorModalVisible(true); + showDecisionModal({ + title: translate('common.downloadFailedTitle'), + prompt: translate('common.downloadFailedDescription'), + secondOptionText: translate('common.buttonConfirm'), + }); }, translate, ); @@ -1236,6 +1263,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa isExported, exportTemplates, beginExportWithTemplate, + showDecisionModal, ]); const primaryActionComponent = ( @@ -2334,24 +2362,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa onNonReimbursablePaymentError={showNonReimbursablePaymentErrorModal} /> )} - setDownloadErrorModalVisible(false)} - secondOptionText={translate('common.buttonConfirm')} - isVisible={downloadErrorModalVisible} - onClose={() => setDownloadErrorModalVisible(false)} - /> - setIsDownloadErrorModalVisible(false)} - secondOptionText={translate('common.buttonConfirm')} - isVisible={isDownloadErrorModalVisible} - onClose={() => setIsDownloadErrorModalVisible(false)} - /> {!!rejectModalAction && ( )} - setOfflineModalVisible(false)} - secondOptionText={translate('common.buttonConfirm')} - isVisible={offlineModalVisible} - onClose={() => setOfflineModalVisible(false)} - /> {nonReimbursablePaymentErrorDecisionModal} { diff --git a/src/hooks/useDecisionModal.ts b/src/hooks/useDecisionModal.ts new file mode 100644 index 0000000000000..85db54244520f --- /dev/null +++ b/src/hooks/useDecisionModal.ts @@ -0,0 +1,27 @@ +import DecisionModalWrapper from '@components/Modal/Global/DecisionModalWrapper'; +import type {ModalProps} from '@components/Modal/Global/ModalContext'; +import {useModal} from '@components/Modal/Global/ModalContext'; + +type DecisionModalOptions = Omit, keyof ModalProps>; + +const useDecisionModal = () => { + const context = useModal(); + + const showDecisionModal = (options: DecisionModalOptions) => { + return context.showModal({ + component: DecisionModalWrapper, + props: { + shouldHandleNavigationBack: true, + ...options, + }, + }); + }; + + return { + ...context, + closeModal: () => context.closeModal(), + showDecisionModal, + }; +}; + +export default useDecisionModal;