Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>(null);
const {unmodifiedPaddings} = useSafeAreaPaddings();
Expand Down Expand Up @@ -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}
</>
);
}
Expand Down
43 changes: 21 additions & 22 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -280,6 +273,24 @@ function Search({
const {translate, localeCompare, formatPhoneNumber} = useLocalize();
const searchListRef = useRef<SelectionListHandle | null>(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);
Expand Down Expand Up @@ -1194,18 +1205,6 @@ function Search({
hasLoadedAllTransactions={hasLoadedAllTransactions}
customCardNames={customCardNames}
/>
<ConfirmModal
title={translate('customApprovalWorkflow.title')}
isVisible={isDEWModalVisible}
onConfirm={() => {
setIsDEWModalVisible(false);
openOldDotLink(CONST.OLDDOT_URLS.INBOX);
}}
onCancel={() => setIsDEWModalVisible(false)}
prompt={translate('customApprovalWorkflow.description')}
confirmText={translate('customApprovalWorkflow.goToExpensifyClassic')}
shouldShowCancelButton={false}
/>
</Animated.View>
</SearchScopeProvider>
);
Expand Down
61 changes: 28 additions & 33 deletions src/hooks/useDeleteSavedSearch.tsx
Original file line number Diff line number Diff line change
@@ -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 = (
<ConfirmModal
title={translate('search.deleteSavedSearch')}
onConfirm={handleDelete}
onCancel={() => 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};
}
3 changes: 1 addition & 2 deletions src/hooks/useSearchTypeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<NonNullable<OnyxCollection<Report>>>()] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
Expand Down Expand Up @@ -213,7 +213,6 @@ export default function useSearchTypeMenu(queryJSON: SearchQueryJSON) {
openMenu,
closeMenu,
allMenuItems: popoverMenuItems,
DeleteConfirmModal,
theme,
styles,
windowHeight,
Expand Down
59 changes: 29 additions & 30 deletions src/pages/Search/EmptySearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ 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';
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';
Expand Down Expand Up @@ -166,7 +167,7 @@ function EmptySearchViewContent({
},
];
const [contextMenuAnchor, setContextMenuAnchor] = useState<RNText | null>(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);
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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,
},
],
Expand All @@ -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,
},
],
Expand Down Expand Up @@ -526,18 +537,6 @@ function EmptySearchViewContent({
</GenericEmptyStateComponent>
</ScrollView>
{CreateReportConfirmationModal}
<ConfirmModal
prompt={translate('sidebarScreen.redirectToExpensifyClassicModal.description')}
isVisible={modalVisible}
onConfirm={() => {
setModalVisible(false);
openOldDotLink(CONST.OLDDOT_URLS.INBOX);
}}
onCancel={() => setModalVisible(false)}
title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')}
confirmText={translate('exitSurvey.goToExpensifyClassic')}
cancelText={translate('common.cancel')}
/>
</>
);
}
Expand Down
10 changes: 2 additions & 8 deletions src/pages/Search/SearchTypeMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down Expand Up @@ -240,13 +240,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) {
<Text style={styles.sectionTitle}>{translate(section.translationPath)}</Text>

{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) => {
Expand Down
Loading