Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ce0b326
fix: correct Audit Reports navigation and prioritize report routes
marufsharifi Apr 13, 2026
14bac24
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi Apr 14, 2026
28cc656
Refactor task modal dismiss logic into shared helper
marufsharifi Apr 14, 2026
225c9c2
Guard leave flow backTo against current report
marufsharifi Apr 14, 2026
1387baa
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi Apr 23, 2026
315fd72
Return to task after edits
marufsharifi Apr 23, 2026
a8b07e2
Unwrap search backTo on leave
marufsharifi Apr 23, 2026
603c727
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi Apr 25, 2026
b80540b
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi Apr 27, 2026
5ff28e6
Preserve backTo when deleting tasks
marufsharifi Apr 27, 2026
4408c14
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi Apr 29, 2026
5ad1115
fix task delete navigation from search
marufsharifi Apr 29, 2026
e05683a
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 4, 2026
ab00393
fix lint
marufsharifi May 4, 2026
fbd2f2c
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 5, 2026
76de249
Fix task delete navigation when task has messages
marufsharifi May 5, 2026
dd5cb71
Update task delete tests
marufsharifi May 5, 2026
3fcfba9
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 8, 2026
54ae055
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 11, 2026
d04c744
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 12, 2026
872c413
fix lint
marufsharifi May 12, 2026
274701e
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 14, 2026
5f7850d
fix back navigation after deleting task details
marufsharifi May 14, 2026
308ef5d
fix nested backTo handling when leaving thread
marufsharifi May 14, 2026
2b1820c
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 18, 2026
270d54b
fix task delete redirect after leaving details
marufsharifi May 18, 2026
92e7825
fix task delete navigation when task has messages
marufsharifi May 18, 2026
1419009
Merge branch 'main' into fix/audit-reports-navigation-prioritize-repo…
marufsharifi May 19, 2026
703284c
guard search route helper before navigation is ready
marufsharifi May 19, 2026
a79ba6d
fix chat expense preview navigation context
marufsharifi May 19, 2026
437f624
fix navigationRef guard in search route helper
marufsharifi May 19, 2026
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
5 changes: 3 additions & 2 deletions src/components/ReportActionItem/MoneyRequestAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import {createTransactionThreadReport} from '@libs/actions/Report';
import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types';
Expand Down Expand Up @@ -131,7 +132,7 @@ function MoneyRequestAction({
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: transactionThreadReport?.reportID, backTo: Navigation.getActiveRoute()}));
return;
}
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(transactionThreadReport?.reportID, undefined, undefined, Navigation.getActiveRoute()));
Navigation.navigate(getReportRouteForCurrentContext({reportID: transactionThreadReport?.reportID}));
return;
}

Expand All @@ -140,7 +141,7 @@ function MoneyRequestAction({
return;
}

Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(action?.childReportID, undefined, undefined, Navigation.getActiveRoute()));
Navigation.navigate(getReportRouteForCurrentContext({reportID: action?.childReportID}));
};

const isDeletedParentAction = isDeletedParentActionReportActionsUtils(action);
Expand Down
4 changes: 2 additions & 2 deletions src/components/ReportActionItem/TaskPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ import ControlSelection from '@libs/ControlSelection';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
import getButtonState from '@libs/getButtonState';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext';
import Navigation from '@libs/Navigation/Navigation';
import Parser from '@libs/Parser';
import {getOriginalMessage} from '@libs/ReportActionsUtils';
import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils';
import type {ContextMenuAnchor} from '@pages/inbox/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Report, ReportAction} from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

Expand Down Expand Up @@ -143,7 +143,7 @@ function TaskPreview({
return (
<View style={[styles.chatItemMessage, !hasAssignee && styles.mv1]}>
<PressableWithoutFeedback
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID, undefined, undefined, Navigation.getActiveRoute()))}
onPress={() => Navigation.navigate(getReportRouteForCurrentContext({reportID: taskReportID}))}
onPressIn={() => canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) =>
Expand Down
18 changes: 18 additions & 0 deletions src/libs/Navigation/helpers/dismissModalForCurrentContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Navigation from '@libs/Navigation/Navigation';
import isSearchTopmostFullScreenRoute from './isSearchTopmostFullScreenRoute';

function dismissModalForCurrentContext(reportID?: string) {
if (isSearchTopmostFullScreenRoute()) {
Navigation.dismissModal();
return;
}

if (!reportID) {
Navigation.dismissModal();
return;
}

Navigation.dismissModalWithReport({reportID});
}

export default dismissModalForCurrentContext;
22 changes: 22 additions & 0 deletions src/libs/Navigation/helpers/getReportRouteForCurrentContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Navigation from '@libs/Navigation/Navigation';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import isSearchTopmostFullScreenRoute from './isSearchTopmostFullScreenRoute';

type GetReportRouteForCurrentContextParams = {
reportID: string | undefined;
reportActionID?: string;
backTo?: Route;
};

function getReportRouteForCurrentContext({reportID, reportActionID, backTo}: GetReportRouteForCurrentContextParams): Route {
const currentRoute = backTo ?? (Navigation.getActiveRoute() as Route);

if (isSearchTopmostFullScreenRoute()) {
return ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo: currentRoute});
}

return ROUTES.REPORT_WITH_ID.getRoute(reportID, reportActionID, undefined, currentRoute);
}

export default getReportRouteForCurrentContext;
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {navigationRef} from '@libs/Navigation/Navigation';
import navigationRef from '@libs/Navigation/navigationRef';
import type {RootNavigatorParamList, State} from '@libs/Navigation/types';
import NAVIGATORS from '@src/NAVIGATORS';
import getActiveTabName from './getActiveTabName';
import {isFullScreenName} from './isNavigatorName';

const isSearchTopmostFullScreenRoute = (): boolean => {
const rootState = navigationRef.getRootState() as State<RootNavigatorParamList>;
if (!navigationRef.isReady?.()) {
return false;
}

const rootState = navigationRef.getRootState?.() as State<RootNavigatorParamList>;

if (!rootState) {
return false;
Expand Down
72 changes: 72 additions & 0 deletions src/libs/Navigation/helpers/shouldUseBackToOnLeaveReport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';

function normalizeRoute(route: string): string {
return route
.replaceAll(/\?.*/g, '')
.replaceAll(/^\/+|\/+$/g, '')
.replaceAll(/\/+/g, '/');
}

function getNestedBackToRoute(route: string): Route | undefined {
const [, queryString = ''] = route.split('?');

if (!queryString) {
return undefined;
}

const params = new URLSearchParams(queryString);
const encodedBackTo = params.get('backTo');

if (!encodedBackTo) {
return undefined;
}

return encodedBackTo as Route;
}

function doesRouteTargetCurrentReport(route: string, reportID: string): boolean {
const normalizedRoute = normalizeRoute(route);
const currentReportRoutes = [
ROUTES.REPORT_WITH_ID.getRoute(reportID),
ROUTES.SEARCH_REPORT.getRoute({reportID}),
ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID}),
ROUTES.EXPENSE_REPORT_RHP.getRoute({reportID}),
].map(normalizeRoute);

return currentReportRoutes.some((currentReportRoute) => normalizedRoute === currentReportRoute || normalizedRoute.startsWith(`${currentReportRoute}/`));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we have discussed this before. Could you please help me understand why we need this change and what we are trying to achieve with it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper is needed because backTo is not always safe to use directly in the leave flow. In some cases it points back to the current report, while in Search flows it represents the user’s original Search context. The helper makes that distinction so we preserve the Search return path when appropriate without changing the existing non-Search behavior


function getBackToOnLeaveReport(reportID: string | undefined, backTo?: Route): Route | undefined {
if (!backTo) {
return undefined;
}

const normalizedBackTo = normalizeRoute(backTo);
const isSearchRoute = normalizedBackTo.startsWith(ROUTES.SEARCH_ROOT.route);

if (isSearchRoute) {
if (!reportID || !doesRouteTargetCurrentReport(backTo, reportID)) {
return backTo;
}

return getNestedBackToRoute(backTo) ?? backTo;
}

if (!reportID) {
return backTo;
}

if (doesRouteTargetCurrentReport(backTo, reportID)) {
return undefined;
}

return backTo;
}

function shouldUseBackToOnLeaveReport(reportID: string | undefined, backTo?: Route): boolean {
return !!getBackToOnLeaveReport(reportID, backTo);
}

export {getBackToOnLeaveReport};
export default shouldUseBackToOnLeaveReport;
19 changes: 16 additions & 3 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ import Log from '@libs/Log';
import {isEmailPublicDomain} from '@libs/LoginUtils';
import {getMovedReportID} from '@libs/ModifiedExpenseMessage';
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
import getReportRouteForCurrentContext from '@libs/Navigation/helpers/getReportRouteForCurrentContext';
import type {LinkToOptions} from '@libs/Navigation/helpers/linkTo/types';
import {getBackToOnLeaveReport} from '@libs/Navigation/helpers/shouldUseBackToOnLeaveReport';
import Navigation from '@libs/Navigation/Navigation';
import enhanceParameters from '@libs/Network/enhanceParameters';
import {getDBTimeWithSkew, getIsOffline as isOfflineNetwork} from '@libs/NetworkState';
Expand Down Expand Up @@ -200,6 +202,7 @@ import CONFIG from '@src/CONFIG';
import type {OnboardingAccounting} from '@src/CONST';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES, {DYNAMIC_ROUTES} from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/NewRoomForm';
import type {
Expand Down Expand Up @@ -2437,7 +2440,7 @@ function navigateToAndOpenChildReport(
) {
const report = childReport ?? createChildReport(childReport, parentReportAction, parentReport, currentUserAccountID, introSelected, betas, isSelfTourViewed, personalDetails);

Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(report.reportID, undefined, undefined, Navigation.getActiveRoute()));
Navigation.navigate(getReportRouteForCurrentContext({reportID: report.reportID}));
}

/**
Expand Down Expand Up @@ -4748,7 +4751,15 @@ function navigateToMostRecentReport(
currentUserAccountID: number,
introSelected: OnyxEntry<IntroSelected>,
betas: OnyxEntry<Beta[]>,
backTo?: Route,
) {
const backToOnLeave = getBackToOnLeaveReport(currentReport?.reportID, backTo);

if (backToOnLeave) {
Navigation.goBack(backToOnLeave);
return;
}

const lastAccessedReportID = findLastAccessedReport(false, false, currentReport?.reportID)?.reportID;

if (lastAccessedReportID) {
Expand Down Expand Up @@ -4800,6 +4811,7 @@ function leaveGroupChat(
conciergeReportID: string | undefined,
introSelected: OnyxEntry<IntroSelected>,
betas: OnyxEntry<Beta[]>,
backTo?: Route,
) {
const reportID = report.reportID;
// Use merge instead of set to avoid deleting the report too quickly, which could cause a brief "not found" page to appear.
Expand Down Expand Up @@ -4847,7 +4859,7 @@ function leaveGroupChat(
},
];

navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas);
navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas, backTo);
API.write(WRITE_COMMANDS.LEAVE_GROUP_CHAT, {reportID}, {optimisticData, successData, failureData});
}

Expand All @@ -4859,6 +4871,7 @@ function leaveRoom(
introSelected: OnyxEntry<IntroSelected>,
betas: OnyxEntry<Beta[]>,
isWorkspaceMemberLeavingWorkspaceRoom = false,
backTo?: Route,
) {
const reportID = report.reportID;
const isChatThread = isChatThreadReportUtils(report);
Expand Down Expand Up @@ -4963,7 +4976,7 @@ function leaveRoom(
return;
}
// In other cases, the report is deleted and we should move the user to another report.
navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas);
navigateToMostRecentReport(report, conciergeReportID, currentUserAccountID, introSelected, betas, backTo);
}

function buildInviteToRoomOnyxData(report: Report, inviteeEmailsToAccountIDs: InvitedEmailsToAccountIDs, formatPhoneNumber: LocaleContextProps['formatPhoneNumber']) {
Expand Down
83 changes: 76 additions & 7 deletions src/libs/actions/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {WRITE_COMMANDS} from '@libs/API/types';
import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import dismissModalForCurrentContext from '@libs/Navigation/helpers/dismissModalForCurrentContext';
import Navigation from '@libs/Navigation/Navigation';
import {getDBTimeWithSkew} from '@libs/NetworkState';
import * as OptionsListUtils from '@libs/OptionsListUtils';
Expand Down Expand Up @@ -388,7 +389,7 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) {
InteractionManager.runAfterInteractions(() => {
clearOutTaskInfo();
});
Navigation.dismissModalWithReport({reportID: parentReportID});
dismissModalForCurrentContext(parentReportID);
}
notifyNewAction(parentReportID, optimisticAddCommentReport.reportAction, true);
}
Expand Down Expand Up @@ -1159,19 +1160,81 @@ function getShareDestination(
};
}

function getNestedBackToRoute(route: Route): Route | undefined {
const queryStringIndex = route.indexOf('?');
const queryString = queryStringIndex === -1 ? '' : route.slice(queryStringIndex + 1);

if (!queryString) {
return undefined;
}

const params = new URLSearchParams(queryString);
const encodedBackTo = params.get('backTo');

if (!encodedBackTo) {
return undefined;
}

return encodedBackTo as Route;
}

function getSearchRouteWithoutReportActionID(route: Route): Route {
const queryStringIndex = route.indexOf('?');
const path = queryStringIndex === -1 ? route : route.slice(0, queryStringIndex);
const queryString = queryStringIndex === -1 ? '' : route.slice(queryStringIndex + 1);
const match = path.match(/^\/?search\/view\/([^/?]+)\/[^/?]+\/?$/);

if (!match) {
return route;
}

const reportID = match.at(1);
if (!reportID) {
return route;
}

const cleanedRoute = ROUTES.SEARCH_REPORT.getRoute({reportID});
if (!queryString) {
return cleanedRoute;
}

return `${cleanedRoute}?${queryString}` as Route;
}

function getFlattenedTaskDeleteBackTo(backTo: Route): Route {
const flattenedBackTo = getSearchRouteWithoutReportActionID(backTo);
const nestedBackTo = getNestedBackToRoute(flattenedBackTo);

if (!nestedBackTo) {
return flattenedBackTo;
}

const flattenedNestedBackTo = getSearchRouteWithoutReportActionID(nestedBackTo);
if (flattenedNestedBackTo === nestedBackTo) {
return flattenedBackTo;
}

const queryStringIndex = flattenedBackTo.indexOf('?');
const path = queryStringIndex === -1 ? flattenedBackTo : flattenedBackTo.slice(0, queryStringIndex);
const queryString = queryStringIndex === -1 ? '' : flattenedBackTo.slice(queryStringIndex + 1);
const params = new URLSearchParams(queryString);
params.set('backTo', flattenedNestedBackTo);

return `${path}?${params.toString()}` as Route;
}

/**
* Calculate the URL to navigate to after a task deletion
* @param report - The task report being deleted
* @returns The URL to navigate to
*/
function getNavigationUrlOnTaskDelete(report: OnyxEntry<OnyxTypes.Report>, conciergeReportID: string | undefined): string | undefined {
function getNavigationUrlOnTaskDelete(report: OnyxEntry<OnyxTypes.Report>, conciergeReportID: string | undefined, backTo?: Route): Route | undefined {
if (!report) {
return undefined;
}

const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID);
if (!shouldDeleteTaskReport) {
return undefined;
if (backTo) {
return getFlattenedTaskDeleteBackTo(backTo);
}

if (report?.parentReportID) {
Expand Down Expand Up @@ -1200,6 +1263,7 @@ function deleteTask(
conciergeReportID: string | undefined,
delegateEmail: string | undefined,
ancestors: ReportUtils.Ancestor[] = [],
backTo?: Route,
) {
if (!report) {
return;
Expand Down Expand Up @@ -1324,9 +1388,14 @@ function deleteTask(
API.write(WRITE_COMMANDS.CANCEL_TASK, parameters, {optimisticData, successData, failureData});
notifyNewAction(report.reportID, undefined, true);

const urlToNavigateBack = getNavigationUrlOnTaskDelete(report, conciergeReportID);
const shouldNavigateAfterDelete = !!backTo || shouldDeleteTaskReport;
const urlToNavigateBack = shouldNavigateAfterDelete ? getNavigationUrlOnTaskDelete(report, conciergeReportID, backTo) : undefined;
if (urlToNavigateBack) {
Navigation.goBack();
if (backTo) {
Navigation.goBack(urlToNavigateBack, {compareParams: false});
} else {
Navigation.goBack();
}
return urlToNavigateBack;
}
}
Expand Down
Loading
Loading