Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/components/DecisionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -67,6 +70,7 @@ function DecisionModal({
isFirstOptionSuccess = true,
isSecondOptionSuccess = false,
isSecondOptionDanger = false,
shouldHandleNavigationBack,
}: DecisionModalProps) {
const styles = useThemeStyles();

Expand All @@ -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}
>
<ScrollView contentContainerStyle={styles.m5}>
<View>
Expand Down Expand Up @@ -112,4 +117,5 @@ function DecisionModal({
);
}

export type {DecisionModalProps};
export default DecisionModal;
50 changes: 50 additions & 0 deletions src/components/Modal/Global/DecisionModalWrapper.tsx
Original file line number Diff line number Diff line change
@@ -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<DecisionModalProps, 'onClose' | 'onSecondOptionSubmit' | 'onFirstOptionSubmit' | 'isVisible' | 'isSmallScreenWidth'>;

function DecisionModalWrapper({closeModal, onModalHide, ...props}: DecisionModalWrapperProps) {
const [isVisible, setIsVisible] = useState(true);
const [closeAction, setCloseAction] = useState<typeof ModalActions.CONFIRM | typeof ModalActions.CLOSE>(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 (
<DecisionModal
// Spreading is needed to forward all modal configuration props from the wrapper to the underlying DecisionModal.
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
isVisible={isVisible}
isSmallScreenWidth={isSmallScreenWidth}
onFirstOptionSubmit={handleFirstOption}
onSecondOptionSubmit={handleSecondOption}
onClose={handleSecondOption}
onModalHide={handleModalHide}
/>
);
}

export default DecisionModalWrapper;
75 changes: 38 additions & 37 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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});
Expand All @@ -384,6 +383,24 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa

const [exportModalStatus, setExportModalStatus] = useState<ExportType | null>(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();
Expand Down Expand Up @@ -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<ValueOf<
typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD | typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT | typeof CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT_BULK
Expand All @@ -532,7 +548,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa

const shouldDisplayNarrowMoreButton = !shouldDisplayNarrowVersion || isWideRHPDisplayedOnWideLayout || isSuperWideRHPDisplayedOnWideLayout;

const [offlineModalVisible, setOfflineModalVisible] = useState(false);
const {showNonReimbursablePaymentErrorModal, shouldBlockDirectPayment, nonReimbursablePaymentErrorDecisionModal} = useNonReimbursablePaymentModal(moneyRequestReport, transactions);

const showExportProgressModal = useCallback(() => {
Expand All @@ -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;
}

Expand All @@ -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');
Expand All @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -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,
);
Expand Down Expand Up @@ -1236,6 +1263,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
isExported,
exportTemplates,
beginExportWithTemplate,
showDecisionModal,
]);

const primaryActionComponent = (
Expand Down Expand Up @@ -2334,24 +2362,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
onNonReimbursablePaymentError={showNonReimbursablePaymentErrorModal}
/>
)}
<DecisionModal
title={translate('common.downloadFailedTitle')}
prompt={translate('common.downloadFailedDescription')}
isSmallScreenWidth={isSmallScreenWidth}
onSecondOptionSubmit={() => setDownloadErrorModalVisible(false)}
secondOptionText={translate('common.buttonConfirm')}
isVisible={downloadErrorModalVisible}
onClose={() => setDownloadErrorModalVisible(false)}
/>
<DecisionModal
title={translate('common.downloadFailedTitle')}
prompt={translate('common.downloadFailedDescription')}
isSmallScreenWidth={isSmallScreenWidth}
onSecondOptionSubmit={() => setIsDownloadErrorModalVisible(false)}
secondOptionText={translate('common.buttonConfirm')}
isVisible={isDownloadErrorModalVisible}
onClose={() => setIsDownloadErrorModalVisible(false)}
/>
{!!rejectModalAction && (
<HoldOrRejectEducationalModal
onClose={dismissRejectModalBasedOnAction}
Expand All @@ -2364,15 +2374,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
onConfirm={dismissModalAndUpdateUseHold}
/>
)}
<DecisionModal
title={translate('common.youAppearToBeOffline')}
prompt={translate('common.offlinePrompt')}
isSmallScreenWidth={isSmallScreenWidth}
onSecondOptionSubmit={() => setOfflineModalVisible(false)}
secondOptionText={translate('common.buttonConfirm')}
isVisible={offlineModalVisible}
onClose={() => setOfflineModalVisible(false)}
/>
{nonReimbursablePaymentErrorDecisionModal}
<Modal
onClose={() => {
Expand Down
27 changes: 27 additions & 0 deletions src/hooks/useDecisionModal.ts
Original file line number Diff line number Diff line change
@@ -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<React.ComponentProps<typeof DecisionModalWrapper>, 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;
Loading