diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 46d45102885bd..9d2d1fd4dc9ef 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -5,6 +5,7 @@ import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useDeleteTransactions from '@hooks/useDeleteTransactions'; @@ -70,7 +71,6 @@ import BrokenConnectionDescription from './BrokenConnectionDescription'; import Button from './Button'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {DropdownOption} from './ButtonWithDropdownMenu/types'; -import ConfirmModal from './ConfirmModal'; import DecisionModal from './DecisionModal'; import {useDelegateNoAccessActions, useDelegateNoAccessState} from './DelegateNoAccessModalProvider'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -80,6 +80,7 @@ import Icon from './Icon'; // eslint-disable-next-line no-restricted-imports import * as Expensicons from './Icon/Expensicons'; import LoadingBar from './LoadingBar'; +import {ModalActions} from './Modal/Global/ModalContext'; import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import MoneyRequestReportTransactionsNavigation from './MoneyRequestReportView/MoneyRequestReportTransactionsNavigation'; @@ -126,14 +127,12 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const transactionViolations = useTransactionViolations(transaction?.transactionID); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transaction?.transactionID ? [transaction.transactionID] : []); - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const {showConfirmModal} = useConfirmModal(); const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); const [isHoldEducationalModalVisible, setIsHoldEducationalModalVisible] = useState(false); const [rejectModalAction, setRejectModalAction] = useState | null>(null); - const [rateErrorModalVisible, setRateErrorModalVisible] = useState(false); - const [duplicatePerDiemErrorModalVisible, setDuplicatePerDiemErrorModalVisible] = useState(false); const [isDuplicateActive, temporarilyDisableDuplicateAction] = useThrottledButtonState(); const [dismissedRejectUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_REJECT_USE_EXPLANATION, {canBeMissing: true}); const [dismissedHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {canBeMissing: true}); @@ -459,12 +458,22 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre value: CONST.REPORT.SECONDARY_ACTIONS.DUPLICATE, onSelected: () => { if (hasCustomUnitOutOfPolicyViolation) { - setRateErrorModalVisible(true); + showConfirmModal({ + title: translate('common.duplicateExpense'), + prompt: translate('iou.correctRateError'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } if (isPerDiemRequestOnNonDefaultWorkspace) { - setDuplicatePerDiemErrorModalVisible(true); + showConfirmModal({ + title: translate('common.duplicateExpense'), + prompt: translate('iou.duplicateNonDefaultWorkspacePerDiemError'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }); return; } @@ -491,7 +500,47 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre icon: Expensicons.Trashcan, value: CONST.REPORT.SECONDARY_ACTIONS.DELETE, onSelected: () => { - setIsDeleteModalVisible(true); + showConfirmModal({ + title: translate('iou.deleteExpense', {count: 1}), + prompt: translate('iou.deleteConfirmation', {count: 1}), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + shouldEnableNewFocusManagement: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + if (!parentReportAction || !transaction) { + throw new Error('Data missing'); + } + if (isTrackExpenseAction(parentReportAction)) { + deleteTrackExpense({ + chatReportID: report?.parentReportID, + chatReport: parentReport, + transactionID: transaction.transactionID, + reportAction: parentReportAction, + iouReport, + chatIOUReport, + transactions: duplicateTransactions, + violations: duplicateTransactionViolations, + isSingleTransactionView: true, + isChatReportArchived: isParentReportArchived, + isChatIOUReportArchived, + allTransactionViolationsParam: allTransactionViolations, + currentUserAccountID: accountID, + }); + } else { + deleteTransactions([transaction.transactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, true); + removeTransaction(transaction.transactionID); + } + if (isInNarrowPaneModal) { + Navigation.navigateBackToLastSuperWideRHPScreen(); + return; + } + + onBackButtonPress(); + }); }, }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT]: { @@ -596,66 +645,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isVisible={downloadErrorModalVisible} onClose={() => setDownloadErrorModalVisible(false)} /> - { - setIsDeleteModalVisible(false); - if (!parentReportAction || !transaction) { - throw new Error('Data missing'); - } - if (isTrackExpenseAction(parentReportAction)) { - deleteTrackExpense({ - chatReportID: report?.parentReportID, - chatReport: parentReport, - transactionID: transaction.transactionID, - reportAction: parentReportAction, - iouReport, - chatIOUReport, - transactions: duplicateTransactions, - violations: duplicateTransactionViolations, - isSingleTransactionView: true, - isChatReportArchived: isParentReportArchived, - isChatIOUReportArchived, - allTransactionViolationsParam: allTransactionViolations, - currentUserAccountID: accountID, - }); - } else { - deleteTransactions([transaction.transactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, true); - removeTransaction(transaction.transactionID); - } - if (isInNarrowPaneModal) { - Navigation.navigateBackToLastSuperWideRHPScreen(); - return; - } - - onBackButtonPress(); - }} - onCancel={() => setIsDeleteModalVisible(false)} - prompt={translate('iou.deleteConfirmation', {count: 1})} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - shouldEnableNewFocusManagement - /> - setRateErrorModalVisible(false)} - onCancel={() => setRateErrorModalVisible(false)} - confirmText={translate('common.buttonConfirm')} - prompt={translate('iou.correctRateError')} - shouldShowCancelButton={false} - /> - setDuplicatePerDiemErrorModalVisible(false)} - onCancel={() => setDuplicatePerDiemErrorModalVisible(false)} - confirmText={translate('common.buttonConfirm')} - prompt={translate('iou.duplicateNonDefaultWorkspacePerDiemError')} - shouldShowCancelButton={false} - /> {!!rejectModalAction && ( { if (isOffline) { @@ -210,7 +212,6 @@ function MoneyRequestReportActionsList({ return; } - setIsExportWithTemplateModalVisible(true); queueExportSearchWithTemplate({ templateName, templateType, @@ -219,17 +220,47 @@ function MoneyRequestReportActionsList({ transactionIDList, policyID: policy?.id, }); + + showConfirmModal({ + title: translate('export.exportInProgress'), + prompt: translate('export.conciergeWillSend'), + confirmText: translate('common.buttonConfirm'), + shouldShowCancelButton: false, + }).then((result) => { + if (result.action === ModalActions.CONFIRM) { + clearSelectedTransactions(undefined, true); + } + }); + }, + [isOffline, report, policy?.id, showConfirmModal, translate, clearSelectedTransactions], + ); + + const onDeleteSelected = useCallback( + (handleDeleteTransactions: () => void, handleDeleteTransactionsWithNavigation: (backToRoute?: Route) => void) => { + showConfirmModal({ + title: translate('iou.deleteExpense', {count: selectedTransactionIDs.length}), + prompt: translate('iou.deleteConfirmation', {count: selectedTransactionIDs.length}), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + shouldEnableNewFocusManagement: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + const shouldNavigateBack = transactions.filter((trans) => trans.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length === selectedTransactionIDs.length; + if (shouldNavigateBack) { + const backToRoute = route.params?.backTo ?? (chatReport?.reportID ? ROUTES.REPORT_WITH_ID.getRoute(chatReport.reportID) : undefined); + handleDeleteTransactionsWithNavigation(backToRoute); + return; + } + handleDeleteTransactions(); + }); }, - [isOffline, report, policy?.id], + [showConfirmModal, translate, selectedTransactionIDs.length, transactions, route.params?.backTo, chatReport?.reportID], ); - const { - options: selectedTransactionsOptions, - handleDeleteTransactions, - handleDeleteTransactionsWithNavigation, - isDeleteModalVisible, - hideDeleteModal, - } = useSelectedTransactionsActions({ + const {options: selectedTransactionsOptions} = useSelectedTransactionsActions({ report, reportActions, allTransactionsLength: transactions.length, @@ -238,6 +269,7 @@ function MoneyRequestReportActionsList({ onExportOffline: () => setOfflineModalVisible(true), policy, beginExportWithTemplate: (templateName, templateType, transactionIDList) => beginExportWithTemplate(templateName, templateType, transactionIDList), + onDeleteSelected, }); // We are reversing actions because in this View we are starting at the top and don't use Inverted list @@ -692,69 +724,47 @@ function MoneyRequestReportActionsList({ ref={wrapperViewRef} > {shouldUseNarrowLayout && isMobileSelectionModeEnabled && ( - <> - - null} - options={selectedTransactionsOptions} - customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} - isSplitButton={false} - shouldAlwaysShowDropdownMenu - wrapperStyle={[styles.w100, styles.ph5]} - /> - - 0 && selectedTransactionIDs.length !== transactionsWithoutPendingDelete.length} - onPress={() => { - if (selectedTransactionIDs.length !== 0) { - clearSelectedTransactions(true); - } else { - setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID)); - } - }} - /> - { - if (isSelectAllChecked) { - clearSelectedTransactions(true); - } else { - setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID)); - } - }} - accessibilityLabel={translate('workspace.people.selectAll')} - role="button" - accessibilityState={{checked: isSelectAllChecked}} - dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - sentryLabel={CONST.SENTRY_LABEL.REPORT.MONEY_REQUEST_REPORT_ACTIONS_LIST_SELECT_ALL} - > - {translate('workspace.people.selectAll')} - - - - { - const shouldNavigateBack = - transactions.filter((trans) => trans.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE).length === selectedTransactionIDs.length; - if (shouldNavigateBack) { - const backToRoute = route.params?.backTo ?? (chatReport?.reportID ? ROUTES.REPORT_WITH_ID.getRoute(chatReport.reportID) : undefined); - handleDeleteTransactionsWithNavigation(backToRoute); - } else { - handleDeleteTransactions(); - } - }} - onCancel={hideDeleteModal} - prompt={translate('iou.deleteConfirmation', {count: selectedTransactionIDs.length})} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - shouldEnableNewFocusManagement + + null} + options={selectedTransactionsOptions} + customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} + isSplitButton={false} + shouldAlwaysShowDropdownMenu + wrapperStyle={[styles.w100, styles.ph5]} /> - + + 0 && selectedTransactionIDs.length !== transactionsWithoutPendingDelete.length} + onPress={() => { + if (selectedTransactionIDs.length !== 0) { + clearSelectedTransactions(true); + } else { + setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID)); + } + }} + /> + { + if (isSelectAllChecked) { + clearSelectedTransactions(true); + } else { + setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID)); + } + }} + accessibilityLabel={translate('workspace.people.selectAll')} + role="button" + accessibilityState={{checked: isSelectAllChecked}} + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} + sentryLabel={CONST.SENTRY_LABEL.REPORT.MONEY_REQUEST_REPORT_ACTIONS_LIST_SELECT_ALL} + > + {translate('workspace.people.selectAll')} + + + )} setOfflineModalVisible(false)} /> - { - setIsExportWithTemplateModalVisible(false); - clearSelectedTransactions(undefined, true); - }} - onCancel={() => setIsExportWithTemplateModalVisible(false)} - isVisible={isExportWithTemplateModalVisible} - title={translate('export.exportInProgress')} - prompt={translate('export.conciergeWillSend')} - confirmText={translate('common.buttonConfirm')} - shouldShowCancelButton={false} - /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index a16d384ec4595..7210c76e0d38a 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -3,12 +3,13 @@ import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import type {ValueOf} from 'type-fest'; -import ConfirmModal from '@components/ConfirmModal'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ReceiptAudit, {ReceiptAuditMessages} from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import useActiveRoute from '@hooks/useActiveRoute'; +import useConfirmModal from '@hooks/useConfirmModal'; import useEnvironment from '@hooks/useEnvironment'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; import useLocalize from '@hooks/useLocalize'; @@ -211,7 +212,7 @@ function MoneyRequestReceiptView({ const errors = useMemo(() => ({...errorsWithoutReportCreation, ...reportCreationError}), [errorsWithoutReportCreation, reportCreationError]); const showReceiptErrorWithEmptyState = shouldShowReceiptEmptyState && !hasReceipt && !isEmptyObject(errors); - const [showConfirmDismissReceiptError, setShowConfirmDismissReceiptError] = useState(false); + const {showConfirmModal} = useConfirmModal(); const transactionAndReportActionErrors = useMemo( () => ({ @@ -362,11 +363,24 @@ function MoneyRequestReceiptView({ const errorMessages = mapValues(Object.fromEntries(errorEntries), (error) => error); const hasReceiptError = Object.values(errorMessages).some((error) => isReceiptError(error)); - if (hasReceiptError) { - setShowConfirmDismissReceiptError(true); - } else { + if (!hasReceiptError) { dismissReceiptError(); + return; } + + showConfirmModal({ + title: translate('iou.dismissReceiptError'), + prompt: translate('iou.dismissReceiptErrorConfirmation'), + confirmText: translate('common.dismiss'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + dismissReceiptError(); + }); }} dismissError={dismissReceiptError} style={[shouldShowAuditMessage ? styles.mt3 : styles.mv3, !showReceiptErrorWithEmptyState && styles.flex1]} @@ -407,22 +421,6 @@ function MoneyRequestReceiptView({ )} {!shouldShowReceiptEmptyState && !hasReceipt && } {!!shouldShowAuditMessage && !hasReceipt && receiptAuditMessagesRow} - { - dismissReceiptError(); - setShowConfirmDismissReceiptError(false); - }} - onCancel={() => { - setShowConfirmDismissReceiptError(false); - }} - title={translate('iou.dismissReceiptError')} - prompt={translate('iou.dismissReceiptErrorConfirmation')} - confirmText={translate('common.dismiss')} - cancelText={translate('common.cancel')} - shouldShowCancelButton - danger - /> ); } diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index 9dc2c0fa758d7..d4565c6a33677 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -10,10 +10,10 @@ import AnimatedSubmitButton from '@components/AnimatedSubmitButton'; import Button from '@components/Button'; import {getButtonRole} from '@components/Button/utils'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; -import ConfirmModal from '@components/ConfirmModal'; import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider'; import Icon from '@components/Icon'; import type {PaymentMethod} from '@components/KYCWall/types'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import MoneyReportHeaderStatusBarSkeleton from '@components/MoneyReportHeaderStatusBarSkeleton'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {PressableWithFeedback} from '@components/Pressable'; @@ -24,6 +24,7 @@ import ExportWithDropdownMenu from '@components/ReportActionItem/ExportWithDropd import AnimatedSettlementButton from '@components/SettlementButton/AnimatedSettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -172,10 +173,10 @@ function MoneyRequestReportPreviewContent({ const {isPaidAnimationRunning, isApprovedAnimationRunning, isSubmittingAnimationRunning, stopAnimation, startAnimation, startApprovedAnimation, startSubmittingAnimation} = usePaymentAnimations(); + const {showConfirmModal} = useConfirmModal(); const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [requestType, setRequestType] = useState(); const [paymentType, setPaymentType] = useState(); - const [isDEWModalVisible, setIsDEWModalVisible] = useState(false); const isIouReportArchived = useReportIsArchived(iouReportID); const isChatReportArchived = useReportIsArchived(chatReport?.reportID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); @@ -312,9 +313,23 @@ function MoneyRequestReportPreviewContent({ ], ); + const showDEWModal = useCallback(() => { + showConfirmModal({ + title: translate('customApprovalWorkflow.title'), + prompt: translate('customApprovalWorkflow.description'), + confirmText: translate('customApprovalWorkflow.goToExpensifyClassic'), + shouldShowCancelButton: false, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }); + }, [showConfirmModal, translate]); + const confirmApproval = () => { if (hasDynamicExternalWorkflow(policy) && !isDEWBetaEnabled) { - setIsDEWModalVisible(true); + showDEWModal(); return; } setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); @@ -621,7 +636,7 @@ function MoneyRequestReportPreviewContent({ text={translate('common.submit')} onPress={() => { if (hasDynamicExternalWorkflow(policy) && !isDEWBetaEnabled) { - setIsDEWModalVisible(true); + showDEWModal(); return; } startSubmittingAnimation(); @@ -947,18 +962,6 @@ function MoneyRequestReportPreviewContent({ /> )} - { - setIsDEWModalVisible(false); - openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setIsDEWModalVisible(false)} - prompt={translate('customApprovalWorkflow.description')} - confirmText={translate('customApprovalWorkflow.goToExpensifyClassic')} - shouldShowCancelButton={false} - /> ); } diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 5fa6b5845b477..2efcbe69ab846 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -59,6 +59,7 @@ function useSelectedTransactionsActions({ policy, beginExportWithTemplate, isOnSearch, + onDeleteSelected, }: { report?: Report; reportActions: ReportAction[]; @@ -69,6 +70,7 @@ function useSelectedTransactionsActions({ policy?: Policy; beginExportWithTemplate: (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => void; isOnSearch?: boolean; + onDeleteSelected?: (handleDeleteTransactions: () => void, handleDeleteTransactionsWithNavigation: (backToRoute?: Route) => void) => void | Promise; }) { const {isOffline} = useNetworkWithOfflineStatus(); const {isDelegateAccessRestricted} = useDelegateNoAccessState(); @@ -392,7 +394,13 @@ function useSelectedTransactionsActions({ text: translate('common.delete'), icon: expensifyIcons.Trashcan, value: CONST.REPORT.SECONDARY_ACTIONS.DELETE, - onSelected: showDeleteModal, + onSelected: () => { + if (onDeleteSelected) { + onDeleteSelected(handleDeleteTransactions, handleDeleteTransactionsWithNavigation); + } else { + showDeleteModal(); + } + }, }); } computedOptions = options;