From 7145dbba7d9ccb3b604e9c5d6db795c1b45cb93f Mon Sep 17 00:00:00 2001 From: Ryan Teguh Date: Wed, 8 Apr 2026 22:51:47 +0800 Subject: [PATCH 1/5] feat: Add Concierge help button on mobile native app --- .../SidePanelButton/index.native.tsx | 50 +++++++++++++++++-- src/pages/settings/InitialSettingsPage.tsx | 12 ++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/components/SidePanel/SidePanelButton/index.native.tsx b/src/components/SidePanel/SidePanelButton/index.native.tsx index ac1a9619954e..74f193b3cdd7 100644 --- a/src/components/SidePanel/SidePanelButton/index.native.tsx +++ b/src/components/SidePanel/SidePanelButton/index.native.tsx @@ -1,6 +1,50 @@ -// Side Panel is disabled on native platforms -function SidePanelButton() { - return null; +import {hasSeenTourSelector} from '@selectors/Onboarding'; +import React from 'react'; +import Icon from '@components/Icon'; +import {PressableWithoutFeedback} from '@components/Pressable'; +import Tooltip from '@components/Tooltip'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useSidePanelState from '@hooks/useSidePanelState'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {navigateToConciergeChat} from '@userActions/Report'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SidePanelButtonProps from './types'; + +function SidePanelButton({style}: SidePanelButtonProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {shouldHideHelpButton} = useSidePanelState(); + const {accountID: currentUserAccountID = CONST.DEFAULT_NUMBER_ID} = useCurrentUserPersonalDetails(); + const {ConciergeAvatar} = useMemoizedLazyExpensifyIcons(['ConciergeAvatar']); + const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); + const [betas] = useOnyx(ONYXKEYS.BETAS); + + if (shouldHideHelpButton) { + return null; + } + + return ( + + navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas)} + > + + + + ); } export default SidePanelButton; diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 2efccddabdc0..561d8757254b 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -560,13 +560,11 @@ function InitialSettingsPage({currentUserPersonalDetails}: InitialSettingsPagePr shouldEnableKeyboardAvoidingView={false} > {shouldDisplayLHB && } - {shouldUseNarrowLayout && ( - - )} + Date: Mon, 13 Apr 2026 10:40:08 +0800 Subject: [PATCH 2/5] Hide side panel button in Concierge chat --- .../SidePanelButton/SidePanelButtonBase.tsx | 4 +++- .../SidePanel/SidePanelButton/index.native.tsx | 4 +++- src/hooks/useIsInConciergeChat.ts | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useIsInConciergeChat.ts diff --git a/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx b/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx index e595065835d3..488201921586 100644 --- a/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx +++ b/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx @@ -2,6 +2,7 @@ import React from 'react'; import Icon from '@components/Icon'; import {PressableWithoutFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; +import useIsInConciergeChat from '@hooks/useIsInConciergeChat'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useSidePanelActions from '@hooks/useSidePanelActions'; @@ -18,8 +19,9 @@ function SidePanelButtonBase({style}: SidePanelButtonProps) { const {shouldHideHelpButton} = useSidePanelState(); const {openSidePanel} = useSidePanelActions(); const {ConciergeAvatar} = useMemoizedLazyExpensifyIcons(['ConciergeAvatar']); + const isInConciergeChat = useIsInConciergeChat(); - if (shouldHideHelpButton) { + if (shouldHideHelpButton || isInConciergeChat) { return null; } diff --git a/src/components/SidePanel/SidePanelButton/index.native.tsx b/src/components/SidePanel/SidePanelButton/index.native.tsx index 74f193b3cdd7..d10229b4befc 100644 --- a/src/components/SidePanel/SidePanelButton/index.native.tsx +++ b/src/components/SidePanel/SidePanelButton/index.native.tsx @@ -4,6 +4,7 @@ import Icon from '@components/Icon'; import {PressableWithoutFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useIsInConciergeChat from '@hooks/useIsInConciergeChat'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -24,8 +25,9 @@ function SidePanelButton({style}: SidePanelButtonProps) { const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const [betas] = useOnyx(ONYXKEYS.BETAS); + const isInConciergeChat = useIsInConciergeChat(); - if (shouldHideHelpButton) { + if (shouldHideHelpButton || isInConciergeChat) { return null; } diff --git a/src/hooks/useIsInConciergeChat.ts b/src/hooks/useIsInConciergeChat.ts new file mode 100644 index 000000000000..9b3d3aafabfd --- /dev/null +++ b/src/hooks/useIsInConciergeChat.ts @@ -0,0 +1,17 @@ +import useOnyx from '@hooks/useOnyx'; +import useRootNavigationState from '@hooks/useRootNavigationState'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; + +/** + * Returns true when the topmost report in the navigation stack is the Concierge chat. + * Used to hide the help button when the user is already in Concierge chat. + */ +function useIsInConciergeChat() { + const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); + const topmostReportID = useRootNavigationState((state) => Navigation.getTopmostReportId(state)); + + return !!conciergeReportID && !!topmostReportID && conciergeReportID === topmostReportID; +} + +export default useIsInConciergeChat; From 6d2ed9ae20eaa3666c357febd97a5ad2f1e78fa7 Mon Sep 17 00:00:00 2001 From: Ryan Teguh Date: Mon, 13 Apr 2026 11:08:31 +0800 Subject: [PATCH 3/5] ESLint fix --- src/hooks/useIsInConciergeChat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useIsInConciergeChat.ts b/src/hooks/useIsInConciergeChat.ts index 9b3d3aafabfd..fd9e178d98bf 100644 --- a/src/hooks/useIsInConciergeChat.ts +++ b/src/hooks/useIsInConciergeChat.ts @@ -1,7 +1,7 @@ -import useOnyx from '@hooks/useOnyx'; -import useRootNavigationState from '@hooks/useRootNavigationState'; import Navigation from '@libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; +import useOnyx from './useOnyx'; +import useRootNavigationState from './useRootNavigationState'; /** * Returns true when the topmost report in the navigation stack is the Concierge chat. From 9225b9c44a4d78e76aeba4ce423c606191eb5bf8 Mon Sep 17 00:00:00 2001 From: Ryan Teguh Date: Mon, 13 Apr 2026 12:01:51 +0800 Subject: [PATCH 4/5] test: Update ProfilePageTest navigation mocks --- tests/ui/ProfilePageTest.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/ui/ProfilePageTest.tsx b/tests/ui/ProfilePageTest.tsx index 599677fecff4..ec909ec92031 100644 --- a/tests/ui/ProfilePageTest.tsx +++ b/tests/ui/ProfilePageTest.tsx @@ -26,6 +26,7 @@ jest.mock('@libs/Navigation/Navigation', () => ({ navigate: jest.fn(), goBack: jest.fn(), getActiveRoute: jest.fn(() => ''), + getTopmostReportId: jest.fn(() => undefined), })); jest.mock('@react-navigation/native', () => { @@ -35,6 +36,8 @@ jest.mock('@react-navigation/native', () => { useRoute: jest.fn(() => ({params: {}})), createNavigationContainerRef: () => ({ getState: () => jest.fn(), + isReady: () => false, + addListener: () => () => {}, }), usePreventRemove: jest.fn(), }; From fde1e4479842248d3ac7f65484246d0fc5f18f2c Mon Sep 17 00:00:00 2001 From: Ryan Teguh Date: Thu, 16 Apr 2026 22:33:49 +0800 Subject: [PATCH 5/5] Hide side panel button in Concierge chat --- .../SidePanelButton/SidePanelButtonBase.tsx | 4 +--- .../SidePanel/SidePanelButton/index.native.tsx | 4 +--- src/hooks/useIsInConciergeChat.ts | 17 ----------------- src/pages/inbox/HeaderView.tsx | 3 ++- tests/ui/ProfilePageTest.tsx | 3 --- 5 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 src/hooks/useIsInConciergeChat.ts diff --git a/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx b/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx index 488201921586..e595065835d3 100644 --- a/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx +++ b/src/components/SidePanel/SidePanelButton/SidePanelButtonBase.tsx @@ -2,7 +2,6 @@ import React from 'react'; import Icon from '@components/Icon'; import {PressableWithoutFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; -import useIsInConciergeChat from '@hooks/useIsInConciergeChat'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useSidePanelActions from '@hooks/useSidePanelActions'; @@ -19,9 +18,8 @@ function SidePanelButtonBase({style}: SidePanelButtonProps) { const {shouldHideHelpButton} = useSidePanelState(); const {openSidePanel} = useSidePanelActions(); const {ConciergeAvatar} = useMemoizedLazyExpensifyIcons(['ConciergeAvatar']); - const isInConciergeChat = useIsInConciergeChat(); - if (shouldHideHelpButton || isInConciergeChat) { + if (shouldHideHelpButton) { return null; } diff --git a/src/components/SidePanel/SidePanelButton/index.native.tsx b/src/components/SidePanel/SidePanelButton/index.native.tsx index d10229b4befc..74f193b3cdd7 100644 --- a/src/components/SidePanel/SidePanelButton/index.native.tsx +++ b/src/components/SidePanel/SidePanelButton/index.native.tsx @@ -4,7 +4,6 @@ import Icon from '@components/Icon'; import {PressableWithoutFeedback} from '@components/Pressable'; import Tooltip from '@components/Tooltip'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import useIsInConciergeChat from '@hooks/useIsInConciergeChat'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -25,9 +24,8 @@ function SidePanelButton({style}: SidePanelButtonProps) { const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const [betas] = useOnyx(ONYXKEYS.BETAS); - const isInConciergeChat = useIsInConciergeChat(); - if (shouldHideHelpButton || isInConciergeChat) { + if (shouldHideHelpButton) { return null; } diff --git a/src/hooks/useIsInConciergeChat.ts b/src/hooks/useIsInConciergeChat.ts deleted file mode 100644 index fd9e178d98bf..000000000000 --- a/src/hooks/useIsInConciergeChat.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import useOnyx from './useOnyx'; -import useRootNavigationState from './useRootNavigationState'; - -/** - * Returns true when the topmost report in the navigation stack is the Concierge chat. - * Used to hide the help button when the user is already in Concierge chat. - */ -function useIsInConciergeChat() { - const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); - const topmostReportID = useRootNavigationState((state) => Navigation.getTopmostReportId(state)); - - return !!conciergeReportID && !!topmostReportID && conciergeReportID === topmostReportID; -} - -export default useIsInConciergeChat; diff --git a/src/pages/inbox/HeaderView.tsx b/src/pages/inbox/HeaderView.tsx index d2d822ac3cd4..e780fe6807e9 100644 --- a/src/pages/inbox/HeaderView.tsx +++ b/src/pages/inbox/HeaderView.tsx @@ -125,6 +125,7 @@ function HeaderView({onNavigationMenuButtonClicked, reportID}: HeaderViewProps) const styles = useThemeStyles(); const isSelfDM = isSelfDMReportUtils(report); const isGroupChat = isGroupChatReportUtils(report) || isDeprecatedGroupDM(report, isReportArchived); + const isConciergeChat = isConciergeChatReport(report, conciergeReportID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const allParticipants = getParticipantsAccountIDsForDisplay(report, false, true, undefined, reportMetadata); @@ -408,7 +409,7 @@ function HeaderView({onNavigationMenuButtonClicked, reportID}: HeaderViewProps) )} {shouldDisplaySearchRouter && } - {!isInSidePanel && } + {!isInSidePanel && !isConciergeChat && } )} diff --git a/tests/ui/ProfilePageTest.tsx b/tests/ui/ProfilePageTest.tsx index ec909ec92031..599677fecff4 100644 --- a/tests/ui/ProfilePageTest.tsx +++ b/tests/ui/ProfilePageTest.tsx @@ -26,7 +26,6 @@ jest.mock('@libs/Navigation/Navigation', () => ({ navigate: jest.fn(), goBack: jest.fn(), getActiveRoute: jest.fn(() => ''), - getTopmostReportId: jest.fn(() => undefined), })); jest.mock('@react-navigation/native', () => { @@ -36,8 +35,6 @@ jest.mock('@react-navigation/native', () => { useRoute: jest.fn(() => ({params: {}})), createNavigationContainerRef: () => ({ getState: () => jest.fn(), - isReady: () => false, - addListener: () => () => {}, }), usePreventRemove: jest.fn(), };