diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7fcb675dc191..cb8bf2fdb5d3 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -849,7 +849,7 @@ type OnyxValuesMapping = { // ONYXKEYS.NVP_TRYNEWDOT is HybridApp onboarding data [ONYXKEYS.NVP_TRYNEWDOT]: OnyxTypes.TryNewDot; - [ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch[]; + [ONYXKEYS.SAVED_SEARCHES]: OnyxTypes.SaveSearch; [ONYXKEYS.RECENTLY_USED_CURRENCIES]: string[]; [ONYXKEYS.ACTIVE_CLIENTS]: string[]; [ONYXKEYS.DEVICE_ID]: string; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 3b074bf772e6..e3a04903f5ca 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -14,6 +14,7 @@ import * as Browser from '@libs/Browser'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; +import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import type AnchorAlignment from '@src/types/utils/AnchorAlignment'; import FocusableMenuItem from './FocusableMenuItem'; import FocusTrapForModal from './FocusTrap/FocusTrapForModal'; @@ -21,6 +22,7 @@ import * as Expensicons from './Icon/Expensicons'; import type {MenuItemProps} from './MenuItem'; import MenuItem from './MenuItem'; import type BaseModalProps from './Modal/types'; +import OfflineWithFeedback from './OfflineWithFeedback'; import PopoverWithMeasuredContent from './PopoverWithMeasuredContent'; import ScrollView from './ScrollView'; import Text from './Text'; @@ -48,6 +50,8 @@ type PopoverMenuItem = MenuItemProps & { /** Whether to close all modals */ shouldCloseAllModals?: boolean; + + pendingAction?: PendingAction; }; type PopoverModalProps = Pick; @@ -262,49 +266,53 @@ function PopoverMenu({ {renderHeaderText()} {enteredSubMenuIndexes.length > 0 && renderBackButtonItem()} {currentMenuItems.map((item, menuIndex) => ( - selectItem(menuIndex)} - focused={focusedIndex === menuIndex} - displayInDefaultIconColor={item.displayInDefaultIconColor} - shouldShowRightIcon={item.shouldShowRightIcon} - shouldShowRightComponent={item.shouldShowRightComponent} - iconRight={item.iconRight} - rightComponent={item.rightComponent} - shouldPutLeftPaddingWhenNoIcon={item.shouldPutLeftPaddingWhenNoIcon} - label={item.label} - style={{backgroundColor: item.isSelected ? theme.activeComponentBG : undefined}} - isLabelHoverable={item.isLabelHoverable} - floatRightAvatars={item.floatRightAvatars} - floatRightAvatarSize={item.floatRightAvatarSize} - shouldShowSubscriptRightAvatar={item.shouldShowSubscriptRightAvatar} - disabled={item.disabled} - onFocus={() => setFocusedIndex(menuIndex)} - success={item.success} - containerStyle={item.containerStyle} - shouldRenderTooltip={item.shouldRenderTooltip} - tooltipAnchorAlignment={item.tooltipAnchorAlignment} - tooltipShiftHorizontal={item.tooltipShiftHorizontal} - tooltipShiftVertical={item.tooltipShiftVertical} - tooltipWrapperStyle={item.tooltipWrapperStyle} - renderTooltipContent={item.renderTooltipContent} - numberOfLinesTitle={item.numberOfLinesTitle} - interactive={item.interactive} - isSelected={item.isSelected} - badgeText={item.badgeText} - /> + pendingAction={item.pendingAction} + > + selectItem(menuIndex)} + focused={focusedIndex === menuIndex} + displayInDefaultIconColor={item.displayInDefaultIconColor} + shouldShowRightIcon={item.shouldShowRightIcon} + shouldShowRightComponent={item.shouldShowRightComponent} + iconRight={item.iconRight} + rightComponent={item.rightComponent} + shouldPutLeftPaddingWhenNoIcon={item.shouldPutLeftPaddingWhenNoIcon} + label={item.label} + style={{backgroundColor: item.isSelected ? theme.activeComponentBG : undefined}} + isLabelHoverable={item.isLabelHoverable} + floatRightAvatars={item.floatRightAvatars} + floatRightAvatarSize={item.floatRightAvatarSize} + shouldShowSubscriptRightAvatar={item.shouldShowSubscriptRightAvatar} + disabled={item.disabled} + onFocus={() => setFocusedIndex(menuIndex)} + success={item.success} + containerStyle={item.containerStyle} + shouldRenderTooltip={item.shouldRenderTooltip} + tooltipAnchorAlignment={item.tooltipAnchorAlignment} + tooltipShiftHorizontal={item.tooltipShiftHorizontal} + tooltipShiftVertical={item.tooltipShiftVertical} + tooltipWrapperStyle={item.tooltipWrapperStyle} + renderTooltipContent={item.renderTooltipContent} + numberOfLinesTitle={item.numberOfLinesTitle} + interactive={item.interactive} + isSelected={item.isSelected} + badgeText={item.badgeText} + /> + ))} diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 722e88808033..0f89232dc3cf 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -55,11 +55,78 @@ function saveSearch({queryJSON, newName}: {queryJSON: SearchQueryJSON; newName?: const saveSearchName = newName ?? queryJSON?.inputQuery ?? ''; const jsonQuery = JSON.stringify(queryJSON); - API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, newName: saveSearchName}); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [queryJSON.hash]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + name: saveSearchName, + query: queryJSON.inputQuery, + }, + }, + }, + ]; + + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [queryJSON.hash]: null, + }, + }, + ]; + + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [queryJSON.hash]: { + pendingAction: null, + }, + }, + }, + ]; + API.write(WRITE_COMMANDS.SAVE_SEARCH, {jsonQuery, newName: saveSearchName}, {optimisticData, failureData, successData}); } function deleteSavedSearch(hash: number) { - API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash}); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [hash]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + }, + ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [hash]: null, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.SAVED_SEARCHES}`, + value: { + [hash]: { + pendingAction: null, + }, + }, + }, + ]; + + API.write(WRITE_COMMANDS.DELETE_SAVED_SEARCH, {hash}, {optimisticData, failureData, successData}); } function search({queryJSON, offset}: {queryJSON: SearchQueryJSON; offset?: number}) { diff --git a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx index fdb06828901e..bd7a94bc1840 100644 --- a/src/pages/Search/SavedSearchItemThreeDotMenu.tsx +++ b/src/pages/Search/SavedSearchItemThreeDotMenu.tsx @@ -2,18 +2,23 @@ import React, {useRef, useState} from 'react'; import {View} from 'react-native'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; type SavedSearchItemThreeDotMenuProps = { menuItems: PopoverMenuItem[]; + isDisabledItem: boolean; }; -function SavedSearchItemThreeDotMenu({menuItems}: SavedSearchItemThreeDotMenuProps) { +function SavedSearchItemThreeDotMenu({menuItems, isDisabledItem}: SavedSearchItemThreeDotMenuProps) { const threeDotsMenuContainerRef = useRef(null); const [threeDotsMenuPosition, setThreeDotsMenuPosition] = useState({horizontal: 0, vertical: 0}); - + const styles = useThemeStyles(); return ( - + { diff --git a/src/pages/Search/SavedSearchRenamePage.tsx b/src/pages/Search/SavedSearchRenamePage.tsx index 9e4d6122ea4d..d2643591ebbf 100644 --- a/src/pages/Search/SavedSearchRenamePage.tsx +++ b/src/pages/Search/SavedSearchRenamePage.tsx @@ -57,6 +57,7 @@ function SavedSearchRenamePage({route}: {route: {params: {q: string; name: strin submitButtonText={translate('common.save')} onSubmit={onSaveSearch} style={[styles.mh5, styles.flex1]} + enabledWhenOffline > , + rightComponent: ( + + ), styles: [styles.alignItemsCenter], + pendingAction: item.pendingAction, + disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }; if (!isNarrow) { @@ -179,7 +185,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { if (!savedSearches) { return []; } - return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item as SaveSearchItem, key, shouldUseNarrowLayout, index)); + return Object.entries(savedSearches).map(([key, item], index) => createSavedSearchMenuItem(item, key, shouldUseNarrowLayout, index)); }; const renderSavedSearchesSection = useCallback( diff --git a/src/pages/Search/SearchTypeMenuNarrow.tsx b/src/pages/Search/SearchTypeMenuNarrow.tsx index 198f40ca9e44..c05d8f69a404 100644 --- a/src/pages/Search/SearchTypeMenuNarrow.tsx +++ b/src/pages/Search/SearchTypeMenuNarrow.tsx @@ -3,7 +3,7 @@ import {Animated, View} from 'react-native'; import type {TextStyle, ViewStyle} from 'react-native'; import Button from '@components/Button'; import Icon from '@components/Icon'; -import type {MenuItemBaseProps} from '@components/MenuItem'; +import type {MenuItemWithLink} from '@components/MenuItemList'; import PopoverMenu from '@components/PopoverMenu'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -26,7 +26,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {SearchTypeMenuItem} from './SearchTypeMenu'; -type SavedSearchMenuItem = MenuItemBaseProps & { +type SavedSearchMenuItem = MenuItemWithLink & { key: string; hash: string; query: string; @@ -120,11 +120,13 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }} + disabled={item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE} /> ), isSelected: currentSavedSearch?.hash === item.hash, + pendingAction: item.pendingAction, + disabled: item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, })); - const allMenuItems = []; allMenuItems.push(...popoverMenuItems); @@ -136,7 +138,6 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, }); allMenuItems.push(...savedSearchItems); } - return ( ; /** * Model of saved searches */ -type SaveSearch = Record; +type SaveSearch = Record; export type {SaveSearch, SaveSearchItem};