diff --git a/src/components/Search/SearchPageHeader/SearchTypeMenuPopover.tsx b/src/components/Search/SearchPageHeader/SearchTypeMenuPopover.tsx index 5b74c66417174..b301997f77c33 100644 --- a/src/components/Search/SearchPageHeader/SearchTypeMenuPopover.tsx +++ b/src/components/Search/SearchPageHeader/SearchTypeMenuPopover.tsx @@ -15,7 +15,7 @@ type SearchTypeMenuNarrowProps = { function SearchTypeMenuPopover({queryJSON}: SearchTypeMenuNarrowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const {isPopoverVisible, delayPopoverMenuFirstRender, openMenu, closeMenu, allMenuItems, DeleteConfirmModal, windowHeight} = useSearchTypeMenu(queryJSON); + const {isPopoverVisible, delayPopoverMenuFirstRender, openMenu, closeMenu, allMenuItems, windowHeight} = useSearchTypeMenu(queryJSON); const buttonRef = useRef(null); const {unmodifiedPaddings} = useSafeAreaPaddings(); @@ -45,10 +45,6 @@ function SearchTypeMenuPopover({queryJSON}: SearchTypeMenuNarrowProps) { scrollContainerStyle={styles.pv0} /> )} - {/* DeleteConfirmModal is a stable JSX element returned by the hook. - Returning the element directly keeps the component identity across re-renders so React - can play its exit animation instead of removing it instantly. */} - {DeleteConfirmModal} ); } diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index b08800ec89c4c..ee0b9c3e322a5 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -7,13 +7,14 @@ import type {OnyxEntry} from 'react-native-onyx'; import Animated, {FadeIn, FadeOut, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import FullPageErrorView from '@components/BlockingViews/FullPageErrorView'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; -import ConfirmModal from '@components/ConfirmModal'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import SearchTableHeader from '@components/SelectionListWithSections/SearchTableHeader'; import type {ReportActionListItemType, SearchListItem, SelectionListHandle, TransactionGroupListItemType, TransactionListItemType} from '@components/SelectionListWithSections/types'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import {WideRHPContext} from '@components/WideRHPContextProvider'; import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet'; import useCardFeedsForDisplay from '@hooks/useCardFeedsForDisplay'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useMultipleSnapshots from '@hooks/useMultipleSnapshots'; @@ -209,17 +210,9 @@ function Search({ const prevIsOffline = usePrevious(isOffline); const {shouldUseNarrowLayout} = useResponsiveLayout(); const styles = useThemeStyles(); - const [isDEWModalVisible, setIsDEWModalVisible] = useState(false); + const {showConfirmModal} = useConfirmModal(); const {isBetaEnabled} = usePermissions(); const isDEWBetaEnabled = isBetaEnabled(CONST.BETAS.NEW_DOT_DEW); - - const handleDEWModalOpen = useCallback(() => { - if (onDEWModalOpen) { - onDEWModalOpen(); - } else { - setIsDEWModalVisible(true); - } - }, [onDEWModalOpen]); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout for enabling the selection mode on small screens only // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth, isLargeScreenWidth} = useResponsiveLayout(); @@ -280,6 +273,24 @@ function Search({ const {translate, localeCompare, formatPhoneNumber} = useLocalize(); const searchListRef = useRef(null); + const handleDEWModalOpen = useCallback(() => { + if (onDEWModalOpen) { + onDEWModalOpen(); + } else { + showConfirmModal({ + title: translate('customApprovalWorkflow.title'), + prompt: translate('customApprovalWorkflow.description'), + confirmText: translate('customApprovalWorkflow.goToExpensifyClassic'), + shouldShowCancelButton: false, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }); + } + }, [onDEWModalOpen, showConfirmModal, translate]); + const clearTransactionsAndSetHashAndKey = useCallback(() => { clearSelectedTransactions(hash); setCurrentSearchHashAndKey(hash, searchKey); @@ -1194,18 +1205,6 @@ function Search({ hasLoadedAllTransactions={hasLoadedAllTransactions} customCardNames={customCardNames} /> - { - setIsDEWModalVisible(false); - openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setIsDEWModalVisible(false)} - prompt={translate('customApprovalWorkflow.description')} - confirmText={translate('customApprovalWorkflow.goToExpensifyClassic')} - shouldShowCancelButton={false} - /> ); diff --git a/src/hooks/useDeleteSavedSearch.tsx b/src/hooks/useDeleteSavedSearch.tsx index f8c8d17537844..9bcb6fca2d517 100644 --- a/src/hooks/useDeleteSavedSearch.tsx +++ b/src/hooks/useDeleteSavedSearch.tsx @@ -1,48 +1,43 @@ -import React, {useState} from 'react'; -import ConfirmModal from '@components/ConfirmModal'; +import {useCallback} from 'react'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import {useSearchContext} from '@components/Search/SearchContext'; import {deleteSavedSearch} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import {buildCannedSearchQuery} from '@libs/SearchQueryUtils'; import ROUTES from '@src/ROUTES'; +import useConfirmModal from './useConfirmModal'; import useLocalize from './useLocalize'; export default function useDeleteSavedSearch() { - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - const [hashToDelete, setHashToDelete] = useState(0); const {translate} = useLocalize(); const {currentSearchHash} = useSearchContext(); + const {showConfirmModal} = useConfirmModal(); - const showDeleteModal = (hash: number) => { - setIsDeleteModalVisible(true); - setHashToDelete(hash); - }; + const handleDeleteSavedSearch = useCallback( + (hash: number) => { + showConfirmModal({ + title: translate('search.deleteSavedSearch'), + prompt: translate('search.deleteSavedSearchConfirm'), + confirmText: translate('common.delete'), + cancelText: translate('common.cancel'), + danger: true, + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + deleteSavedSearch(hash); - const handleDelete = () => { - deleteSavedSearch(hashToDelete); - setIsDeleteModalVisible(false); - - if (hashToDelete === currentSearchHash) { - Navigation.navigate( - ROUTES.SEARCH_ROOT.getRoute({ - query: buildCannedSearchQuery(), - }), - ); - } - }; - - const DeleteConfirmModal = ( - setIsDeleteModalVisible(false)} - isVisible={isDeleteModalVisible} - prompt={translate('search.deleteSavedSearchConfirm')} - confirmText={translate('common.delete')} - cancelText={translate('common.cancel')} - danger - /> + if (hash === currentSearchHash) { + Navigation.navigate( + ROUTES.SEARCH_ROOT.getRoute({ + query: buildCannedSearchQuery(), + }), + ); + } + }); + }, + [showConfirmModal, translate, currentSearchHash], ); - return {showDeleteModal, DeleteConfirmModal}; + return {showDeleteModal: handleDeleteSavedSearch}; } diff --git a/src/hooks/useSearchTypeMenu.tsx b/src/hooks/useSearchTypeMenu.tsx index 3eeaaf89f6e90..16039efadafcf 100644 --- a/src/hooks/useSearchTypeMenu.tsx +++ b/src/hooks/useSearchTypeMenu.tsx @@ -41,7 +41,7 @@ export default function useSearchTypeMenu(queryJSON: SearchQueryJSON) { const {translate} = useLocalize(); const {typeMenuSections, shouldShowSuggestedSearchSkeleton} = useSearchTypeMenuSections(); const {clearSelectedTransactions} = useSearchContext(); - const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); + const {showDeleteModal} = useDeleteSavedSearch(); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const personalDetails = usePersonalDetails(); const [reports = getEmptyObject>>()] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); @@ -213,7 +213,6 @@ export default function useSearchTypeMenu(queryJSON: SearchQueryJSON) { openMenu, closeMenu, allMenuItems: popoverMenuItems, - DeleteConfirmModal, theme, styles, windowHeight, diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index 87b8cb9f7d637..04b97dc986715 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -7,12 +7,12 @@ import type {GestureResponderEvent, ImageStyle, Text as RNText, TextStyle, ViewS import {Linking, View} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import BookTravelButton from '@components/BookTravelButton'; -import ConfirmModal from '@components/ConfirmModal'; import GenericEmptyStateComponent from '@components/EmptyStateComponent/GenericEmptyStateComponent'; import type {EmptyStateButton, HeaderMedia, MediaTypes} from '@components/EmptyStateComponent/types'; import type {FeatureListItem} from '@components/FeatureList'; import LottieAnimations from '@components/LottieAnimations'; import MenuItem from '@components/MenuItem'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import ScrollView from '@components/ScrollView'; import {SearchScopeProvider} from '@components/Search/SearchScopeProvider'; @@ -20,6 +20,7 @@ import type {SearchQueryJSON} from '@components/Search/types'; import SearchRowSkeleton from '@components/Skeletons/SearchRowSkeleton'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; +import useConfirmModal from '@hooks/useConfirmModal'; import useCreateEmptyReportConfirmation from '@hooks/useCreateEmptyReportConfirmation'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useIsPaidPolicyAdmin from '@hooks/useIsPaidPolicyAdmin'; @@ -166,7 +167,7 @@ function EmptySearchViewContent({ }, ]; const [contextMenuAnchor, setContextMenuAnchor] = useState(null); - const [modalVisible, setModalVisible] = useState(false); + const {showConfirmModal} = useConfirmModal(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const {isBetaEnabled} = usePermissions(); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); @@ -237,6 +238,30 @@ function EmptySearchViewContent({ } }; + const handleRedirectToExpensifyClassic = () => { + showConfirmModal({ + prompt: translate('sidebarScreen.redirectToExpensifyClassicModal.description'), + title: translate('sidebarScreen.redirectToExpensifyClassicModal.title'), + confirmText: translate('exitSurvey.goToExpensifyClassic'), + cancelText: translate('common.cancel'), + }).then((result) => { + if (result.action !== ModalActions.CONFIRM) { + return; + } + openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }); + }; + + const handleCreateMoneyRequest = (iouType: typeof CONST.IOU.TYPE.CREATE | typeof CONST.IOU.TYPE.INVOICE) => { + interceptAnonymousUser(() => { + if (shouldRedirectToExpensifyClassic) { + handleRedirectToExpensifyClassic(); + return; + } + startMoneyRequest(iouType, generateReportID()); + }); + }; + const typeMenuItems = typeMenuSections.map((section) => section.menuItems).flat(); const onLongPress = (event: GestureResponderEvent | MouseEvent) => { @@ -432,14 +457,7 @@ function EmptySearchViewContent({ : []), { buttonText: translate('iou.createExpense'), - buttonAction: () => - interceptAnonymousUser(() => { - if (shouldRedirectToExpensifyClassic) { - setModalVisible(true); - return; - } - startMoneyRequest(CONST.IOU.TYPE.CREATE, generateReportID()); - }), + buttonAction: () => handleCreateMoneyRequest(CONST.IOU.TYPE.CREATE), success: true, }, ], @@ -465,14 +483,7 @@ function EmptySearchViewContent({ : []), { buttonText: translate('workspace.invoices.sendInvoice'), - buttonAction: () => - interceptAnonymousUser(() => { - if (shouldRedirectToExpensifyClassic) { - setModalVisible(true); - return; - } - startMoneyRequest(CONST.IOU.TYPE.INVOICE, generateReportID()); - }), + buttonAction: () => handleCreateMoneyRequest(CONST.IOU.TYPE.INVOICE), success: true, }, ], @@ -526,18 +537,6 @@ function EmptySearchViewContent({ {CreateReportConfirmationModal} - { - setModalVisible(false); - openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setModalVisible(false)} - title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} - confirmText={translate('exitSurvey.goToExpensifyClassic')} - cancelText={translate('common.cancel')} - /> ); } diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index d58ad796ebada..ccb65489dfa6f 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -70,7 +70,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { 'CreditCardHourglass', 'Bank', ] as const); - const {showDeleteModal, DeleteConfirmModal} = useDeleteSavedSearch(); + const {showDeleteModal} = useDeleteSavedSearch(); const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); const personalDetails = usePersonalDetails(); const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); @@ -240,13 +240,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { {translate(section.translationPath)} {section.translationPath === 'search.savedSearchesMenuItemTitle' ? ( - <> - {renderSavedSearchesSection(savedSearchesMenuItems)} - {/* DeleteConfirmModal is a stable JSX element returned by the hook. - Returning the element directly keeps the component identity across re-renders so React - can play its exit animation instead of removing it instantly. */} - {DeleteConfirmModal} - + renderSavedSearchesSection(savedSearchesMenuItems) ) : ( <> {section.menuItems.map((item, itemIndex) => {