Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
17 changes: 17 additions & 0 deletions src/libs/PendingConciergeDeepLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
let hasPendingConciergeDeepLink = false;
Comment thread
x-dev90 marked this conversation as resolved.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist the Concierge intent across page reloads

On web, this module-scoped flag is reset whenever the document reloads. If a user opens /concierge while logged out and refreshes during sign-in or onboarding, the browser is already displaying a sign-in/onboarding route, so openReportFromDeepLink() cannot reconstruct the original Concierge intent; navigateAfterOnboarding() then falls back to Home. Store the tab-scoped intent in persistent state such as sessionStorage or Onyx and clear it through the existing consume/clear paths.

Useful? React with 👍 / 👎.


function setPendingConciergeDeepLink() {
hasPendingConciergeDeepLink = true;
}

function consumePendingConciergeDeepLink() {
const shouldNavigateToConcierge = hasPendingConciergeDeepLink;
hasPendingConciergeDeepLink = false;
return shouldNavigateToConcierge;
}

function clearPendingConciergeDeepLink() {
hasPendingConciergeDeepLink = false;
}

export {setPendingConciergeDeepLink, consumePendingConciergeDeepLink, clearPendingConciergeDeepLink};
15 changes: 15 additions & 0 deletions src/libs/actions/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Navigation from '@libs/Navigation/Navigation';
import navigationRef from '@libs/Navigation/navigationRef';
import REPORT_LINK_ROUTE_PARAMS from '@libs/Navigation/reportLinkRouteParams';
import {getIsOffline} from '@libs/NetworkState';
import {clearPendingConciergeDeepLink, setPendingConciergeDeepLink} from '@libs/PendingConciergeDeepLink';
import {findLastAccessedReport, getReportIDFromLink, getReportOrDraftReport, getRouteFromLink, isMoneyRequestReport} from '@libs/ReportUtils';
import shouldSkipDeepLinkNavigation from '@libs/shouldSkipDeepLinkNavigation';
import {endSpan, getSpan, startSpan} from '@libs/telemetry/activeSpans';
Expand Down Expand Up @@ -451,6 +452,12 @@ function openLink(href: string, environmentURL: string, isAttachment = false) {
openExternalLink(href);
}

function isConciergeRoute(route: string) {
const [routeWithoutParams] = normalizePath(route).split(/[?#]/, 1);
const normalizedRoute = routeWithoutParams.replace(/\/$/, '');
return normalizedRoute === normalizePath(ROUTES.CONCIERGE);
}

function openReportFromDeepLink(
url: string,
reports: OnyxCollection<Report>,
Expand Down Expand Up @@ -496,6 +503,14 @@ function openReportFromDeepLink(
route = '';
}

if (!isAuthenticated) {
if (isConciergeRoute(route)) {
setPendingConciergeDeepLink();
} else {
clearPendingConciergeDeepLink();
}
}

// If we are not authenticated and are navigating to a public screen, we don't want to navigate again to the screen after sign-in/sign-up
if (!isAuthenticated && isPublicScreenRoute(route)) {
return;
Expand Down
3 changes: 3 additions & 0 deletions src/libs/actions/SignInRedirect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {getMicroSecondOnyxErrorWithMessage} from '@libs/ErrorUtils';
import {clearSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
import {getIsOffline} from '@libs/NetworkState';
import {clearPendingConciergeDeepLink} from '@libs/PendingConciergeDeepLink';

import CONFIG from '@src/CONFIG';
import type {OnyxKey} from '@src/ONYXKEYS';
Expand Down Expand Up @@ -47,6 +48,8 @@ Onyx.connectWithoutView({
});

function clearStorageAndRedirect(errorMessage?: string, isSAMLReauthentication?: boolean): Promise<void> {
clearPendingConciergeDeepLink();

// Under certain conditions, there are key-values we'd like to keep in storage even when a user is logged out.
// We pass these into the clear() method in order to avoid having to reset them on a delayed tick and getting
// flashes of unwanted default state.
Expand Down
3 changes: 3 additions & 0 deletions src/libs/navigateAfterOnboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SidePanelActions from './actions/SidePanel';
import {setOnboardingRHPVariant} from './actions/Welcome';
import shouldOpenOnAdminRoom from './Navigation/helpers/shouldOpenOnAdminRoom';
import Navigation from './Navigation/Navigation';
import {consumePendingConciergeDeepLink} from './PendingConciergeDeepLink';
import {findLastAccessedReport, isConciergeChatReport, isSelfDM} from './ReportUtils';

let onboardingRHPVariant: OnyxEntry<OnboardingRHPVariant>;
Expand Down Expand Up @@ -99,6 +100,8 @@ function navigateAfterOnboarding(
);
if (reportID) {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
} else if (consumePendingConciergeDeepLink()) {
Navigation.navigate(conciergeReportID ? ROUTES.REPORT_WITH_ID.getRoute(conciergeReportID) : (ROUTES.CONCIERGE as Route));
} else {
// Navigate to home to trigger guard evaluation
Navigation.navigate(ROUTES.HOME);
Expand Down
33 changes: 33 additions & 0 deletions tests/unit/navigateAfterOnboardingTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {navigateAfterOnboarding} from '@libs/navigateAfterOnboarding';
import Navigation from '@libs/Navigation/Navigation';
import {clearPendingConciergeDeepLink, setPendingConciergeDeepLink} from '@libs/PendingConciergeDeepLink';
import type * as ReportUtils from '@libs/ReportUtils';

import initOnyxDerivedValues from '@userActions/OnyxDerived';
Expand Down Expand Up @@ -62,6 +63,7 @@ describe('navigateAfterOnboarding', () => {

beforeEach(async () => {
jest.clearAllMocks();
clearPendingConciergeDeepLink();
return Onyx.clear();
});

Expand Down Expand Up @@ -155,4 +157,35 @@ describe('navigateAfterOnboarding', () => {
navigateAfterOnboarding(false, true, '', {}, undefined, ONBOARDING_ADMINS_CHAT_REPORT_ID, false, CONST.ONBOARDING_RHP_VARIANT.INBOX_ADMINS_BESPOKE);
expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(ONBOARDING_ADMINS_CHAT_REPORT_ID));
});

it('should navigate to Concierge instead of Home when a pending Concierge deep link is available', () => {
const navigate = jest.spyOn(Navigation, 'navigate');
setPendingConciergeDeepLink();

navigateAfterOnboarding(false, true, REPORT_ID, {}, undefined, undefined);

expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(REPORT_ID));
expect(navigate).not.toHaveBeenCalledWith(ROUTES.HOME);
});

it('should navigate to Concierge route when pending deep link is set but conciergeReportID is empty', () => {
const navigate = jest.spyOn(Navigation, 'navigate');
setPendingConciergeDeepLink();

navigateAfterOnboarding(false, true, '', {}, undefined, undefined);

expect(navigate).toHaveBeenCalledWith(ROUTES.CONCIERGE);
expect(navigate).not.toHaveBeenCalledWith(ROUTES.HOME);
});

it('should consume the pending Concierge deep link after onboarding navigation', () => {
const navigate = jest.spyOn(Navigation, 'navigate');
setPendingConciergeDeepLink();

navigateAfterOnboarding(false, true, REPORT_ID, {}, undefined, undefined);
navigateAfterOnboarding(false, true, REPORT_ID, {}, undefined, undefined);

expect(navigate).toHaveBeenNthCalledWith(1, ROUTES.REPORT_WITH_ID.getRoute(REPORT_ID));
expect(navigate).toHaveBeenNthCalledWith(2, ROUTES.HOME);
});
});
Loading