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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7974,6 +7974,7 @@ const CONST = {
AUTOCOMPLETE_SUGGESTION: 'autocompleteSuggestion',
SEARCH: 'searchItem',
FIND_ITEM: 'findItem',
ASK_CONCIERGE: 'askConcierge',
},
SEARCH_USER_FRIENDLY_KEYS: {
TYPE: 'type',
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,9 @@ const ONYXKEYS = {
/** Company cards custom names */
NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames',

/** Whether to kick off the "Concierge is thinking" indicator when AgentZeroStatusGate mounts */
CONCIERGE_THINKING_KICKOFF: 'conciergeThinkingKickoff',

/** The user's Concierge reportID */
CONCIERGE_REPORT_ID: 'conciergeReportID',

Expand Down Expand Up @@ -1516,6 +1519,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.LAST_ROUTE]: string;
[ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean;
[ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record<string, string>;
[ONYXKEYS.CONCIERGE_THINKING_KICKOFF]: boolean;
[ONYXKEYS.CONCIERGE_REPORT_ID]: string;
[ONYXKEYS.SELF_DM_REPORT_ID]: string;
[ONYXKEYS.SHARE_UNKNOWN_USER_DETAILS]: Participant;
Expand Down
12 changes: 6 additions & 6 deletions src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ type SearchAutocompleteListProps = {
/** Callback to trigger search action * */
handleSearch: (value: string) => void;

/** An optional item to always display on the top of the router list */
searchQueryItem?: SearchQueryItem;
/** Optional items to always display at the top of the router list */
searchQueryItems?: SearchQueryItem[];

/** Any extra sections that should be displayed in the router list. */
getAdditionalSections?: GetAdditionalSectionsCallback;
Expand Down Expand Up @@ -131,7 +131,7 @@ function SearchRouterItem(props: UserListItemProps<AutocompleteListItem> | Searc
function SearchAutocompleteList({
autocompleteQueryValue,
handleSearch,
searchQueryItem,
searchQueryItems,
getAdditionalSections,
onListItemPress,
shouldSubscribeToArrowKeyEvents = true,
Expand Down Expand Up @@ -402,8 +402,8 @@ function SearchAutocompleteList({
nextSuggestionsCount += section.data.filter((item) => item.keyForList !== CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM).length;
};

if (searchQueryItem) {
pushSection({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++});
if (searchQueryItems && searchQueryItems.length > 0) {
pushSection({data: searchQueryItems as AutocompleteListItem[], sectionIndex: sectionIndex++});
}

const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex);
Expand Down Expand Up @@ -493,7 +493,7 @@ function SearchAutocompleteList({
recentReportsOptions,
recentSearchesData,
searchOptions,
searchQueryItem,
searchQueryItems,
styles,
translate,
isLoadingOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type IconAsset from '@src/types/utils/IconAsset';

type SearchQueryItem = ListItem & {
singleIcon?: IconAsset;
/** Whether to apply the theme fill color to the icon. Set to false for multi-colored icons like avatars. Defaults to true. */
shouldIconApplyFill?: boolean;
searchItemType?: ValueOf<typeof CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE>;
searchQuery?: string;
autocompleteID?: string;
Expand Down Expand Up @@ -56,7 +58,7 @@ function SearchQueryListItem({item, isFocused, showTooltip, onSelectRow, onFocus
{!!item.singleIcon && (
<Icon
src={item.singleIcon}
fill={theme.icon}
fill={item.shouldIconApplyFill !== false ? theme.icon : undefined}
additionalStyles={styles.mr3}
medium
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ function SearchPageInputNarrow({queryJSON, searchRouterListVisible, hideSearchRo
const {
autocompleteSubstitutions,
autocompleteQueryValue,
searchQueryItem,
searchQueryItems,
selection,
textInputRef,
textInputValue,
Expand Down Expand Up @@ -80,7 +80,7 @@ function SearchPageInputNarrow({queryJSON, searchRouterListVisible, hideSearchRo
<SearchAutocompleteList
autocompleteQueryValue={autocompleteQueryValue}
handleSearch={handleSearchAction}
searchQueryItem={searchQueryItem}
searchQueryItems={searchQueryItems}
onListItemPress={onListItemPress}
textInputRef={textInputRef}
autocompleteSubstitutions={autocompleteSubstitutions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function SearchPageInputWide({queryJSON, handleSearch}: SearchPageInputWideProps
const {
autocompleteSubstitutions,
autocompleteQueryValue,
searchQueryItem,
searchQueryItems,
selection,
textInputRef,
textInputValue,
Expand Down Expand Up @@ -97,7 +97,7 @@ function SearchPageInputWide({queryJSON, handleSearch}: SearchPageInputWideProps
<SearchAutocompleteList
autocompleteQueryValue={autocompleteQueryValue}
handleSearch={handleSearchAction}
searchQueryItem={searchQueryItem}
searchQueryItems={searchQueryItems}
onListItemPress={onListItemPress}
ref={listRef}
shouldSubscribeToArrowKeyEvents={isAutocompleteListVisible}
Expand Down
22 changes: 12 additions & 10 deletions src/components/Search/SearchPageHeader/useSearchPageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,17 @@ function useSearchPageInput({queryJSON, onSearch, onSubmit}: UseSearchPageInputP
}
}

const searchQueryItem = textInputValue
? {
text: textInputValue,
singleIcon: expensifyIcons.MagnifyingGlass,
searchQuery: textInputValue,
itemStyle: styles.activeComponentBG,
keyForList: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM,
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH,
}
const searchQueryItems = textInputValue
? [
{
text: textInputValue,
singleIcon: expensifyIcons.MagnifyingGlass,
searchQuery: textInputValue,
itemStyle: styles.activeComponentBG,
keyForList: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM,
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH,
},
]
: undefined;

return {
Expand All @@ -216,7 +218,7 @@ function useSearchPageInput({queryJSON, onSearch, onSubmit}: UseSearchPageInputP
personalAndWorkspaceCards,
personalDetails,
reports,
searchQueryItem,
searchQueryItems,
selection,
textInputRef,
textInputValue,
Expand Down
44 changes: 32 additions & 12 deletions src/components/Search/SearchRouter/SearchRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {getQueryWithSubstitutions} from './getQueryWithSubstitutions';
import {getUpdatedSubstitutionsMap} from './getUpdatedSubstitutionsMap';
import {getContextualReportData, getContextualSearchAutocompleteKey, getContextualSearchQuery} from './SearchRouterUtils';
import updateAutocompleteSubstitutionsForSelection from './updateAutocompleteSubstitutionsForSelection';
import useAskConcierge from './useAskConcierge';

const privateIsArchivedSelector = (nvp: {private_isArchived?: string} | undefined): boolean | undefined => !!nvp?.private_isArchived;

Expand All @@ -72,7 +73,8 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
const personalDetails = usePersonalDetails();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const listRef = useRef<SelectionListWithSectionsHandle>(null);
const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']);
const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass', 'ConciergeAvatar']);
const askConcierge = useAskConcierge();

// The actual input text that the user sees
const [textInputValue, , setTextInputValue] = useDebouncedState('', 500);
Expand Down Expand Up @@ -198,15 +200,26 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
],
);

const searchQueryItem = textInputValue
? {
text: textInputValue,
singleIcon: expensifyIcons.MagnifyingGlass,
searchQuery: textInputValue,
itemStyle: styles.activeComponentBG,
keyForList: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM,
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH,
}
const searchQueryItems = textInputValue
? [
{
text: textInputValue,
singleIcon: expensifyIcons.MagnifyingGlass,
searchQuery: textInputValue,
itemStyle: styles.activeComponentBG,
keyForList: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM,
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.SEARCH,
},
{
text: translate('search.askConcierge', textInputValue),
singleIcon: expensifyIcons.ConciergeAvatar,
shouldIconApplyFill: false,
searchQuery: textInputValue,
itemStyle: styles.activeComponentBG,
keyForList: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.ASK_CONCIERGE,
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.ASK_CONCIERGE,
},
]
: undefined;

const shouldScrollRef = useRef(false);
Expand Down Expand Up @@ -308,6 +321,12 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
setAutocompleteSubstitutions,
});
setFocusAndScrollToRight();
} else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.ASK_CONCIERGE) {
const {searchQuery} = item;
backHistory(() => {
askConcierge(searchQuery);
});
onRouterClose();
} else {
submitSearch(item.searchQuery, item.keyForList !== CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM);
}
Expand Down Expand Up @@ -335,13 +354,14 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
betas,
contextualPoliciesMap,
contextualReportsMap,
askConcierge,
],
);

useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, () => {
onRouterClose();
});
const updateAndScrollToFocusedIndex = useCallback(() => listRef.current?.updateAndScrollToFocusedIndex(1, true), []);
const updateAndScrollToFocusedIndex = useCallback(() => listRef.current?.updateAndScrollToFocusedIndex(searchQueryItems?.length ?? 1, true), [searchQueryItems?.length]);

const modalWidth = shouldUseNarrowLayout ? styles.w100 : {width: variables.searchRouterPopoverWidth};

Expand Down Expand Up @@ -387,7 +407,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret, isSearchRouterDispla
<DeferredAutocompleteList
autocompleteQueryValue={autocompleteQueryValue || textInputValue}
handleSearch={searchInServer}
searchQueryItem={searchQueryItem}
searchQueryItems={searchQueryItems}
getAdditionalSections={getAdditionalSections}
onListItemPress={onListItemPress}
onHighlightFirstItem={updateAndScrollToFocusedIndex}
Expand Down
44 changes: 44 additions & 0 deletions src/components/Search/SearchRouter/useAskConcierge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDelegateAccountID from '@hooks/useDelegateAccountID';
import useOnyx from '@hooks/useOnyx';
import useOpenConciergeAnywhere from '@hooks/useOpenConciergeAnywhere';
import useSidePanelReportID from '@hooks/useSidePanelReportID';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {addComment, setConciergeThinkingKickoff} from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

/**
* Returns a callback that opens the side panel (or Concierge chat on native)
* and sends the provided search query as a message.
*/
function useAskConcierge() {
Comment thread
mhawryluk marked this conversation as resolved.
const sidePanelReportID = useSidePanelReportID();
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const {openConciergeAnywhere, isInSidePanel} = useOpenConciergeAnywhere();
const targetReportID = (isInSidePanel ? sidePanelReportID : undefined) ?? conciergeReportID;
const [targetReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(targetReportID)}`);
const {timezone, accountID: currentUserAccountID} = useCurrentUserPersonalDetails();
const delegateAccountID = useDelegateAccountID();

return (searchQuery: string) => {
openConciergeAnywhere();
if (!targetReport || !targetReportID) {
return;
}
setConciergeThinkingKickoff();
addComment({
report: targetReport,
notifyReportID: targetReportID,
ancestors: [],
text: searchQuery,
timezoneParam: timezone ?? CONST.DEFAULT_TIME_ZONE,
currentUserAccountID,
shouldPlaySound: true,
isInSidePanel,
delegateAccountID,
});
};
}

export default useAskConcierge;
11 changes: 9 additions & 2 deletions src/components/SidePanel/SidePanelContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ function SidePanelContextProvider({children}: PropsWithChildren) {

if (prevShouldHideSidePanel !== shouldHideSidePanel) {
setPrevShouldHideSidePanel(shouldHideSidePanel);
if (!shouldHideSidePanel) {
if (shouldHideSidePanel) {
setSessionStartTime(null);
} else if (!sessionStartTime) {
setSessionStartTime(DateUtils.getDBTime());
}
}
Expand Down Expand Up @@ -135,6 +137,11 @@ function SidePanelContextProvider({children}: PropsWithChildren) {
focusComposerWithDelay(ReportActionComposeFocusManager.composerRef.current, CONST.SIDE_PANEL_ANIMATED_TRANSITION + CONST.COMPOSER_FOCUS_DELAY)(true);
};

const openSidePanel = () => {
setSessionStartTime(DateUtils.getDBTime());
SidePanelActions.openSidePanel(!isExtraLargeScreenWidth);
};

// Because of the React Compiler we don't need to memoize it manually
// eslint-disable-next-line react/jsx-no-constructed-context-values
const stateValue = {
Expand All @@ -153,7 +160,7 @@ function SidePanelContextProvider({children}: PropsWithChildren) {
// Because of the React Compiler we don't need to memoize it manually
// eslint-disable-next-line react/jsx-no-constructed-context-values
const actionsValue = {
openSidePanel: () => SidePanelActions.openSidePanel(!isExtraLargeScreenWidth),
openSidePanel,
closeSidePanel,
};

Expand Down
25 changes: 25 additions & 0 deletions src/hooks/useOpenConciergeAnywhere/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {hasSeenTourSelector} from '@selectors/Onboarding';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useOnyx from '@hooks/useOnyx';
import {navigateToConciergeChat} from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';

/**
* Returns a callback that navigates to the Concierge chat on native (opens the side panel on web instead),
* and a flag indicating that the concierge is not opened in the side panel.
*/
function useOpenConciergeAnywhere() {
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 {accountID: currentUserAccountID} = useCurrentUserPersonalDetails();

const openConciergeAnywhere = () => {
navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas);
};

return {openConciergeAnywhere, isInSidePanel: false};
}

export default useOpenConciergeAnywhere;
22 changes: 22 additions & 0 deletions src/hooks/useOpenConciergeAnywhere/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import useSidePanelActions from '@hooks/useSidePanelActions';
import useSidePanelState from '@hooks/useSidePanelState';

/**
* Returns a callback that opens the Concierge side panel on web (opens the Concierge chat on native instead),
* and a flag indicating that the concierge is opened in the side panel.
*/
function useOpenConciergeAnywhere() {
const {shouldHideSidePanel} = useSidePanelState();
const {openSidePanel} = useSidePanelActions();

const openConciergeAnywhere = () => {
if (!shouldHideSidePanel) {
return;
}
openSidePanel();
};

return {openConciergeAnywhere, isInSidePanel: true};
}

export default useOpenConciergeAnywhere;
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7786,6 +7786,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
recentSearches: 'Letzte Suchen',
recentChats: 'Neueste Chats',
searchIn: 'Suchen in',
askConcierge: (message: string) => `Frage Concierge „${message}“`,
searchPlaceholder: 'Nach etwas suchen...',
suggestions: 'Vorschläge',
suggestionsAvailable: (
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7793,6 +7793,7 @@ const translations = {
recentSearches: 'Recent searches',
recentChats: 'Recent chats',
searchIn: 'Search in',
askConcierge: (message: string) => `Ask Concierge “${message}”`,
searchPlaceholder: 'Search for something...',
suggestions: 'Suggestions',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7638,6 +7638,7 @@ ${amount} para ${merchant} - ${date}`,
recentSearches: 'Búsquedas recientes',
recentChats: 'Chats recientes',
searchIn: 'Buscar en',
askConcierge: (message: string) => `Pregunta a Concierge “${message}”`,
searchPlaceholder: 'Busca algo...',
suggestions: 'Sugerencias',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
Expand Down
Loading
Loading