diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 1ff2b57ea9a7a..816371e2d4301 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -5,7 +5,6 @@ import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Button from '@components/Button'; import CaretWrapper from '@components/CaretWrapper'; -import ConfirmModal from '@components/ConfirmModal'; import DisplayNames from '@components/DisplayNames'; import Icon from '@components/Icon'; // eslint-disable-next-line no-restricted-imports @@ -22,9 +21,6 @@ import SidePanelButton from '@components/SidePanel/SidePanelButton'; import TaskHeaderActionButton from '@components/TaskHeaderActionButton'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; -import useAncestors from '@hooks/useAncestors'; -import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import useHasOutstandingChildTask from '@hooks/useHasOutstandingChildTask'; import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility'; @@ -76,7 +72,6 @@ import EarlyDiscountBanner from '@pages/settings/Subscription/CardSection/Billin import FreeTrial from '@pages/settings/Subscription/FreeTrial'; import {joinRoom} from '@userActions/Report'; import {callFunctionIfActionIsAllowed} from '@userActions/Session'; -import {deleteTask} from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; @@ -108,7 +103,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); const route = useRoute(); - const [isDeleteTaskConfirmModalVisible, setIsDeleteTaskConfirmModalVisible] = React.useState(false); const invoiceReceiverPolicyID = report?.invoiceReceiver && 'policyID' in report.invoiceReceiver ? report.invoiceReceiver.policyID : undefined; const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`, {canBeMissing: true}); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID) ?? getNonEmptyStringOnyxID(report?.reportID)}`, {canBeMissing: true}); @@ -121,11 +115,9 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, {canBeMissing: true}); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, {canBeMissing: true}); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); - const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true}); const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report?.reportID}`, {canBeMissing: true}); const isReportArchived = isArchivedReport(reportNameValuePairs); - const hasOutstandingChildTask = useHasOutstandingChildTask(report); const {translate, localeCompare, formatPhoneNumber} = useLocalize(); const theme = useTheme(); @@ -163,7 +155,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, // This is used to ensure that we display the text exactly as the user entered it when displaying thread header text, instead of parsing their text to HTML. const shouldParseFullTitle = parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT && !isGroupChat; const subscriptionPlan = useSubscriptionPlan(); - const ancestors = useAncestors(report); const shouldShowSubtitle = () => { if (!subtitle) { @@ -382,28 +373,6 @@ function HeaderView({report, parentReportAction, onNavigationMenuButtonClicked, {!isInSidePanel && } {shouldDisplaySearchRouter && } - { - setIsDeleteTaskConfirmModalVisible(false); - deleteTask( - report, - parentReport, - isReportArchived, - currentUserPersonalDetails.accountID, - hasOutstandingChildTask, - parentReportAction ?? undefined, - ancestors, - ); - }} - onCancel={() => setIsDeleteTaskConfirmModalVisible(false)} - title={translate('task.deleteTask')} - prompt={translate('task.deleteConfirmation')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - shouldEnableNewFocusManagement - /> )} diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 422166b4373cf..7fd6d38c6efdb 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -6,10 +6,11 @@ import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, View} import {DeviceEventEmitter, Dimensions, InteractionManager} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {Actions, ActionSheetAwareScrollViewContext} from '@components/ActionSheetAwareScrollView'; -import ConfirmModal from '@components/ConfirmModal'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import {useSearchContext} from '@components/Search/SearchContext'; import useAncestors from '@hooks/useAncestors'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDeleteTransactions from '@hooks/useDeleteTransactions'; import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations'; @@ -49,6 +50,7 @@ type PopoverReportActionContextMenuProps = { function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuProps) { const {translate} = useLocalize(); + const {showConfirmModal} = useConfirmModal(); const reportIDRef = useRef(undefined); const typeRef = useRef(undefined); const reportActionRef = useRef> | null>(null); @@ -75,8 +77,6 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro const {email} = useCurrentUserPersonalDetails(); const [isPopoverVisible, setIsPopoverVisible] = useState(false); - const [isDeleteCommentConfirmModalVisible, setIsDeleteCommentConfirmModalVisible] = useState(false); - const [shouldSetModalVisibilityForDeleteConfirmation, setShouldSetModalVisibilityForDeleteConfirmation] = useState(true); const [isRoomArchived, setIsRoomArchived] = useState(false); const [isChronosReportEnabled, setIsChronosReportEnabled] = useState(false); @@ -104,7 +104,6 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro const onPopoverHide = useRef(() => {}); const onEmojiPickerToggle = useRef void)>(undefined); const onCancelDeleteModal = useRef(() => {}); - const onConfirmDeleteModal = useRef(() => {}); const onPopoverHideActionCallback = useRef(() => {}); const callbackWhenDeleteModalHide = useRef(() => {}); @@ -343,8 +342,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro } ancestorsRef.current = ancestors; }, [originalReport, ancestors]); - const confirmDeleteAndHideModal = useCallback(() => { - callbackWhenDeleteModalHide.current = runAndResetCallback(onConfirmDeleteModal.current); + const performDelete = useCallback(() => { const reportAction = reportActionRef.current; if (isMoneyRequestAction(reportAction)) { const originalMessage = getOriginalMessage(reportAction); @@ -375,7 +373,6 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro } DeviceEventEmitter.emit(`deletedReportAction_${reportIDRef.current}`, reportAction?.reportActionID); - setIsDeleteCommentConfirmModalVisible(false); }, [ report, iouReport, @@ -392,27 +389,42 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro violations, ]); - const hideDeleteModal = () => { + const hideDeleteModal = useCallback(() => { callbackWhenDeleteModalHide.current = () => (onCancelDeleteModal.current = runAndResetCallback(onCancelDeleteModal.current)); - setIsDeleteCommentConfirmModalVisible(false); - setShouldSetModalVisibilityForDeleteConfirmation(true); setIsRoomArchived(false); setIsChronosReportEnabled(false); setIsChatPinned(false); setHasUnreadMessages(false); - }; + }, []); /** Opens the Confirm delete action modal */ - const showDeleteModal: ReportActionContextMenu['showDeleteModal'] = (reportID, reportAction, shouldSetModalVisibility = true, onConfirm = () => {}, onCancel = () => {}) => { - onCancelDeleteModal.current = onCancel; + const showDeleteModal: ReportActionContextMenu['showDeleteModal'] = useCallback( + async (reportID, reportAction, shouldSetModalVisibility = true, onConfirm = () => {}, onCancel = () => {}) => { + reportIDRef.current = reportID; + reportActionRef.current = reportAction ?? null; + + const result = await showConfirmModal({ + title: translate('reportActionContextMenu.deleteAction', {action: reportAction}), + prompt: translate('reportActionContextMenu.deleteConfirmation', {action: reportAction}), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + shouldSetModalVisibility, + }); - onConfirmDeleteModal.current = onConfirm; - reportIDRef.current = reportID; - reportActionRef.current = reportAction ?? null; + if (result.action === ModalActions.CONFIRM) { + onConfirm(); + performDelete(); + } else { + onCancel(); + } - setShouldSetModalVisibilityForDeleteConfirmation(shouldSetModalVisibility); - setIsDeleteCommentConfirmModalVisible(true); - }; + // Clear refs and run callbacks AFTER the action is performed + clearActiveReportAction(); + callbackWhenDeleteModalHide.current(); + }, + [showConfirmModal, translate, performDelete], + ); useImperativeHandle(ref, () => ({ showContextMenu, @@ -428,60 +440,41 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro composerToRefocusOnCloseEmojiPicker: composerToRefocusOnClose, })); - const reportAction = reportActionRef.current; - return ( - <> - hideContextMenu()} + onModalShow={runAndResetOnPopoverShow} + onModalHide={runAndResetOnPopoverHide} + anchorPosition={popoverAnchorPosition.current} + animationIn="fadeIn" + disableAnimation={false} + shouldSetModalVisibility={false} + fullscreen + withoutOverlay={isWithoutOverlay} + anchorDimensions={contextMenuDimensions.current} + anchorRef={anchorRef} + shouldSwitchPositionIfOverflow={shouldSwitchPositionIfOverflow} + > + hideContextMenu()} - onModalShow={runAndResetOnPopoverShow} - onModalHide={runAndResetOnPopoverHide} - anchorPosition={popoverAnchorPosition.current} - animationIn="fadeIn" - disableAnimation={false} - shouldSetModalVisibility={false} - fullscreen - withoutOverlay={isWithoutOverlay} - anchorDimensions={contextMenuDimensions.current} - anchorRef={anchorRef} - shouldSwitchPositionIfOverflow={shouldSwitchPositionIfOverflow} - > - - - { - clearActiveReportAction(); - callbackWhenDeleteModalHide.current(); - }} - prompt={translate('reportActionContextMenu.deleteConfirmation', {action: reportAction})} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger + type={typeRef.current} + reportID={reportIDRef.current} + reportActionID={reportActionIDRef.current} + draftMessage={reportActionDraftMessageRef.current} + selection={selectionRef.current} + isArchivedRoom={isRoomArchived} + isChronosReport={isChronosReportEnabled} + isPinnedChat={isChatPinned} + isUnreadChat={hasUnreadMessages} + isThreadReportParentAction={isThreadReportParentAction} + anchor={contextMenuTargetNode} + contentRef={contentRef} + originalReportID={originalReportIDRef.current} + disabledActions={disabledActions} + setIsEmojiPickerActive={onEmojiPickerToggle.current} /> - + ); } diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index 2da4ac2bbffc8..10c53353cd110 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -58,7 +58,7 @@ type HideContextMenu = (params?: HideContextMenuParams) => void; type ReportActionContextMenu = { showContextMenu: ShowContextMenu; hideContextMenu: HideContextMenu; - showDeleteModal: (reportID: string, reportAction: OnyxEntry, shouldSetModalVisibility?: boolean, onConfirm?: OnConfirm, onCancel?: OnCancel) => void; + showDeleteModal: (reportID: string, reportAction: OnyxEntry, shouldSetModalVisibility?: boolean, onConfirm?: OnConfirm, onCancel?: OnCancel) => Promise; hideDeleteModal: () => void; isActiveReportAction: (accountID: string | number) => boolean; instanceIDRef: RefObject; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 6bd27e0ae1040..b8ded6c076cc4 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -10,7 +10,6 @@ import type {Emoji} from '@assets/emojis/types'; import * as ActionSheetAwareScrollView from '@components/ActionSheetAwareScrollView'; import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; -import ConfirmModal from '@components/ConfirmModal'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; @@ -18,6 +17,7 @@ import Icon from '@components/Icon'; import InlineSystemMessage from '@components/InlineSystemMessage'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; @@ -39,6 +39,7 @@ import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import UnreadActionIndicator from '@components/UnreadActionIndicator'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -501,6 +502,7 @@ function PureReportActionItem({ }: PureReportActionItemProps) { const actionSheetAwareScrollViewContext = useContext(ActionSheetAwareScrollView.ActionSheetAwareScrollViewContext); const {translate, formatPhoneNumber, localeCompare, formatTravelDate, getLocalDateFromDatetime} = useLocalize(); + const {showConfirmModal} = useConfirmModal(); const personalDetail = useCurrentUserPersonalDetails(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const reportID = report?.reportID ?? action?.reportID; @@ -563,7 +565,6 @@ function PureReportActionItem({ currentSearchHash = searchContextCurrentSearchHash; } - const [showConfirmDismissReceiptError, setShowConfirmDismissReceiptError] = useState(false); const dismissError = useCallback(() => { const transactionID = isMoneyRequestAction(action) ? getOriginalMessage(action)?.IOUTransactionID : undefined; if (transactionID) { @@ -572,18 +573,33 @@ function PureReportActionItem({ clearAllRelatedReportActionErrors(reportID, action); }, [reportID, clearError, clearAllRelatedReportActionErrors, action]); - const onClose = () => { + const showDismissReceiptErrorModal = useCallback(async () => { + const result = await showConfirmModal({ + title: translate('iou.dismissReceiptError'), + prompt: translate('iou.dismissReceiptErrorConfirmation'), + confirmText: translate('common.dismiss'), + cancelText: translate('common.cancel'), + shouldShowCancelButton: true, + danger: true, + }); + if (result.action === ModalActions.CONFIRM) { + dismissError(); + } + }, [showConfirmModal, translate, dismissError]); + + const onClose = useCallback(() => { const errors = linkedTransactionRouteError ?? getLatestErrorMessageField(action as OnyxDataWithErrors); const errorEntries = Object.entries(errors ?? {}); const errorMessages = mapValues(Object.fromEntries(errorEntries), (error) => error); const hasReceiptError = Object.values(errorMessages).some((error) => isReceiptError(error)); if (hasReceiptError) { - setShowConfirmDismissReceiptError(true); + showDismissReceiptErrorModal(); } else { dismissError(); } - }; + }, [linkedTransactionRouteError, action, showDismissReceiptErrorModal, dismissError]); + useEffect( () => () => { // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, @@ -1929,22 +1945,6 @@ function PureReportActionItem({ - { - dismissError(); - 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/pages/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx index d19d33da74ff8..13be42d70a539 100644 --- a/src/pages/home/report/ReportDetailsExportPage.tsx +++ b/src/pages/home/report/ReportDetailsExportPage.tsx @@ -1,12 +1,13 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback} from 'react'; import type {ValueOf} from 'type-fest'; import ConfirmationPage from '@components/ConfirmationPage'; -import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import ScreenWrapper from '@components/ScreenWrapper'; import UserListItem from '@components/SelectionListWithSections/UserListItem'; import type {SelectorType} from '@components/SelectionScreen'; import SelectionScreen from '@components/SelectionScreen'; +import useConfirmModal from '@hooks/useConfirmModal'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -36,7 +37,7 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { const policyID = report?.policyID; const {translate} = useLocalize(); - const [modalStatus, setModalStatus] = useState(null); + const {showConfirmModal} = useConfirmModal(); const styles = useThemeStyles(); const lazyIllustrations = useMemoizedLazyIllustrations(['LaptopWithSecondScreenAndHourglass']); const expensifyIcons = useMemoizedLazyExpensifyIcons(['XeroSquare', 'QBOSquare', 'NetSuiteSquare', 'IntacctSquare', 'QBDSquare']); @@ -46,16 +47,30 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { const isExported = isExportedUtil(reportActions); const confirmExport = useCallback( - (type = modalStatus) => { + (type: ExportType) => { if (type === CONST.REPORT.EXPORT_OPTIONS.EXPORT_TO_INTEGRATION) { exportToIntegration(reportID, connectionName); } else if (type === CONST.REPORT.EXPORT_OPTIONS.MARK_AS_EXPORTED) { markAsManuallyExported(reportID, connectionName); } - setModalStatus(null); Navigation.dismissModal(); }, - [connectionName, modalStatus, reportID], + [connectionName, reportID], + ); + + const showExportAgainModal = useCallback( + async (type: ExportType) => { + const result = await showConfirmModal({ + title: translate('workspace.exportAgainModal.title'), + prompt: translate('workspace.exportAgainModal.description', {reportName: report?.reportName ?? '', connectionName}), + confirmText: translate('workspace.exportAgainModal.confirmText'), + cancelText: translate('workspace.exportAgainModal.cancelText'), + }); + if (result.action === ModalActions.CONFIRM) { + confirmExport(type); + } + }, + [showConfirmModal, translate, report?.reportName, connectionName, confirmExport], ); const exportSelectorOptions: ExportSelectorType[] = [ @@ -105,36 +120,25 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { } return ( - <> - - policyID={policyID} - accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]} - featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED} - displayName="ReportDetailsExportPage" - sections={[{data: exportSelectorOptions}]} - listItem={UserListItem} - shouldBeBlocked={false} - onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo))} - title="common.export" - connectionName={connectionName} - onSelectRow={({value}) => { - if (isExported) { - setModalStatus(value); - } else { - confirmExport(value); - } - }} - /> - setModalStatus(null)} - prompt={translate('workspace.exportAgainModal.description', {reportName: report?.reportName ?? '', connectionName})} - confirmText={translate('workspace.exportAgainModal.confirmText')} - cancelText={translate('workspace.exportAgainModal.cancelText')} - isVisible={!!modalStatus} - /> - + + policyID={policyID} + accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]} + featureName={CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED} + displayName="ReportDetailsExportPage" + sections={[{data: exportSelectorOptions}]} + listItem={UserListItem} + shouldBeBlocked={false} + onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo))} + title="common.export" + connectionName={connectionName} + onSelectRow={({value}) => { + if (isExported) { + showExportAgainModal(value); + } else { + confirmExport(value); + } + }} + /> ); }