From 2a94a5109a3b794940ff418a757ed49b3b99739f Mon Sep 17 00:00:00 2001 From: truph01 Date: Sat, 23 May 2026 11:20:04 +0700 Subject: [PATCH 01/12] feat: create ExportDownloadStatusModal --- src/components/ExportDownloadStatusModal.tsx | 189 +++++++++++++++++++ src/languages/de.ts | 14 ++ src/languages/en.ts | 14 ++ src/languages/es.ts | 14 ++ src/languages/fr.ts | 14 ++ src/languages/it.ts | 14 ++ src/languages/ja.ts | 14 ++ src/languages/nl.ts | 14 ++ src/languages/pl.ts | 14 ++ src/languages/pt-BR.ts | 14 ++ src/languages/zh-hans.ts | 14 ++ src/types/onyx/ExportDownload.ts | 3 + tests/unit/ExportDownloadStatusModalTest.tsx | 178 +++++++++++++++++ 13 files changed, 510 insertions(+) create mode 100644 src/components/ExportDownloadStatusModal.tsx create mode 100644 tests/unit/ExportDownloadStatusModalTest.tsx diff --git a/src/components/ExportDownloadStatusModal.tsx b/src/components/ExportDownloadStatusModal.tsx new file mode 100644 index 000000000000..0f8f7915ee50 --- /dev/null +++ b/src/components/ExportDownloadStatusModal.tsx @@ -0,0 +1,189 @@ +import {hasSeenTourSelector} from '@selectors/Onboarding'; +import React, {useCallback, useEffect, useRef} from 'react'; +import {View} from 'react-native'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import fileDownload from '@libs/fileDownload'; +import {clearExportDownload, sendExportFileFromConcierge} from '@userActions/Export'; +import {navigateToConciergeChat} from '@userActions/Report'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type ExportDownload from '@src/types/onyx/ExportDownload'; +import ActivityIndicator from './ActivityIndicator'; +import Button from './Button'; +import Modal from './Modal'; +import Text from './Text'; + +type ExportDownloadStatusModalProps = { + /** The export ID to subscribe to */ + exportID: string; + + /** Whether the modal is visible */ + isVisible: boolean; + + /** Callback when the modal is closed */ + onClose: () => void; + + /** Body text for the failed state — PDF and CSV use different copy */ + failedBody?: string; +}; + +function ExportDownloadStatusModal({exportID, isVisible, onClose, failedBody}: ExportDownloadStatusModalProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); + + const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const [betas] = useOnyx(ONYXKEYS.BETAS); + const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); + + const [exportDownload] = useOnyx(`${ONYXKEYS.COLLECTION.EXPORT_DOWNLOAD}${exportID}`); + + const lastKnownExportRef = useRef(null); + if (exportDownload) { + lastKnownExportRef.current = exportDownload; + } + + const displayedExport = exportDownload ?? lastKnownExportRef.current; + + const state = displayedExport?.state; + const shouldSendFromConcierge = displayedExport?.shouldSendFromConcierge; + const downloadURL = displayedExport?.downloadURL; + const isPreparing = state === CONST.EXPORT_DOWNLOAD.STATE.PREPARING && !shouldSendFromConcierge; + const isConcierge = !!shouldSendFromConcierge; + const isReady = state === CONST.EXPORT_DOWNLOAD.STATE.READY; + const isFailed = state === CONST.EXPORT_DOWNLOAD.STATE.FAILED; + + useEffect(() => { + if (!isReady || !downloadURL) { + return; + } + fileDownload(translate, downloadURL); + }, [isReady, downloadURL, translate]); + + const handleSendFromConcierge = useCallback(() => { + sendExportFileFromConcierge(exportID, displayedExport ?? undefined); + }, [exportID, displayedExport]); + + const handleGoToConcierge = useCallback(() => { + onClose(); + navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas); + }, [onClose, conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas]); + + const handleDismiss = useCallback(() => { + onClose(); + }, [onClose]); + + const handleDownloadFile = useCallback(() => { + if (downloadURL) { + fileDownload(translate, downloadURL); + } + }, [downloadURL, translate]); + + const handleClose = useCallback(() => { + clearExportDownload(exportID, displayedExport ?? undefined); + onClose(); + }, [exportID, displayedExport, onClose]); + + const isNonDismissable = isPreparing; + + const renderContent = () => { + if (isPreparing) { + return ( + <> + + {translate('exportDownload.preparingTitle')} + {translate('exportDownload.preparingBody')} +