From 73ef964a9f12105f4a006956794d16226cb16697 Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Thu, 4 Dec 2025 23:08:16 +0530 Subject: [PATCH 1/8] fix: close RHP when navigating from RHP to non-RHP screen using link --- .../helpers/willRouteNavigateToRHP.ts | 23 +++++++++++++++++++ src/libs/actions/Link.ts | 14 +++++++++++ 2 files changed, 37 insertions(+) create mode 100644 src/libs/Navigation/helpers/willRouteNavigateToRHP.ts diff --git a/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts b/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts new file mode 100644 index 000000000000..9bb7a2a70481 --- /dev/null +++ b/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts @@ -0,0 +1,23 @@ +import NAVIGATORS from '@src/NAVIGATORS'; +import type {Route} from '@src/ROUTES'; +import getStateFromPath from './getStateFromPath'; + +/** + * Check if a route will navigate to a screen in the RightModalNavigator + */ +function willRouteNavigateToRHP(route: Route): boolean { + try { + const state = getStateFromPath(route); + if (!state) { + return false; + } + // Check if the last route in the state is the RightModalNavigator + const lastRoute = state?.routes?.at(-1); + return lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; + } catch { + // If parsing fails, assume it's not an RHP route + return false; + } +} + +export default willRouteNavigateToRHP; diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index f94d07337edb..b84cfa22d520 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -11,6 +11,7 @@ import isPublicScreenRoute from '@libs/isPublicScreenRoute'; import {isOnboardingFlowName} from '@libs/Navigation/helpers/isNavigatorName'; import normalizePath from '@libs/Navigation/helpers/normalizePath'; import shouldOpenOnAdminRoom from '@libs/Navigation/helpers/shouldOpenOnAdminRoom'; +import willRouteNavigateToRHP from '@libs/Navigation/helpers/willRouteNavigateToRHP'; import Navigation from '@libs/Navigation/Navigation'; import navigationRef from '@libs/Navigation/navigationRef'; import type {NetworkStatus} from '@libs/NetworkConnection'; @@ -192,6 +193,13 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const internalNewExpensifyPath = getInternalNewExpensifyPath(href); const internalExpensifyPath = getInternalExpensifyPath(href); + // Check if RHP is currently open + const currentState = navigationRef.getRootState(); + const isRHPOpen = currentState?.routes?.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; + + // Check if the destination route will also open in RHP + const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); + // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, // clicking on the link will open the chat in NewDot. However, when a user is in NewDot and clicks on the concierge link, the link needs to be handled differently. @@ -200,6 +208,9 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { if (hasExpensifyOrigin && href.indexOf('newdotreport?reportID=') > -1) { const reportID = href.split('newdotreport?reportID=').pop(); const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID); + if (isRHPOpen && !willOpenInRHP) { + Navigation.closeRHPFlow(); + } Navigation.navigate(reportRoute); return; } @@ -211,6 +222,9 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { signOutAndRedirectToSignIn(); return; } + if (isRHPOpen && !willOpenInRHP) { + Navigation.closeRHPFlow(); + } Navigation.navigate(internalNewExpensifyPath as Route); return; } From c87d37cc8baa6fee0204e558d3a955a2c5b9beaf Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Thu, 4 Dec 2025 23:49:32 +0530 Subject: [PATCH 2/8] fixed the animation issue happening in narrow mobile screens --- src/libs/actions/Link.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index b84cfa22d520..b678d6cdc16f 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -7,6 +7,7 @@ import type {GenerateSpotnanaTokenParams} from '@libs/API/parameters'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; import asyncOpenURL from '@libs/asyncOpenURL'; import * as Environment from '@libs/Environment/Environment'; +import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import isPublicScreenRoute from '@libs/isPublicScreenRoute'; import {isOnboardingFlowName} from '@libs/Navigation/helpers/isNavigatorName'; import normalizePath from '@libs/Navigation/helpers/normalizePath'; @@ -197,8 +198,10 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const currentState = navigationRef.getRootState(); const isRHPOpen = currentState?.routes?.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - // Check if the destination route will also open in RHP + // Check if the destination route will open in RHP const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); + const isNarrowLayout = getIsNarrowLayout(); + const shouldCloseRHP = isRHPOpen && !willOpenInRHP && !isNarrowLayout; // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, @@ -208,7 +211,7 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { if (hasExpensifyOrigin && href.indexOf('newdotreport?reportID=') > -1) { const reportID = href.split('newdotreport?reportID=').pop(); const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID); - if (isRHPOpen && !willOpenInRHP) { + if (shouldCloseRHP) { Navigation.closeRHPFlow(); } Navigation.navigate(reportRoute); @@ -222,7 +225,7 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { signOutAndRedirectToSignIn(); return; } - if (isRHPOpen && !willOpenInRHP) { + if (shouldCloseRHP) { Navigation.closeRHPFlow(); } Navigation.navigate(internalNewExpensifyPath as Route); From 52a58ac7d09d5359d8e1d5c0184587544d255b0b Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Mon, 8 Dec 2025 17:26:55 +0530 Subject: [PATCH 3/8] removed unnecessary code comments --- src/libs/Navigation/helpers/willRouteNavigateToRHP.ts | 3 +-- src/libs/actions/Link.ts | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts b/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts index 9bb7a2a70481..eb769b65dcf1 100644 --- a/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts +++ b/src/libs/Navigation/helpers/willRouteNavigateToRHP.ts @@ -11,11 +11,10 @@ function willRouteNavigateToRHP(route: Route): boolean { if (!state) { return false; } - // Check if the last route in the state is the RightModalNavigator + const lastRoute = state?.routes?.at(-1); return lastRoute?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; } catch { - // If parsing fails, assume it's not an RHP route return false; } } diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index b678d6cdc16f..1c176186b658 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -194,11 +194,9 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const internalNewExpensifyPath = getInternalNewExpensifyPath(href); const internalExpensifyPath = getInternalExpensifyPath(href); - // Check if RHP is currently open const currentState = navigationRef.getRootState(); const isRHPOpen = currentState?.routes?.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - // Check if the destination route will open in RHP const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); const isNarrowLayout = getIsNarrowLayout(); const shouldCloseRHP = isRHPOpen && !willOpenInRHP && !isNarrowLayout; From 8f94fbc6b1b76d647c7631352903933ee3aecffe Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Wed, 17 Dec 2025 18:38:42 +0530 Subject: [PATCH 4/8] fixed RHP overlay issue when the new route has entirely different base path --- src/libs/actions/Link.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 1c176186b658..a3d9a84733d1 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -188,6 +188,12 @@ function getInternalExpensifyPath(href: string) { return attrPath; } +function getRouteBasePath(route: string) { + const routeWithoutParams = route.split('?').at(0) ?? ''; + const segments = routeWithoutParams.split('/').filter((segment) => segment !== ''); + return segments.at(0) ?? ''; +} + function openLink(href: string, environmentURL: string, isAttachment = false) { const hasSameOrigin = Url.hasSameExpensifyOrigin(href, environmentURL); const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.STAGING_API_ROOT); @@ -196,10 +202,19 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const currentState = navigationRef.getRootState(); const isRHPOpen = currentState?.routes?.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); const isNarrowLayout = getIsNarrowLayout(); - const shouldCloseRHP = isRHPOpen && !willOpenInRHP && !isNarrowLayout; + + let isDifferentRHPRoute = false; + if (isRHPOpen && willOpenInRHP) { + const currentRoute = Navigation.getActiveRoute(); + const newRoute = internalNewExpensifyPath; + const currentRouteBase = getRouteBasePath(currentRoute); + const newRouteBase = getRouteBasePath(newRoute); + isDifferentRHPRoute = currentRouteBase !== newRouteBase && currentRouteBase !== '' && newRouteBase !== ''; + } + + const shouldCloseRHP = !isNarrowLayout && isRHPOpen && (!willOpenInRHP || isDifferentRHPRoute); // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, From 521bd3820273ab529fe60d3f7e01eb75714bf7bc Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Sat, 20 Dec 2025 17:41:35 +0530 Subject: [PATCH 5/8] fixed issues related to different route navigation in RHP --- src/libs/actions/Link.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index a3d9a84733d1..292f87580cf1 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -188,10 +188,18 @@ function getInternalExpensifyPath(href: string) { return attrPath; } -function getRouteBasePath(route: string) { +function getNormalizedRoute(route: string) { const routeWithoutParams = route.split('?').at(0) ?? ''; const segments = routeWithoutParams.split('/').filter((segment) => segment !== ''); - return segments.at(0) ?? ''; + const normalizedSegments = segments.map((segment) => { + // Check if segment is a number, UUID, or likely a dynamic ID and return :id for that + if (/^[\d]+$/.test(segment) || /^[a-f0-9-]{20,}$/i.test(segment) || /^[A-Z0-9]{8,}$/i.test(segment)) { + return ':id'; + } + return segment; + }); + + return normalizedSegments.join('/'); } function openLink(href: string, environmentURL: string, isAttachment = false) { @@ -205,16 +213,16 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); const isNarrowLayout = getIsNarrowLayout(); - let isDifferentRHPRoute = false; + let isOldAndNewNormalizedRouteSame = false; if (isRHPOpen && willOpenInRHP) { const currentRoute = Navigation.getActiveRoute(); const newRoute = internalNewExpensifyPath; - const currentRouteBase = getRouteBasePath(currentRoute); - const newRouteBase = getRouteBasePath(newRoute); - isDifferentRHPRoute = currentRouteBase !== newRouteBase && currentRouteBase !== '' && newRouteBase !== ''; + const currentNormalizedRoute = getNormalizedRoute(currentRoute); + const newNormalizedRoute = getNormalizedRoute(newRoute); + isOldAndNewNormalizedRouteSame = currentNormalizedRoute === newNormalizedRoute; } - const shouldCloseRHP = !isNarrowLayout && isRHPOpen && (!willOpenInRHP || isDifferentRHPRoute); + const shouldCloseRHP = !isNarrowLayout && isRHPOpen && (!willOpenInRHP || !isOldAndNewNormalizedRouteSame); // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, From 6ab7e762850c0864e6d50721c9ccad7cf5399632 Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Mon, 29 Dec 2025 21:04:18 +0530 Subject: [PATCH 6/8] fix PR comments --- src/libs/actions/Link.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 9d689ad61d98..ce07d5d56fee 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -213,16 +213,13 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); const isNarrowLayout = getIsNarrowLayout(); - let isOldAndNewNormalizedRouteSame = false; + let willOpenSameRoute = false; if (isRHPOpen && willOpenInRHP) { const currentRoute = Navigation.getActiveRoute(); - const newRoute = internalNewExpensifyPath; - const currentNormalizedRoute = getNormalizedRoute(currentRoute); - const newNormalizedRoute = getNormalizedRoute(newRoute); - isOldAndNewNormalizedRouteSame = currentNormalizedRoute === newNormalizedRoute; + willOpenSameRoute = getNormalizedRoute(currentRoute) === getNormalizedRoute(internalNewExpensifyPath); } - const shouldCloseRHP = !isNarrowLayout && isRHPOpen && (!willOpenInRHP || !isOldAndNewNormalizedRouteSame); + const shouldCloseRHP = !isNarrowLayout && isRHPOpen && (!willOpenInRHP || !willOpenSameRoute); // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, From ae4e78ba83ca9cbc020907425f22bc24d3a0d89e Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Wed, 7 Jan 2026 14:28:23 +0530 Subject: [PATCH 7/8] fixed pr comments --- src/libs/actions/Link.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index ce07d5d56fee..128d9bd45707 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -208,19 +208,17 @@ function openLink(href: string, environmentURL: string, isAttachment = false) { const internalNewExpensifyPath = getInternalNewExpensifyPath(href); const internalExpensifyPath = getInternalExpensifyPath(href); + const isNarrowLayout = getIsNarrowLayout(); const currentState = navigationRef.getRootState(); const isRHPOpen = currentState?.routes?.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR; - const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); - const isNarrowLayout = getIsNarrowLayout(); - - let willOpenSameRoute = false; - if (isRHPOpen && willOpenInRHP) { + let shouldCloseRHP = false; + if (!isNarrowLayout && isRHPOpen) { + const willOpenInRHP = willRouteNavigateToRHP(internalNewExpensifyPath as Route); const currentRoute = Navigation.getActiveRoute(); - willOpenSameRoute = getNormalizedRoute(currentRoute) === getNormalizedRoute(internalNewExpensifyPath); + const willOpenSameRoute = getNormalizedRoute(currentRoute) === getNormalizedRoute(internalNewExpensifyPath); + shouldCloseRHP = !willOpenInRHP || !willOpenSameRoute; } - const shouldCloseRHP = !isNarrowLayout && isRHPOpen && (!willOpenInRHP || !willOpenSameRoute); - // There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this: // https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot, // clicking on the link will open the chat in NewDot. However, when a user is in NewDot and clicks on the concierge link, the link needs to be handled differently. From 1bfc0b966720d19cc6c2d26db29d4f6f76a5ca7a Mon Sep 17 00:00:00 2001 From: Burhan-Rashid Date: Fri, 9 Jan 2026 18:11:43 +0530 Subject: [PATCH 8/8] added comments --- src/libs/actions/Link.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/Link.ts b/src/libs/actions/Link.ts index 128d9bd45707..d70598d5e886 100644 --- a/src/libs/actions/Link.ts +++ b/src/libs/actions/Link.ts @@ -188,6 +188,9 @@ function getInternalExpensifyPath(href: string) { return attrPath; } +/** + * Normalizes a route by replacing route path variables with a generic placeholder(:id). For example /report/12345 becomes /report/:id + */ function getNormalizedRoute(route: string) { const routeWithoutParams = route.split('?').at(0) ?? ''; const segments = routeWithoutParams.split('/').filter((segment) => segment !== '');