Skip to content
Open
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
186 changes: 186 additions & 0 deletions src/components/ExportDownloadStatusModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import {hasSeenTourSelector} from '@selectors/Onboarding';
import React, {useCallback, useEffect} from 'react';
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.

Please don't use the useCallBack in any new component because the component must compile with React Compiler, quote from here

import {View} from 'react-native';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePreviousDefined from '@hooks/usePreviousDefined';
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 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();
// isSmallScreenWidth is needed here because the modal type depends on actual screen width, not layout mode
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();
const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
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.

❌ CONSISTENCY-5 (docs)

The eslint-disable comment on line 39 suppresses rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth but provides no justification explaining why the rule is intentionally bypassed. Without a comment, future maintainers cannot determine whether this suppression is permanent and intentional or a temporary workaround that should be revisited.

Add a justification comment explaining why isSmallScreenWidth is used instead of shouldUseNarrowLayout, or if this is a temporary suppression, consider using SEATBELT_INCREASE instead. For example:

// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
// isSmallScreenWidth is needed here because the modal type depends on actual screen width, not layout mode
const {isSmallScreenWidth} = useResponsiveLayout();

Reviewed at: 2a94a51 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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.

I added comment


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 displayedExport = usePreviousDefined(exportDownload);

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 || shouldSendFromConcierge) {
return;
}
fileDownload(translate, downloadURL);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isReady]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include download URL in auto-download effect deps

The auto-download effect reads downloadURL and shouldSendFromConcierge but only re-runs when isReady changes. If the export transitions to ready before downloadURL is merged into Onyx (or shouldSendFromConcierge flips after ready), this effect exits early once and never retries, so the automatic download is skipped even though the file later becomes available. Add the missing dependencies (or key the effect off the full readiness condition) so late-arriving Onyx fields still trigger the download.

Useful? React with 👍 / 👎.


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) {
return;
}
fileDownload(translate, downloadURL);
}, [downloadURL, translate]);

const handleClose = useCallback(() => {
clearExportDownload(exportID, displayedExport ?? undefined);
onClose();
}, [exportID, displayedExport, onClose]);

const isNonDismissible = isPreparing;

const renderContent = () => {
if (isPreparing) {
return (
<>
<ActivityIndicator
size="large"
style={styles.mb4}
reasonAttributes={{context: 'ExportDownloadStatusModal.preparing'}}
/>
<Text style={[styles.textHeadlineH1, styles.textAlignCenter, styles.mb2]}>{translate('exportDownload.preparingTitle')}</Text>
<Text style={[styles.textAlignCenter, styles.mb5]}>{translate('exportDownload.preparingBody')}</Text>
<Button
success
text={translate('exportDownload.sendFromConcierge')}
onPress={handleSendFromConcierge}
style={styles.w100}
/>
</>
);
}

if (isConcierge) {
return (
<>
<Text style={[styles.textHeadlineH1, styles.textAlignCenter, styles.mb2]}>{translate('exportDownload.conciergeTitle')}</Text>
<Text style={[styles.textAlignCenter, styles.mb5]}>{translate('exportDownload.conciergeBody')}</Text>
<Button
success
text={translate('exportDownload.goToConcierge')}
onPress={handleGoToConcierge}
style={styles.w100}
/>
<Button
text={translate('exportDownload.dismiss')}
onPress={handleDismiss}
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.

I think we only need to call the onClose here.

style={[styles.w100, styles.mt3]}
/>
</>
);
}

if (isReady) {
return (
<>
<Text style={[styles.textHeadlineH1, styles.textAlignCenter, styles.mb2]}>{translate('exportDownload.readyTitle')}</Text>
<Text style={[styles.textAlignCenter, styles.mb5]}>{translate('exportDownload.readyBody')}</Text>
<Button
success
text={translate('exportDownload.downloadFile')}
onPress={handleDownloadFile}
style={styles.w100}
/>
<Button
text={translate('exportDownload.close')}
onPress={handleClose}
style={[styles.w100, styles.mt3]}
/>
</>
);
}

if (isFailed) {
return (
<>
<Text style={[styles.textHeadlineH1, styles.textAlignCenter, styles.mb2]}>{translate('exportDownload.failedTitle')}</Text>
{!!failedBody && <Text style={[styles.textAlignCenter, styles.mb5]}>{failedBody}</Text>}
<Button
text={translate('exportDownload.close')}
onPress={handleClose}
style={styles.w100}
/>
</>
);
}

return null;
};

return (
<Modal
isVisible={isVisible}
onClose={isNonDismissible ? () => {} : onClose}
onBackdropPress={isNonDismissible ? () => {} : undefined}
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
innerContainerStyle={styles.pv0}
>
<View style={[styles.m5, styles.alignItemsCenter]}>{renderContent()}</View>
</Modal>
);
}

ExportDownloadStatusModal.displayName = 'ExportDownloadStatusModal';

export default ExportDownloadStatusModal;
14 changes: 14 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9282,6 +9282,20 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
exportInProgress: 'Export wird ausgeführt',
conciergeWillSend: 'Concierge wird dir die Datei in Kürze senden.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Nicht verifiziert',
retry: 'Wiederholen',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9285,6 +9285,20 @@ const translations = {
exportInProgress: 'Export in progress',
conciergeWillSend: 'Concierge will send you the file shortly.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Not verified',
retry: 'Retry',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9435,6 +9435,20 @@ ${amount} para ${merchant} - ${date}`,
exportInProgress: 'Exportación en curso',
conciergeWillSend: 'Concierge te enviará el archivo en breve.',
},
exportDownload: {
preparingTitle: 'Preparando descarga...',
preparingBody: 'Puedes esperar a que termine la descarga o Concierge puede enviártelo por chat.',
sendFromConcierge: 'Envíame el archivo cuando esté listo',
conciergeTitle: '¡Por supuesto!',
conciergeBody: 'Concierge te enviará un mensaje cuando el archivo esté listo.',
goToConcierge: 'Ir a Concierge',
dismiss: 'Descartar',
readyTitle: '¡Tu archivo está listo!',
readyBody: 'Si no se descargó automáticamente, usa el botón de abajo.',
downloadFile: 'Descargar archivo',
failedTitle: 'Exportación fallida',
close: 'Cerrar',
},
openAppFailureModal: {
title: 'Algo salió mal...',
subtitle: `No hemos podido cargar todos sus datos. Hemos sido notificados y estamos investigando el problema. Si esto persiste, por favor comuníquese con`,
Expand Down
14 changes: 14 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9311,6 +9311,20 @@ Voici un *reçu test* pour vous montrer comment ça fonctionne :`,
exportInProgress: 'Export en cours',
conciergeWillSend: 'Concierge vous enverra le fichier sous peu.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Non vérifié',
retry: 'Réessayer',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9273,6 +9273,20 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
exportInProgress: 'Esportazione in corso',
conciergeWillSend: 'Concierge ti invierà il file a breve.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Non verificato',
retry: 'Riprova',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9157,6 +9157,20 @@ ${reportName}
exportInProgress: 'エクスポート処理中',
conciergeWillSend: 'Conciergeがまもなくファイルを送信します。',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: '未確認',
retry: '再試行',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9242,6 +9242,20 @@ Hier is een *proefbon* om je te laten zien hoe het werkt:`,
exportInProgress: 'Export bezig',
conciergeWillSend: 'Concierge stuurt je het bestand zo meteen.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Niet geverifieerd',
retry: 'Opnieuw proberen',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9226,6 +9226,20 @@ Oto *paragon testowy*, żeby pokazać Ci, jak to działa:`,
exportInProgress: 'Trwa eksport',
conciergeWillSend: 'Concierge wkrótce wyśle Ci plik.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Niezweryfikowane',
retry: 'Ponów próbę',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9233,6 +9233,20 @@ Aqui está um *comprovante de teste* para mostrar como funciona:`,
exportInProgress: 'Exportação em andamento',
conciergeWillSend: 'O Concierge enviará o arquivo para você em breve.',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: 'Não verificado',
retry: 'Tentar novamente',
Expand Down
14 changes: 14 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9001,6 +9001,20 @@ ${reportName}
exportInProgress: '导出进行中',
conciergeWillSend: 'Concierge 将很快把文件发送给你。',
},
exportDownload: {
preparingTitle: 'Preparing download...',
preparingBody: 'You can either wait for the download to finish or Concierge can send it to you via chat.',
sendFromConcierge: "Send me the file when it's ready",
conciergeTitle: 'You bet!',
conciergeBody: 'Concierge will send you a message when the file is ready.',
goToConcierge: 'Go to Concierge',
dismiss: 'Dismiss',
readyTitle: 'Your file is ready!',
readyBody: "If it didn't automatically download, use the button below.",
downloadFile: 'Download file',
failedTitle: 'Export failed',
close: 'Close',
},
domain: {
notVerified: '未验证',
retry: '重试',
Expand Down
Loading
Loading