Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
821ecd7
Keep ready exports on reload instead of clearing them
mollfpr Aug 10, 2026
29c6b16
Show export status in one app-level modal instead of per screen
mollfpr Aug 10, 2026
7ca6857
Update export tests for the app-level status modal
mollfpr Aug 10, 2026
548a32e
Skip Concierge hand-offs in the export status manager
mollfpr Aug 13, 2026
5de1e87
Fix the import type annotation in the useExportActions test
mollfpr Aug 13, 2026
0901054
Merge remote-tracking branch 'origin/main' into mollfpr-export-status…
mollfpr Aug 13, 2026
79f147a
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 13, 2026
4f021f3
keep the concierge export confirmation and remember it was dismissed
mollfpr Aug 16, 2026
e2639d9
show the concierge confirmation modal and open concierge in the side …
mollfpr Aug 16, 2026
5e3b37d
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 16, 2026
6e94511
Never clear a Concierge export record on dismiss
mollfpr Aug 16, 2026
7d5a57a
Never clear a concierge export record from the frontend
mollfpr Aug 16, 2026
06a0792
Add test for the Concierge handoff
mollfpr Aug 16, 2026
4893468
Add comment back
mollfpr Aug 16, 2026
cb1aed7
Keyed the modal by exportID
mollfpr Aug 16, 2026
9664283
Merge main and route receipts export through the status manager, drop…
mollfpr Aug 17, 2026
363fb9d
Only auto-download exports on the leader tab to avoid duplicates acro…
mollfpr Aug 17, 2026
b6ac844
rewrite comments as plain sentences without jargon
mollfpr Aug 17, 2026
697be31
Merge remote-tracking branch 'origin/main' into mollfpr-export-status…
mollfpr Aug 18, 2026
5d42643
Unmount export modal if the export is dismiss from other tab
mollfpr Aug 20, 2026
306d7d0
Add test for ExportDownloadStatusManager
mollfpr Aug 20, 2026
cfeb0d8
Remove export surfaced flag
mollfpr Aug 21, 2026
c33239a
Remove hasBeenSurfaced flag and add latch to prevent modal dismiss af…
mollfpr Aug 21, 2026
204a597
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 25, 2026
c6deb88
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 26, 2026
3a0684f
Dropping queue from the doc
mollfpr Aug 26, 2026
f730047
Only run autodownload where the state drive from preparing
mollfpr Aug 26, 2026
1c8334b
Fix modal open in other tab when intiated
mollfpr Aug 26, 2026
b311593
Update export modal test
mollfpr Aug 26, 2026
a37cccb
Add surfaceable to cspell
mollfpr Aug 26, 2026
eea0f4a
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 26, 2026
0d765a6
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 26, 2026
ecec009
Fix failing jest
mollfpr Aug 26, 2026
c61c05d
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 28, 2026
1341f3a
Apply batched suggestions from code review
mollfpr Aug 31, 2026
5bf9357
Apply batched suggestions from code review
mollfpr Aug 31, 2026
2be7ba0
Merge branch 'main' of github.com:Expensify/App into mollfpr-export-s…
mollfpr Aug 31, 2026
6df1934
Fix render hooks issue and eslint
mollfpr Aug 31, 2026
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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@
"superapp",
"superpowered",
"supportal",
"surfaceable",
"svgs",
"symbolicate",
"symbolicated",
Expand Down
97 changes: 97 additions & 0 deletions src/components/ExportDownloadStatusManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import useOnyx from '@hooks/useOnyx';

import {clearExportDownload, wasExportInitiatedLocally} from '@libs/actions/Export';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

import React, {useState} from 'react';

import ExportDownloadStatusModal from './ExportDownloadStatusModal';

/**
* Renders the export status modal for the whole app. It watches the export collection and shows the modal
* for the active export (preparing or ready). Because it is the single owner of the modal and reads straight
* from Onyx, a screen only has to start an export (which writes its record); this shows the progress, delivers
* the file when it is ready, and still surfaces it after a reload. There is no per-screen modal to coordinate with.
*/
function ExportDownloadStatusManager() {
const [exportDownloads, exportDownloadsMetadata] = useOnyx(ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD);
const [openExportID, setOpenExportID] = useState<string | undefined>();

// Record keys that already existed when this tab loaded. A reload re-reads the collection and re-seeds this,
// which is how an in-flight export resurfaces after a reload. A record that appears later because another
// tab started an export is not in here, so it does not pop the modal open in this tab until it reloads.
const [recordKeysAtLoad, setRecordKeysAtLoad] = useState<Set<string> | undefined>(undefined);
if (recordKeysAtLoad === undefined && exportDownloadsMetadata.status === 'loaded') {
setRecordKeysAtLoad(new Set(Object.keys(exportDownloads ?? {})));
}

const surfaceableCandidate = Object.entries(exportDownloads ?? {}).find(([key, exportDownload]) => {
if (!exportDownload) {
return false;
}

// Concierge hand-off is owned by the BE worker: it delivers via the Concierge chat on success and posts
Comment thread
mollfpr marked this conversation as resolved.
// a failure notice there too. There is nothing useful to show in a modal, so we never surface these.
if (exportDownload.shouldSendFromConcierge) {
return false;
}
const isSurfaceableState = exportDownload.state === CONST.EXPORT_DOWNLOAD.STATE.PREPARING || exportDownload.state === CONST.EXPORT_DOWNLOAD.STATE.READY;
if (!isSurfaceableState) {
return false;
}

// Only surface an export this tab owns: one it started itself, or one that already existed when it
Comment thread
mollfpr marked this conversation as resolved.
// loaded. An export started in another tab must not pop open here until this tab reloads.
const exportID = key.replace(ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD, '');
return !!recordKeysAtLoad?.has(key) || wasExportInitiatedLocally(exportID);
});
const [surfaceableKey] = surfaceableCandidate ?? [];
const surfaceableExportID = surfaceableKey?.replace(ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD, '');

// Adjust state to avoid a flash. openExportID keeps the modal open through the state changes
// the selector above skips (like the flip to shouldSendFromConcierge), and close it only once
// the record is gone.
if (!openExportID && surfaceableExportID) {
setOpenExportID(surfaceableExportID);
}
if (openExportID && !exportDownloads?.[`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${openExportID}`]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested branch: reset when the tracked record disappears

When the tracked record is removed (e.g. cleared from another tab), the manager must drop openExportID so a subsequently-queued export can surface. Nothing drives the render → record-removed → new-record-surfaces sequence, so a regression that leaves the manager stuck on a stale ID wouldn't be caught.

Add to tests/unit/ExportDownloadStatusManagerTest.tsx:

it('resets and surfaces a new export after the tracked record is removed', async () => {
    await Onyx.set(EXPORT_KEY, {state: 'ready', fileName: CSV_FILE_NAME});
    render(<ExportDownloadStatusManager />);
    await waitForBatchedUpdatesWithAct();

    await Onyx.set(EXPORT_KEY, null);
    await waitForBatchedUpdatesWithAct();
    expect(screen.queryByText('exportDownload.readyTitle')).toBeNull();

    const SECOND_KEY = `${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}second-export` as const;
    await Onyx.set(SECOND_KEY, {state: 'preparing'});
    await waitForBatchedUpdatesWithAct();
    expect(screen.getByText('exportDownload.preparingTitle')).toBeTruthy();
});

setOpenExportID(undefined);
}

if (!openExportID) {
return null;
}

const openExportKey = `${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${openExportID}`;
const exportDownload = exportDownloads?.[openExportKey];
if (!exportDownload) {
return null;
}

const handleClose = () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component is the heart of the refactor, but it has no test file. Both non-trivial pieces are uncovered:

  • the activeEntry predicate (lines 22–34), whose Concierge branch is state-independent (return !exportDownload.hasBeenSurfaced)
  • this handleClose (lines 43–53), with its three branches: concierge → markExportDownloadSurfaced, preparing → no-op, else → clearExportDownload

The deleted useExportDownloadStatusModalTest.ts used to cover exactly these cases. Without a replacement, a future change that flips a branch — e.g. dropping the !hasBeenSurfaced guard — would make the Concierge confirmation modal re-appear on every reload and no test would fail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 306d7d0

if (exportDownload.state === CONST.EXPORT_DOWNLOAD.STATE.PREPARING && !exportDownload.shouldSendFromConcierge) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested branch: preparing close is a no-op

Dismissing while still preparing must keep the modal open and clear nothing. The existing test titled "dismissing a preparing export is a no-op" actually presses the Send-from-Concierge button (sendExportFileFromConcierge), not the close path — so this early-return is unverified.

Add to tests/unit/ExportDownloadStatusManagerTest.tsx:

it('dismissing while preparing keeps the modal open and clears nothing', async () => {
    await Onyx.set(EXPORT_KEY, {state: 'preparing'});
    render(<ExportDownloadStatusManager />);
    await waitForBatchedUpdatesWithAct();

    fireEvent.press(screen.getByText('exportDownload.close')); // or backdrop dismiss
    expect(mockClearExportDownload).not.toHaveBeenCalled();
    expect(screen.getByText('exportDownload.preparingTitle')).toBeTruthy();
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already covered in ExportDownloadStatusModalTest at is non-dismissible during preparing state.

return;
}
if (exportDownload.shouldSendFromConcierge) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Untested branch: Concierge hand-off close must not clear the record

This branch (setOpenExportID(undefined) without clearExportDownload) is the invariant your own comment protects: clearing here would wipe shouldSendFromConcierge before the worker reads it, so the file never reaches Concierge. It was covered by the now-deleted useExportDownloadStatusModalTest.ts ("keeps the export NVP intact when sending via Concierge") — that coverage is lost, not migrated.

Add to tests/unit/ExportDownloadStatusManagerTest.tsx:

it('closing after hand-off to Concierge drops the modal without clearing the record', async () => {
    await Onyx.set(EXPORT_KEY, {state: 'preparing'});
    render(<ExportDownloadStatusManager />);
    await waitForBatchedUpdatesWithAct();

    // Worker now owns delivery.
    await Onyx.merge(EXPORT_KEY, {shouldSendFromConcierge: true});
    await waitForBatchedUpdatesWithAct();

    // Trigger onClose (dismiss/backdrop) and assert the record is preserved.
    fireEvent.press(screen.getByText('exportDownload.close'));
    expect(mockClearExportDownload).not.toHaveBeenCalled();
});

setOpenExportID(undefined);
return;
}
clearExportDownload(openExportID, exportDownload);
setOpenExportID(undefined);
};

Comment thread
mollfpr marked this conversation as resolved.
return (
<ExportDownloadStatusModal
key={openExportID}
exportID={openExportID}
isVisible
onClose={handleClose}
Comment thread
mollfpr marked this conversation as resolved.
/>
);
}

ExportDownloadStatusManager.displayName = 'ExportDownloadStatusManager';

export default ExportDownloadStatusManager;
35 changes: 28 additions & 7 deletions src/components/ExportDownloadStatusModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import usePreviousDefined from '@hooks/usePreviousDefined';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';

import {isClientTheLeader} from '@libs/ActiveClientManager';
import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL';
import {isMobileSafari} from '@libs/Browser';
import {getOldDotURLFromEnvironment} from '@libs/Environment/Environment';
import fileDownload from '@libs/fileDownload';
import {buildSecureDownloadURL} from '@libs/UrlUtils';

import {sendExportFileFromConcierge} from '@userActions/Export';
import {close} from '@userActions/Modal';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Session} from '@src/types/onyx';

import React, {useEffect} from 'react';
import type {OnyxEntry} from 'react-native-onyx';

import React, {useEffect, useRef} from 'react';
import {View} from 'react-native';

import ActivityIndicator from './ActivityIndicator';
Expand All @@ -28,6 +33,8 @@ import Modal from './Modal';
import RenderHTML from './RenderHTML';
import Text from './Text';

const selectEncryptedAuthToken = (session: OnyxEntry<Session>) => session?.encryptedAuthToken;

type ExportDownloadStatusModalProps = {
/** The export ID to subscribe to */
exportID: string;
Expand All @@ -51,10 +58,11 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E
const {login: currentUserLogin} = useCurrentUserPersonalDetails();
const {environment} = useEnvironment();

const [encryptedAuthToken] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.encryptedAuthToken});
const [encryptedAuthToken] = useOnyx(ONYXKEYS.SESSION, {selector: selectEncryptedAuthToken});

const [exportDownload] = useOnyx(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${exportID}`);
const displayedExport = usePreviousDefined(exportDownload);
const wasRecordCleared = !exportDownload && !!displayedExport;

const state = displayedExport?.state;
const shouldSendFromConcierge = displayedExport?.shouldSendFromConcierge;
Expand All @@ -70,9 +78,18 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E
const isFailed = state === CONST.EXPORT_DOWNLOAD.STATE.FAILED;
const isEmptyReceipts = isReady && exportType === CONST.EXPORT_DOWNLOAD.TYPE.RECEIPTS && receiptCount === 0;

const wasPreparingRef = useRef(false);
useEffect(() => {
if (state !== CONST.EXPORT_DOWNLOAD.STATE.PREPARING) {
return;
}

wasPreparingRef.current = true;
}, [state]);

// Build the secure download URL the same way downloadReportPDF does, so the host always follows
// the app's current environment (instead of the env baked into a backend-built URL) and authenticates
// via the encryptedAuthToken no separate OldDot sign-in needed.
// via the encryptedAuthToken, so no separate OldDot sign-in is needed.
const downloadFile = () => {
if (!fileName || !currentUserLogin) {
return;
Expand All @@ -85,7 +102,8 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E
};

useEffect(() => {
if (!isReady || !fileName || shouldSendFromConcierge || isEmptyReceipts) {
// Only the leader tab auto-downloads, so a ready export isn't downloaded once per open tab.
if (!isReady || !fileName || shouldSendFromConcierge || isEmptyReceipts || !isClientTheLeader() || !wasPreparingRef.current) {
return;
}
downloadFile();
Comment on lines +105 to 109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-downloads the file on every reload while a READY export is still on screen

clearStaleExportDownloads now keeps READY records (Export.ts:78) so they resurface after a reload — good. But this effect fires on mount whenever isReady is already true, not on the preparing → ready transition:

useEffect(() => {
    if (!isReady || !fileName || shouldSendFromConcierge || isEmptyReceipts || !isClientTheLeader()) {
        return;
    }
    downloadFile();
}, [isReady, fileName, shouldSendFromConcierge, isEmptyReceipts]);

clearStaleExportDownloads() runs once on app init (AuthScreensInitHandler.tsx:206), so on a plain refresh at "Your file is ready!": record survives → manager remounts this modal → isReady true on mount → the leader tab silently downloads the file again. The record clears only on Download/dismiss, so every refresh drops another copy into Downloads. The leader-gate stops the multi-tab duplicate but not the same-tab-reload duplicate, and QA step 8 only checks that the modal resurfaces.

Suggested fix — only auto-download when this session watched it finish preparing (the key={openExportID} on the manager keeps the ref per-export):

const wasPreparingRef = useRef(false);
useEffect(() => {
    if (state === CONST.EXPORT_DOWNLOAD.STATE.PREPARING) {
        wasPreparingRef.current = true;
    }
}, [state]);

useEffect(() => {
    // A resurfaced (post-reload) ready export shows the Download button instead of silently re-downloading.
    if (!isReady || !fileName || shouldSendFromConcierge || isEmptyReceipts || !isClientTheLeader() || !wasPreparingRef.current) {
        return;
    }
    downloadFile();
}, [isReady, fileName, shouldSendFromConcierge, isEmptyReceipts]);
RF-1.webm

Expand All @@ -96,10 +114,14 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E
sendExportFileFromConcierge(exportID, displayedExport ?? undefined);
};
const {openConciergeAnywhere} = useOpenConciergeAnywhere();
const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: isSmallScreenWidth, addOfflineIndicatorBottomSafeAreaPadding: false, style: styles.m5});

if (wasRecordCleared) {
return null;
}

const handleGoToConcierge = () => {
onClose();
openConciergeAnywhere({forceConcierge: true});
close(() => openConciergeAnywhere({forceConcierge: true}));
};

const handleDownloadFile = () => {
Expand All @@ -110,7 +132,6 @@ function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: E
};

const isNonDismissible = isPreparing;
const bottomSafeAreaPaddingStyle = useBottomSafeSafeAreaPaddingStyle({addBottomSafeAreaPadding: isSmallScreenWidth, addOfflineIndicatorBottomSafeAreaPadding: false, style: styles.m5});

const renderContent = () => {
if (isPreparing) {
Expand Down
17 changes: 7 additions & 10 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {View} from 'react-native';
import HeaderLoadingBar from './HeaderLoadingBar';
import HeaderWithBackButton from './HeaderWithBackButton';
import MoneyReportHeaderActions from './MoneyReportHeaderActions';
import {ExportDownloadStatusProvider} from './MoneyReportHeaderActions/ExportDownloadStatusProvider';
import MoneyReportHeaderModals from './MoneyReportHeaderModals';
import MoneyReportHeaderMoreContent from './MoneyReportHeaderMoreContent';
import {PaymentAnimationsProvider} from './PaymentAnimationsContext';
Expand All @@ -44,15 +43,13 @@ type MoneyReportHeaderProps = {
function MoneyReportHeader({reportID, shouldDisplayBackButton = false, onBackButtonPress}: MoneyReportHeaderProps) {
return (
<MoneyReportHeaderModals reportID={reportID}>
<ExportDownloadStatusProvider>
<PaymentAnimationsProvider>
<MoneyReportHeaderContent
reportID={reportID}
shouldDisplayBackButton={shouldDisplayBackButton}
onBackButtonPress={onBackButtonPress}
/>
</PaymentAnimationsProvider>
</ExportDownloadStatusProvider>
<PaymentAnimationsProvider>
<MoneyReportHeaderContent
reportID={reportID}
shouldDisplayBackButton={shouldDisplayBackButton}
onBackButtonPress={onBackButtonPress}
/>
</PaymentAnimationsProvider>
</MoneyReportHeaderModals>
);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import BulkDuplicateHandler from '@components/Search/BulkDuplicateHandler';
import {useSearchSelectionActions, useSearchSelectionContext} from '@components/Search/SearchContext';

import useConfirmModal from '@hooks/useConfirmModal';
import useExportDownloadStatusModal from '@hooks/useExportDownloadStatusModal';
import useFilterSelectedTransactions from '@hooks/useFilterSelectedTransactions';
import useLocalize from '@hooks/useLocalize';
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
Expand Down Expand Up @@ -82,7 +81,6 @@ function SelectionToolbar({reportID, transactions, reportActions}: SelectionTool

const isMobileSelectionModeEnabled = useMobileSelectionMode();
const {showConfirmModal} = useConfirmModal();
const {trackExport, exportDownloadStatusModal} = useExportDownloadStatusModal(() => clearSelectedTransactions(true));

const [offlineModalVisible, setOfflineModalVisible] = useState(false);
const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
Expand All @@ -100,7 +98,7 @@ function SelectionToolbar({reportID, transactions, reportActions}: SelectionTool
return;
}

const exportID = queueExportSearchWithTemplate(
queueExportSearchWithTemplate(
{
templateName,
templateType,
Expand All @@ -112,7 +110,9 @@ function SelectionToolbar({reportID, transactions, reportActions}: SelectionTool
},
true,
);
trackExport(exportID);

// Clear the selection now that the export has started; the app-level ExportDownloadStatusManager shows the modal.
Comment thread
mollfpr marked this conversation as resolved.
Comment thread
mollfpr marked this conversation as resolved.
clearSelectedTransactions(true);
};

const onDeleteSelected = (handleDeleteTransactions: () => void, handleDeleteTransactionsWithNavigation: (backToRoute?: Route) => void) => {
Expand Down Expand Up @@ -238,7 +238,6 @@ function SelectionToolbar({reportID, transactions, reportActions}: SelectionTool

return (
<>
{exportDownloadStatusModal}
{isDuplicateOptionVisible && (
<BulkDuplicateHandler
selectedTransactionsKeys={selectedTransactionIDs}
Expand Down
2 changes: 0 additions & 2 deletions src/components/Search/SearchBulkActionsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,6 @@ function SearchBulkActionsButton({queryJSON}: SearchBulkActionsButtonProps) {
handleExpensifyCardStatementPDFModalHide,
isExpensifyCardStatementMultiFeedAlertVisible,
handleExpensifyCardStatementMultiFeedAlertClose,
exportDownloadStatusModal,
dismissModalAndUpdateUseHold,
dismissRejectModalBasedOnAction,
isDuplicateOptionVisible,
Expand Down Expand Up @@ -321,7 +320,6 @@ function SearchBulkActionsButton({queryJSON}: SearchBulkActionsButtonProps) {
isDM={areAllTransactionsFromDMReports}
/>
)}
{exportDownloadStatusModal}
</>
);
}
Expand Down
14 changes: 8 additions & 6 deletions src/hooks/useExportActions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types';
import {useExportDownloadStatus} from '@components/MoneyReportHeaderActions/ExportDownloadStatusProvider';
import type {PopoverMenuItem} from '@components/PopoverMenu';
import {useSearchSelectionActions} from '@components/Search/SearchContext';

import {getAccountingIntegrationDisplayName} from '@libs/AccountingUtils';
import {exportReceiptsToZip} from '@libs/actions/Export';
Expand Down Expand Up @@ -85,7 +85,7 @@ function useExportActions({reportID, policy, onPDFModalOpen}: UseExportActionsPa

const {showDecisionModal} = useDecisionModal();
const {triggerExportOrConfirm} = useExportAgainModal(moneyRequestReport?.reportID, moneyRequestReport?.policyID);
const {trackExport} = useExportDownloadStatus();
const {clearSelectedTransactions} = useSearchSelectionActions();

const expensifyIcons = useMemoizedLazyExpensifyIcons([
'Table',
Expand Down Expand Up @@ -132,7 +132,7 @@ function useExportActions({reportID, policy, onPDFModalOpen}: UseExportActionsPa
return;
}

const exportID = queueExportSearchWithTemplate(
queueExportSearchWithTemplate(
{
templateName,
templateType,
Expand All @@ -144,7 +144,9 @@ function useExportActions({reportID, policy, onPDFModalOpen}: UseExportActionsPa
},
true,
);
trackExport(exportID);

// Clear the selection now that the export has started. The app-level ExportDownloadStatusManager shows the modal.
Comment thread
mollfpr marked this conversation as resolved.
clearSelectedTransactions(true);
};

const exportSubmenuOptions: Record<string, DropdownOption<string>> = {
Expand Down Expand Up @@ -297,8 +299,8 @@ function useExportActions({reportID, policy, onPDFModalOpen}: UseExportActionsPa
if (!moneyRequestReport?.reportID) {
return;
}
const exportID = exportReceiptsToZip({reportIDs: [moneyRequestReport.reportID]});
trackExport(exportID);
exportReceiptsToZip({reportIDs: [moneyRequestReport.reportID]});
clearSelectedTransactions(true);
},
},
[CONST.REPORT.SECONDARY_ACTIONS.PRINT]: {
Expand Down
Loading
Loading