-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[NO QA] feat: introduce ExportDownloadStatusModal #91490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
2a94a51
96630e6
9342054
d4948b9
dd0d2d3
1f851b8
f78d1ce
0fd0549
ae51f63
84a9543
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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'; | ||
| 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(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❌ CONSISTENCY-5 (docs)The Add a justification comment explaining why // 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.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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]); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The auto-download effect reads 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} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we only need to call the |
||
| 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; | ||
There was a problem hiding this comment.
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
useCallBackin any new component because the component must compile with React Compiler, quote from here