From f1b7e3bc55cd87b6aa7d556e64d3691abe7f184d Mon Sep 17 00:00:00 2001 From: Rajat Date: Thu, 25 Sep 2025 13:25:46 +0530 Subject: [PATCH 01/13] Revert "Revert "Trigger whisper when user invites a member to the chat."" --- src/libs/actions/Report.ts | 8 ++++++++ src/pages/RoomInvitePage.tsx | 14 ++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index b62a55b7ce3d6..fb512baacd81e 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3890,6 +3890,13 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails API.write(WRITE_COMMANDS.INVITE_TO_ROOM, parameters, {optimisticData, successData, failureData}); } +/** Invites people to a room via concierge whisper */ +function inviteToRoomAction(reportID: string, inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, timezoneParam: Timezone) { + const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs); + + addComment(reportID, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false); +} + function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { const reportMetadata = getReportMetadata(reportID); Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { @@ -6143,6 +6150,7 @@ export { inviteToGroupChat, buildInviteToRoomOnyxData, inviteToRoom, + inviteToRoomAction, joinRoom, leaveGroupChat, leaveRoom, diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 738990d0bd8e9..d699a82d56b65 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -12,12 +12,13 @@ import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem import type {Section} from '@components/SelectionList/types'; import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; -import {inviteToRoom, searchInServer} from '@libs/actions/Report'; +import {inviteToRoomAction, searchInServer} from '@libs/actions/Report'; import {clearUserSearchPhrase, updateUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase'; import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -55,13 +56,14 @@ function RoomInvitePage({ }, }: RoomInvitePageProps) { const styles = useThemeStyles(); - const {translate, formatPhoneNumber} = useLocalize(); + const {translate} = useLocalize(); const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE, {canBeMissing: true}); const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(userSearchPhrase ?? ''); const [selectedOptions, setSelectedOptions] = useState([]); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const isReportArchived = useReportIsArchived(report.reportID); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const {options, areOptionsInitialized} = useOptionsList(); @@ -207,11 +209,11 @@ function RoomInvitePage({ invitedEmailsToAccountIDs[login] = Number(accountID); }); if (reportID) { - inviteToRoom(reportID, invitedEmailsToAccountIDs, formatPhoneNumber); + inviteToRoomAction(reportID, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE); + clearUserSearchPhrase(); + Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID, backTo)); } - clearUserSearchPhrase(); - Navigation.goBack(backRoute); - }, [selectedOptions, backRoute, reportID, validate, formatPhoneNumber]); + }, [validate, selectedOptions, reportID, currentUserPersonalDetails.timezone, backTo]); const goBack = useCallback(() => { Navigation.goBack(backRoute); From 083e106a9cacfa9c29920b42322042b5e52acc18 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Tue, 21 Oct 2025 05:29:55 +0530 Subject: [PATCH 02/13] Add lib to find the topmost active report in history to navigate back to --- .../helpers/getFirstReportRouteOnBack.ts | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts diff --git a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts new file mode 100644 index 0000000000000..b4f0349230058 --- /dev/null +++ b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts @@ -0,0 +1,124 @@ +import type {NavigationState} from '@react-navigation/native'; +import {navigationRef} from '@libs/Navigation/Navigation'; +import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; +import ROUTES, {Route} from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; +import {PlatformStackRouteProp} from '../PlatformStackNavigation/types'; + +const ReportScreens = { + [SCREENS.REPORT]: ROUTES.REPORT_WITH_ID, + [SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT, + [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: ROUTES.SEARCH_MONEY_REQUEST_REPORT, +} as const; + +const REPORT_SCREEN_NAMES = new Set(Object.keys(ReportScreens)); + +type ReportNavigationState = NavigationState; + +type ReportRoute = + | PlatformStackRouteProp + | PlatformStackRouteProp + | PlatformStackRouteProp; + +/** + * Searches a specific stack state (like ReportsSplitNavigator's state) for the + * most recently navigated report screen, preferring the 'history' for accuracy. + * + * @param stackState The internal state object of a stack navigator. + * @returns The most recent report route in this stack, or null. + */ +function findDeepestReportInSplitNavigator(stackState: ReportNavigationState): ReportRoute | null { + if (!stackState || !stackState.routes || stackState.routes.length === 0) { + return null; + } + + // Use the history array for chronological order, falling back to current routes if history is absent + const keysToSearch = stackState.history || stackState.routes.map((r) => r.key); + + const routeMap: Record = stackState.routes.reduce( + (map, route) => { + map[route.key] = route; + return map; + }, + {} as Record, + ); + + // Iterate the history/keys from the most recent (last element) backward (LIFO) + for (let i = keysToSearch.length - 1; i >= 0; i--) { + const routeKey = keysToSearch[i]; + const route = routeMap[routeKey as string]; + + if (!route) { + continue; // Skip keys that are no longer in the active routes array + } + + if (REPORT_SCREEN_NAMES.has(route.name)) { + return route; + } + } + + return null; +} + +/** + * Recursively searches a navigation state object to find the first report screen + * encountered when navigating backward (most recent report screen). + * + * @param navigationState The navigation state object (top-level or nested). + * @returns The route object of the most recent report screen found, or null. + */ +function findFirstReportScreenOnBack(navigationState = navigationRef.getRootState()): ReportRoute | null { + if (!navigationState || !navigationState.routes || navigationState.routes.length === 0) { + return null; + } + + const {routes} = navigationState; + + // Iterate over the current routes array in REVERSE (LIFO) order. + // This prioritizes the most active navigator (e.g., RightModalNavigator, then SearchFullscreenNavigator, then ReportsSplitNavigator). + for (let i = routes.length - 1; i >= 0; i--) { + const route = routes[i]; + + // 1. Handle primary navigators (like ReportsSplitNavigator) + if (route.name === 'ReportsSplitNavigator' && route.state) { + const reportInSplit = findDeepestReportInSplitNavigator(route.state as ReportNavigationState); + if (reportInSplit) { + return reportInSplit; + } + } + + // 2. Handle nested navigators (like modals/fullscreens) + if (route.state) { + const nestedReport = findFirstReportScreenOnBack(route.state as ReportNavigationState); + if (nestedReport) { + return nestedReport; + } + } + + if (REPORT_SCREEN_NAMES.has(route.name)) { + return route as ReportRoute; + } + } + + return null; +} + +function getFirstReportRouteOnBack(): Route | undefined { + const route = findFirstReportScreenOnBack(); + if (!route) { + return; + } + switch (route.name) { + case SCREENS.REPORT: + const {reportID, reportActionID, referrer, backTo} = route.params; + return ReportScreens[SCREENS.REPORT].getRoute(reportID, reportActionID, referrer, backTo); + case SCREENS.SEARCH.REPORT_RHP: + return ReportScreens[SCREENS.SEARCH.REPORT_RHP].getRoute(route.params); + case SCREENS.SEARCH.MONEY_REQUEST_REPORT: + return ReportScreens[SCREENS.SEARCH.MONEY_REQUEST_REPORT].getRoute(route.params); + default: + return; + } +} + +export default getFirstReportRouteOnBack; From af40dae588447f6d2fdd7d72d0a6aab056ef469a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Tue, 21 Oct 2025 05:30:06 +0530 Subject: [PATCH 03/13] navigate to the correct report --- src/pages/RoomInvitePage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 9c7b699127986..275c2556d44c3 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -24,6 +24,7 @@ import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import HttpUtils from '@libs/HttpUtils'; import {appendCountryCodeWithCountryCode} from '@libs/LoginUtils'; +import getFirstReportRouteOnBack from '@libs/Navigation/helpers/getFirstReportRouteOnBack'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types'; @@ -211,7 +212,7 @@ function RoomInvitePage({ if (reportID) { inviteToRoomAction(reportID, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE); clearUserSearchPhrase(); - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID, backTo)); + Navigation.goBack(getFirstReportRouteOnBack()); } }, [validate, selectedOptions, reportID, currentUserPersonalDetails.timezone, backTo]); From 027ed9569a8283b2065dce2b978a58aff40cd84b Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Tue, 21 Oct 2025 05:37:28 +0530 Subject: [PATCH 04/13] Fix action method param order --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index f584cbda4621b..af0d57dabc72a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3972,7 +3972,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails function inviteToRoomAction(reportID: string, inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, timezoneParam: Timezone) { const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs); - addComment(reportID, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false); + addComment(reportID, reportID, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false); } function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { From e6bad2cd417e3aa5b6c2e4d2a2f5117707443261 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Tue, 21 Oct 2025 23:22:12 +0530 Subject: [PATCH 05/13] lint fixes --- .../helpers/getFirstReportRouteOnBack.ts | 24 ++++++++++++------- src/libs/actions/Report.ts | 3 +++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts index b4f0349230058..c94a4c134ed39 100644 --- a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts +++ b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts @@ -1,9 +1,10 @@ import type {NavigationState} from '@react-navigation/native'; import {navigationRef} from '@libs/Navigation/Navigation'; +import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; -import ROUTES, {Route} from '@src/ROUTES'; +import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import {PlatformStackRouteProp} from '../PlatformStackNavigation/types'; const ReportScreens = { [SCREENS.REPORT]: ROUTES.REPORT_WITH_ID, @@ -28,15 +29,16 @@ type ReportRoute = * @returns The most recent report route in this stack, or null. */ function findDeepestReportInSplitNavigator(stackState: ReportNavigationState): ReportRoute | null { - if (!stackState || !stackState.routes || stackState.routes.length === 0) { + if (!stackState?.routes || stackState.routes.length === 0) { return null; } // Use the history array for chronological order, falling back to current routes if history is absent - const keysToSearch = stackState.history || stackState.routes.map((r) => r.key); + const keysToSearch = stackState.history ?? stackState.routes.map((r) => r.key); const routeMap: Record = stackState.routes.reduce( (map, route) => { + // eslint-disable-next-line no-param-reassign map[route.key] = route; return map; }, @@ -45,7 +47,7 @@ function findDeepestReportInSplitNavigator(stackState: ReportNavigationState): R // Iterate the history/keys from the most recent (last element) backward (LIFO) for (let i = keysToSearch.length - 1; i >= 0; i--) { - const routeKey = keysToSearch[i]; + const routeKey = keysToSearch.at(i); const route = routeMap[routeKey as string]; if (!route) { @@ -68,7 +70,7 @@ function findDeepestReportInSplitNavigator(stackState: ReportNavigationState): R * @returns The route object of the most recent report screen found, or null. */ function findFirstReportScreenOnBack(navigationState = navigationRef.getRootState()): ReportRoute | null { - if (!navigationState || !navigationState.routes || navigationState.routes.length === 0) { + if (!navigationState?.routes || navigationState.routes.length === 0) { return null; } @@ -77,7 +79,11 @@ function findFirstReportScreenOnBack(navigationState = navigationRef.getRootStat // Iterate over the current routes array in REVERSE (LIFO) order. // This prioritizes the most active navigator (e.g., RightModalNavigator, then SearchFullscreenNavigator, then ReportsSplitNavigator). for (let i = routes.length - 1; i >= 0; i--) { - const route = routes[i]; + const route = routes.at(i); + + if (!route) { + continue; + } // 1. Handle primary navigators (like ReportsSplitNavigator) if (route.name === 'ReportsSplitNavigator' && route.state) { @@ -109,15 +115,15 @@ function getFirstReportRouteOnBack(): Route | undefined { return; } switch (route.name) { - case SCREENS.REPORT: + case SCREENS.REPORT: { const {reportID, reportActionID, referrer, backTo} = route.params; return ReportScreens[SCREENS.REPORT].getRoute(reportID, reportActionID, referrer, backTo); + } case SCREENS.SEARCH.REPORT_RHP: return ReportScreens[SCREENS.SEARCH.REPORT_RHP].getRoute(route.params); case SCREENS.SEARCH.MONEY_REQUEST_REPORT: return ReportScreens[SCREENS.SEARCH.MONEY_REQUEST_REPORT].getRoute(route.params); default: - return; } } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index c72917ad0df73..c6f2d35a883d0 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3554,6 +3554,7 @@ function openReportFromDeepLink( const currentFocusedRoute = findFocusedRoute(state); if (isOnboardingFlowName(currentFocusedRoute?.name)) { + // eslint-disable-next-line @typescript-eslint/no-deprecated setOnboardingErrorMessage(Localize.translateLocal('onboarding.purpose.errorBackButton')); return; } @@ -4566,6 +4567,8 @@ function resolveActionableMentionWhisper( if (actionOriginalMessage && policyID) { const currentUserDetails = allPersonalDetails?.[getCurrentUserAccountID()]; const welcomeNoteSubject = `# ${currentUserDetails?.displayName ?? ''} invited you to ${policy?.name ?? 'a workspace'}`; + + // eslint-disable-next-line @typescript-eslint/no-deprecated const welcomeNote = Localize.translateLocal('workspace.common.welcomeNote'); const policyMemberAccountIDs = Object.values(getMemberAccountIDsForWorkspace(policy?.employeeList, false, false)); From beb0856c50f30563ad0e1c10bf238aa292842224 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 22 Oct 2025 00:54:48 +0530 Subject: [PATCH 06/13] Lint issues --- src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts index c94a4c134ed39..ccbd9d03292ed 100644 --- a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts +++ b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts @@ -93,7 +93,7 @@ function findFirstReportScreenOnBack(navigationState = navigationRef.getRootStat } } - // 2. Handle nested navigators (like modals/fullscreens) + // 2. Handle nested navigators (like modals) if (route.state) { const nestedReport = findFirstReportScreenOnBack(route.state as ReportNavigationState); if (nestedReport) { From a3df0596a990112a2819f04ea9a83468299c090a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 23 Oct 2025 16:13:53 +0530 Subject: [PATCH 07/13] Use backto For correcr navigation --- .../helpers/getFirstReportRouteOnBack.ts | 130 ------------------ src/pages/RoomInvitePage.tsx | 7 +- 2 files changed, 5 insertions(+), 132 deletions(-) delete mode 100644 src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts diff --git a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts b/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts deleted file mode 100644 index ccbd9d03292ed..0000000000000 --- a/src/libs/Navigation/helpers/getFirstReportRouteOnBack.ts +++ /dev/null @@ -1,130 +0,0 @@ -import type {NavigationState} from '@react-navigation/native'; -import {navigationRef} from '@libs/Navigation/Navigation'; -import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; -import ROUTES from '@src/ROUTES'; -import type {Route} from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; - -const ReportScreens = { - [SCREENS.REPORT]: ROUTES.REPORT_WITH_ID, - [SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT, - [SCREENS.SEARCH.MONEY_REQUEST_REPORT]: ROUTES.SEARCH_MONEY_REQUEST_REPORT, -} as const; - -const REPORT_SCREEN_NAMES = new Set(Object.keys(ReportScreens)); - -type ReportNavigationState = NavigationState; - -type ReportRoute = - | PlatformStackRouteProp - | PlatformStackRouteProp - | PlatformStackRouteProp; - -/** - * Searches a specific stack state (like ReportsSplitNavigator's state) for the - * most recently navigated report screen, preferring the 'history' for accuracy. - * - * @param stackState The internal state object of a stack navigator. - * @returns The most recent report route in this stack, or null. - */ -function findDeepestReportInSplitNavigator(stackState: ReportNavigationState): ReportRoute | null { - if (!stackState?.routes || stackState.routes.length === 0) { - return null; - } - - // Use the history array for chronological order, falling back to current routes if history is absent - const keysToSearch = stackState.history ?? stackState.routes.map((r) => r.key); - - const routeMap: Record = stackState.routes.reduce( - (map, route) => { - // eslint-disable-next-line no-param-reassign - map[route.key] = route; - return map; - }, - {} as Record, - ); - - // Iterate the history/keys from the most recent (last element) backward (LIFO) - for (let i = keysToSearch.length - 1; i >= 0; i--) { - const routeKey = keysToSearch.at(i); - const route = routeMap[routeKey as string]; - - if (!route) { - continue; // Skip keys that are no longer in the active routes array - } - - if (REPORT_SCREEN_NAMES.has(route.name)) { - return route; - } - } - - return null; -} - -/** - * Recursively searches a navigation state object to find the first report screen - * encountered when navigating backward (most recent report screen). - * - * @param navigationState The navigation state object (top-level or nested). - * @returns The route object of the most recent report screen found, or null. - */ -function findFirstReportScreenOnBack(navigationState = navigationRef.getRootState()): ReportRoute | null { - if (!navigationState?.routes || navigationState.routes.length === 0) { - return null; - } - - const {routes} = navigationState; - - // Iterate over the current routes array in REVERSE (LIFO) order. - // This prioritizes the most active navigator (e.g., RightModalNavigator, then SearchFullscreenNavigator, then ReportsSplitNavigator). - for (let i = routes.length - 1; i >= 0; i--) { - const route = routes.at(i); - - if (!route) { - continue; - } - - // 1. Handle primary navigators (like ReportsSplitNavigator) - if (route.name === 'ReportsSplitNavigator' && route.state) { - const reportInSplit = findDeepestReportInSplitNavigator(route.state as ReportNavigationState); - if (reportInSplit) { - return reportInSplit; - } - } - - // 2. Handle nested navigators (like modals) - if (route.state) { - const nestedReport = findFirstReportScreenOnBack(route.state as ReportNavigationState); - if (nestedReport) { - return nestedReport; - } - } - - if (REPORT_SCREEN_NAMES.has(route.name)) { - return route as ReportRoute; - } - } - - return null; -} - -function getFirstReportRouteOnBack(): Route | undefined { - const route = findFirstReportScreenOnBack(); - if (!route) { - return; - } - switch (route.name) { - case SCREENS.REPORT: { - const {reportID, reportActionID, referrer, backTo} = route.params; - return ReportScreens[SCREENS.REPORT].getRoute(reportID, reportActionID, referrer, backTo); - } - case SCREENS.SEARCH.REPORT_RHP: - return ReportScreens[SCREENS.SEARCH.REPORT_RHP].getRoute(route.params); - case SCREENS.SEARCH.MONEY_REQUEST_REPORT: - return ReportScreens[SCREENS.SEARCH.MONEY_REQUEST_REPORT].getRoute(route.params); - default: - } -} - -export default getFirstReportRouteOnBack; diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 275c2556d44c3..d59677903dbf2 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -24,7 +24,6 @@ import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import HttpUtils from '@libs/HttpUtils'; import {appendCountryCodeWithCountryCode} from '@libs/LoginUtils'; -import getFirstReportRouteOnBack from '@libs/Navigation/helpers/getFirstReportRouteOnBack'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types'; @@ -212,7 +211,11 @@ function RoomInvitePage({ if (reportID) { inviteToRoomAction(reportID, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE); clearUserSearchPhrase(); - Navigation.goBack(getFirstReportRouteOnBack()); + if (backTo) { + Navigation.goBack(backTo); + } else { + Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + } } }, [validate, selectedOptions, reportID, currentUserPersonalDetails.timezone, backTo]); From 28d94ba52f35c52de48dd5ef3eef29ad1a15fc7d Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 2 Nov 2025 02:48:00 +0530 Subject: [PATCH 08/13] Hide invite to submit expenses for system users --- src/libs/ReportActionsUtils.ts | 7 +++++++ src/pages/home/report/PureReportActionItem.tsx | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index e82026942aee5..63aa17009a4db 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -3285,6 +3285,12 @@ function getSubmittedTo(action: OnyxEntry): string | undefined { return getOriginalMessage(action)?.to; } +function isSystemUserMentioned(action: OnyxInputOrEntry>): boolean { + const whisperedTo = getOriginalMessage(action)?.whisperedTo; + const systemAccountIDs = new Set(Object.values(CONST.ACCOUNT_ID)); + return whisperedTo?.some((accountID) => systemAccountIDs.has(accountID)) ?? false; +} + export { doesReportHaveVisibleActions, extractLinksFromMessageHtml, @@ -3461,6 +3467,7 @@ export { isPendingHide, filterOutDeprecatedReportActions, getActionableCardFraudAlertMessage, + isSystemUserMentioned, }; export type {LastVisibleMessage}; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 09466a3c31380..2dc065cfdc16c 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -130,6 +130,7 @@ import { isRenamedAction, isResolvedConciergeCategoryOptions, isSplitBillAction as isSplitBillActionReportActionsUtils, + isSystemUserMentioned, isTagModificationAction, isTaskAction, isTrackExpenseAction as isTrackExpenseActionReportActionsUtils, @@ -901,7 +902,7 @@ function PureReportActionItem({ const actionableMentionWhisperOptions = []; const isReportInPolicy = !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE && getPersonalPolicy()?.id !== report.policyID; - if (isReportInPolicy && (isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID))) { + if (!isSystemUserMentioned(action) && isReportInPolicy && (isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID))) { actionableMentionWhisperOptions.push({ text: 'actionableMentionWhisperOptions.inviteToSubmitExpense', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE}`, From 2a54e40ec0a60b9918a5e490294b361dd97f92c8 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 13 Nov 2025 06:29:17 +0530 Subject: [PATCH 09/13] Show the invite to submit expense button even if one of the mentioned users is a not a policy member --- src/pages/home/report/PureReportActionItem.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index edcbac846771c..5fbc411a6e8a8 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -57,8 +57,8 @@ import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import {isReportMessageAttachment} from '@libs/isReportMessageAttachment'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; -import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; -import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner} from '@libs/PolicyUtils'; +import {getDisplayNameOrDefault, getLoginByAccountID} from '@libs/PersonalDetailsUtils'; +import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner, isPolicyUser} from '@libs/PolicyUtils'; import { extractLinksFromMessageHtml, getActionableCardFraudAlertMessage, @@ -904,7 +904,13 @@ function PureReportActionItem({ const actionableMentionWhisperOptions = []; const isReportInPolicy = !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE && getPersonalPolicy()?.id !== report.policyID; - if (!isSystemUserMentioned(action) && isReportInPolicy && (isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID))) { + // Show the invite to submit expense button even if one of the mentioned users is a not a policy member + const hasMentionedPolicyMembers = + getOriginalMessage(action) + ?.whisperedTo?.map((accountID, index) => getLoginByAccountID(accountID)) + ?.every((login) => isPolicyUser(policy, login)) ?? false; + + if ((isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID)) && isReportInPolicy && !isSystemUserMentioned(action) && !hasMentionedPolicyMembers) { actionableMentionWhisperOptions.push({ text: 'actionableMentionWhisperOptions.inviteToSubmitExpense', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE_TO_SUBMIT_EXPENSE}`, From ce9e2371e22aff39fc90b017216e2a5d6308d70f Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 13 Nov 2025 06:34:04 +0530 Subject: [PATCH 10/13] fix lint --- src/pages/home/report/PureReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 5fbc411a6e8a8..02b6bee4524d4 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -907,7 +907,7 @@ function PureReportActionItem({ // Show the invite to submit expense button even if one of the mentioned users is a not a policy member const hasMentionedPolicyMembers = getOriginalMessage(action) - ?.whisperedTo?.map((accountID, index) => getLoginByAccountID(accountID)) + ?.whisperedTo?.map(getLoginByAccountID) ?.every((login) => isPolicyUser(policy, login)) ?? false; if ((isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID)) && isReportInPolicy && !isSystemUserMentioned(action) && !hasMentionedPolicyMembers) { From 51f207d3f8a581760106731cacbb91fb64fd6bc4 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 13 Nov 2025 14:11:39 +0530 Subject: [PATCH 11/13] Fix mention checks --- src/libs/ReportActionsUtils.ts | 4 ++-- src/pages/home/report/PureReportActionItem.tsx | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 2dc2a9359c10e..d6b6cb6d614cd 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -3299,9 +3299,9 @@ function getSubmittedTo(action: OnyxEntry): string | undefined { } function isSystemUserMentioned(action: OnyxInputOrEntry>): boolean { - const whisperedTo = getOriginalMessage(action)?.whisperedTo; + const mentionedUsers = getOriginalMessage(action)?.inviteeAccountIDs; const systemAccountIDs = new Set(Object.values(CONST.ACCOUNT_ID)); - return whisperedTo?.some((accountID) => systemAccountIDs.has(accountID)) ?? false; + return mentionedUsers?.some((accountID) => systemAccountIDs.has(accountID)) ?? false; } export { diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 02b6bee4524d4..a867d1c59c330 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -905,10 +905,7 @@ function PureReportActionItem({ const isReportInPolicy = !!report?.policyID && report.policyID !== CONST.POLICY.ID_FAKE && getPersonalPolicy()?.id !== report.policyID; // Show the invite to submit expense button even if one of the mentioned users is a not a policy member - const hasMentionedPolicyMembers = - getOriginalMessage(action) - ?.whisperedTo?.map(getLoginByAccountID) - ?.every((login) => isPolicyUser(policy, login)) ?? false; + const hasMentionedPolicyMembers = getOriginalMessage(action)?.inviteeEmails?.every((login) => isPolicyUser(policy, login)) ?? false; if ((isPolicyAdmin(policy) || isPolicyOwner(policy, currentUserAccountID)) && isReportInPolicy && !isSystemUserMentioned(action) && !hasMentionedPolicyMembers) { actionableMentionWhisperOptions.push({ From fab9eeb8f3083ab5e4a827c82e2a8366fa4dc8f3 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Thu, 13 Nov 2025 15:45:41 +0530 Subject: [PATCH 12/13] Apply suggestion from @parasharrajat --- src/pages/home/report/PureReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index a867d1c59c330..22f37dfd8425d 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -57,7 +57,7 @@ import focusComposerWithDelay from '@libs/focusComposerWithDelay'; import {isReportMessageAttachment} from '@libs/isReportMessageAttachment'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; -import {getDisplayNameOrDefault, getLoginByAccountID} from '@libs/PersonalDetailsUtils'; +import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; import {getCleanedTagName, getPersonalPolicy, isPolicyAdmin, isPolicyOwner, isPolicyUser} from '@libs/PolicyUtils'; import { extractLinksFromMessageHtml, From 4d20c5ee98a8ad6245c2c34a4f7a3bd1efc1ab44 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 19 Nov 2025 23:05:09 +0530 Subject: [PATCH 13/13] Pass ancestors --- src/libs/actions/Report.ts | 4 ++-- src/pages/RoomInvitePage.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 352194b743e4f..d8a495edc178d 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3854,10 +3854,10 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails } /** Invites people to a room via concierge whisper */ -function inviteToRoomAction(reportID: string, inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, timezoneParam: Timezone) { +function inviteToRoomAction(reportID: string, ancestors: Ancestor[], inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, timezoneParam: Timezone) { const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs); - addComment(reportID, reportID, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false); + addComment(reportID, reportID, ancestors, inviteeEmails.map((login) => `@${login}`).join(' '), timezoneParam, false); } function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index d6ddd23b89acf..01eca5ddfae76 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -12,6 +12,7 @@ import InviteMemberListItem from '@components/SelectionListWithSections/InviteMe import type {Section} from '@components/SelectionListWithSections/types'; import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; +import useAncestors from '@hooks/useAncestors'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; @@ -194,6 +195,9 @@ function RoomInvitePage({ return reportID && (!isPolicyEmployee || isReportArchived ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo) : ROUTES.ROOM_MEMBERS.getRoute(reportID, backTo)); }, [isPolicyEmployee, reportID, backTo, isReportArchived]); const reportName = useMemo(() => getReportName(report), [report]); + + const ancestors = useAncestors(report); + const inviteUsers = useCallback(() => { HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); @@ -210,7 +214,7 @@ function RoomInvitePage({ invitedEmailsToAccountIDs[login] = Number(accountID); } if (reportID) { - inviteToRoomAction(reportID, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE); + inviteToRoomAction(reportID, ancestors, invitedEmailsToAccountIDs, currentUserPersonalDetails.timezone ?? CONST.DEFAULT_TIME_ZONE); clearUserSearchPhrase(); if (backTo) { Navigation.goBack(backTo); @@ -218,7 +222,7 @@ function RoomInvitePage({ Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(reportID)); } } - }, [validate, selectedOptions, reportID, currentUserPersonalDetails.timezone, backTo]); + }, [validate, selectedOptions, ancestors, reportID, currentUserPersonalDetails.timezone, backTo]); const goBack = useCallback(() => { Navigation.goBack(backRoute);