Skip to content

Commit f843d86

Browse files
committed
Act on the Submit deeplink only for users who already onboarded
The link is scoped to existing users with an intent already set, so anyone who still has guided setup ahead of them is left to the normal onboarding flow, which already offers the Submit outcome. That removes the need to suppress onboarding at all, so the router, the navigation guard and the shared workspace-creation hook go back to their original behaviour. Also corrects a stale comment describing the post-creation destination as Categories; the shared helper navigates to Spend with #admins in the side panel.
1 parent 06e18f0 commit f843d86

8 files changed

Lines changed: 23 additions & 71 deletions

File tree

src/ONYXKEYS.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {ValueOf} from 'type-fest';
22

33
import type CONST from './CONST';
4-
import type {OnboardingAccounting, OnboardingIntent} from './CONST';
4+
import type {OnboardingAccounting} from './CONST';
55
import type {TranslationPaths} from './languages/types';
66
import type {OnboardingFeatureMapItem} from './libs/actions/Welcome/OnboardingFeatures';
77
import type {OnboardingCompanySize} from './libs/actions/Welcome/OnboardingFlow';
@@ -548,9 +548,6 @@ const ONYXKEYS = {
548548
/** Onboarding customized choices to display to the user based on their profile when signing up */
549549
ONBOARDING_CUSTOM_CHOICES: 'onboardingCustomChoices',
550550

551-
/** Onboarding outcome requested by the deeplink this session was opened with, e.g. `onboarding?intent=submit` */
552-
ONBOARDING_DEEPLINK_INTENT: 'onboardingDeeplinkIntent',
553-
554551
/** Onboarding error message translation key to be displayed to the user */
555552
ONBOARDING_ERROR_MESSAGE_TRANSLATION_KEY: 'onboardingErrorMessageTranslationKey',
556553

@@ -1713,7 +1710,6 @@ type OnyxValuesMapping = {
17131710
[ONYXKEYS.ONBOARDING_COMPANY_SIZE]: OnboardingCompanySize;
17141711
[ONYXKEYS.ONBOARDING_PERSONAL_TRACK_GOAL]: string;
17151712
[ONYXKEYS.ONBOARDING_CUSTOM_CHOICES]: OnyxTypes.OnboardingPurpose[] | [];
1716-
[ONYXKEYS.ONBOARDING_DEEPLINK_INTENT]: OnboardingIntent;
17171713
[ONYXKEYS.ONBOARDING_ERROR_MESSAGE_TRANSLATION_KEY]: TranslationPaths;
17181714
[ONYXKEYS.ONBOARDING_POLICY_ID]: string;
17191715
[ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string;

src/components/SubmitPlanWelcomeModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ function SubmitPlanWelcomeModal() {
4949

5050
const handleConfirm = () => {
5151
// The user has already completed onboarding, so we skip CompleteGuidedSetup and just create the
52-
// Submit workspace. autoCreateSubmitWorkspace then dismisses this modal and navigates to Categories
53-
// with #admins in the RHP, which triggers the useBeforeRemove persistence above.
52+
// Submit workspace. autoCreateSubmitWorkspace then dismisses this modal and navigates to Spend
53+
// with #admins in the side panel, which triggers the useBeforeRemove persistence above.
5454
autoCreateSubmitWorkspace(firstName ?? '', lastName ?? '', false);
5555
};
5656

src/hooks/useAutoCreateSubmitWorkspace.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {canEditWorkspaceSettings, isGroupPolicy, isSubmitPolicy} from '@libs/Pol
55

66
import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy';
77
import {completeOnboarding} from '@userActions/Report';
8-
import {setOnboardingAdminsChatReportID, setOnboardingDeeplinkIntent, setOnboardingPolicyID} from '@userActions/Welcome';
8+
import {setOnboardingAdminsChatReportID, setOnboardingPolicyID} from '@userActions/Welcome';
99

1010
import CONST from '@src/CONST';
1111
import ONYXKEYS from '@src/ONYXKEYS';
@@ -116,9 +116,6 @@ function useAutoCreateSubmitWorkspace() {
116116

117117
setOnboardingAdminsChatReportID();
118118
setOnboardingPolicyID();
119-
// Both deeplink paths (the onboarding flow and the already-onboarded handler) end up here, so this is the
120-
// one place that reliably retires a Submit deeplink intent once it has been honoured.
121-
setOnboardingDeeplinkIntent(null);
122119

123120
// Already-onboarded callers (the Submit plan welcome modal) can reach this point with no workspace
124121
// created and no onboarding policy ID when an editable Submit workspace already exists. Navigate to

src/hooks/useOnboardingDeeplinkIntent.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,21 @@ import getOnboardingIntentFromUrl from '@libs/getOnboardingIntentFromUrl';
44
import getCurrentUrl from '@libs/Navigation/currentUrl';
55

66
import type {OnboardingIntent} from '@src/CONST';
7-
import ONYXKEYS from '@src/ONYXKEYS';
87

98
import {useState} from 'react';
109

11-
import useOnyx from './useOnyx';
12-
1310
/**
1411
* Resolves the onboarding outcome requested by the deeplink this session was opened with, e.g. `onboarding?intent=submit`.
1512
*
16-
* All three sources are needed. The stored copy is the durable one but is written asynchronously, so it is absent
17-
* during the first render, which is exactly when the onboarding navigator picks its entry step. The browser URL covers
18-
* that first render but is rewritten as soon as the flow navigates, hence the mount-time latch. The initial URL is the
19-
* only source on native, where the browser URL is empty and the deeplink resolves asynchronously.
13+
* The URL is latched at mount because the app rewrites it as soon as it navigates, which happens well before the
14+
* intent has been acted on. The initial URL is the only source on native, where the browser URL is empty and the
15+
* deeplink resolves asynchronously.
2016
*/
2117
function useOnboardingDeeplinkIntent(): OnboardingIntent | undefined {
2218
const {initialURL} = useInitialURLState();
2319
const [urlAtMount] = useState(getCurrentUrl);
24-
const [storedIntent] = useOnyx(ONYXKEYS.ONBOARDING_DEEPLINK_INTENT);
2520

26-
return storedIntent ?? getOnboardingIntentFromUrl(urlAtMount) ?? getOnboardingIntentFromUrl(initialURL);
21+
return getOnboardingIntentFromUrl(urlAtMount) ?? getOnboardingIntentFromUrl(initialURL);
2722
}
2823

2924
export default useOnboardingDeeplinkIntent;

src/hooks/useOnboardingFlow.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {completeHybridAppOnboarding} from '@userActions/Welcome';
1010
import {startOnboardingFlow} from '@userActions/Welcome/OnboardingFlow';
1111

1212
import CONFIG from '@src/CONFIG';
13-
import CONST from '@src/CONST';
1413
import ONYXKEYS from '@src/ONYXKEYS';
1514
import ROUTES from '@src/ROUTES';
1615
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
@@ -20,7 +19,6 @@ import {hasCompletedGuidedSetupFlowSelector, tryNewDotOnyxSelector, wasInvitedTo
2019
import {emailSelector} from '@selectors/Session';
2120
import {useCallback, useEffect} from 'react';
2221

23-
import useOnboardingDeeplinkIntent from './useOnboardingDeeplinkIntent';
2422
import useOnyx from './useOnyx';
2523
import useShouldSuppressPromotionalUI from './useShouldSuppressPromotionalUI';
2624

@@ -55,8 +53,6 @@ function useOnboardingFlowRouter() {
5553
const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED);
5654
const [onboardingCompanySize] = useOnyx(ONYXKEYS.ONBOARDING_COMPANY_SIZE);
5755
const [onboardingInitialPath] = useOnyx(ONYXKEYS.ONBOARDING_LAST_VISITED_PATH);
58-
// A Submit deeplink creates the workspace outright, so there is nothing left for onboarding to ask.
59-
const hasSubmitDeeplinkIntent = useOnboardingDeeplinkIntent() === CONST.ONBOARDING_INTENTS.SUBMIT;
6056
const [hasNonPersonalPolicy] = useOnyx(ONYXKEYS.HAS_NON_PERSONAL_POLICY);
6157
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
6258
const wasInvitedToNewDot = wasInvitedToNewDotSelector(introSelected);
@@ -82,10 +78,6 @@ function useOnboardingFlowRouter() {
8278
return;
8379
}
8480

85-
if (hasSubmitDeeplinkIntent) {
86-
return;
87-
}
88-
8981
if (isLoadingApp !== false || isOnboardingLoading) {
9082
return;
9183
}
@@ -166,7 +158,6 @@ function useOnboardingFlowRouter() {
166158
onboardingCompanySize,
167159
onboardingPurposeSelected,
168160
onboardingInitialPath,
169-
hasSubmitDeeplinkIntent,
170161
hasBeenAddedToNudgeMigration,
171162
hasNonPersonalPolicy,
172163
wasInvitedToNewDot,
@@ -176,9 +167,8 @@ function useOnboardingFlowRouter() {
176167
]);
177168

178169
return {
179-
// Treat the flow as completed for secure-link visitors so the onboarding modal is not mounted over the report,
180-
// and for Submit deeplink visitors so it is not mounted over the workspace being created for them.
181-
isOnboardingCompleted: isVisitingSecureLink || hasSubmitDeeplinkIntent ? true : hasCompletedGuidedSetupFlowSelector(onboardingValues),
170+
// Treat the flow as completed for secure-link visitors so the onboarding modal is not mounted over the report.
171+
isOnboardingCompleted: isVisitingSecureLink ? true : hasCompletedGuidedSetupFlowSelector(onboardingValues),
182172
isHybridAppOnboardingCompleted,
183173
isOnboardingLoading: !!onboardingValues?.isLoading,
184174
};

src/libs/Navigation/AppNavigator/SubmitIntentDeeplinkHandler/ApplySubmitOnboardingIntent.tsx

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'
33
import useOnyx from '@hooks/useOnyx';
44

55
import {setSubmitMigrationModalShown} from '@userActions/User';
6-
import {setOnboardingDeeplinkIntent} from '@userActions/Welcome';
76

8-
import CONST from '@src/CONST';
97
import ONYXKEYS from '@src/ONYXKEYS';
108

119
import {hasCompletedGuidedSetupFlowSelector} from '@selectors/Onboarding';
@@ -15,10 +13,10 @@ import {useEffect, useRef} from 'react';
1513
/**
1614
* Creates the Submit workspace requested by an `intent=submit` onboarding deeplink.
1715
*
18-
* The link is sent to existing users, so it deliberately skips the onboarding UI entirely rather than pre-answering
19-
* its questions: the workspace is created outright and the user lands wherever the "Submit to my employer" flow
20-
* normally leaves them. Onboarding is suppressed for the whole session by useOnboardingFlowRouter and OnboardingGuard,
21-
* both of which read the intent recorded here.
16+
* The link is only sent to existing users, so it acts solely on recipients who have already been through guided
17+
* setup: for them the workspace is created outright and they land wherever the "Submit to my employer" flow normally
18+
* leaves them. Anyone who still has onboarding ahead of them is left to it untouched, since that flow already offers
19+
* the Submit outcome.
2220
*
2321
* Only rendered once the deeplink has been recognised, so the Onyx subscriptions behind `useAutoCreateSubmitWorkspace`
2422
* are never set up for ordinary sessions.
@@ -34,30 +32,28 @@ function ApplySubmitOnboardingIntent() {
3432
const [isOnboardingCompleted] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasCompletedGuidedSetupFlowSelector});
3533
const [isSupportalSession] = useOnyx(ONYXKEYS.SESSION, {selector: isSupportalSessionSelector});
3634

37-
const hasCreatedWorkspace = useRef(false);
35+
const hasRun = useRef(false);
3836

3937
useEffect(() => {
40-
if (isSupportalSession) {
38+
if (hasRun.current || !hasLoadedApp || isOnboardingCompleted === undefined || isSupportalSession) {
4139
return;
4240
}
43-
setOnboardingDeeplinkIntent(CONST.ONBOARDING_INTENTS.SUBMIT);
44-
}, [isSupportalSession]);
41+
hasRun.current = true;
4542

46-
useEffect(() => {
47-
if (hasCreatedWorkspace.current || !hasLoadedApp || isOnboardingCompleted === undefined || isSupportalSession) {
43+
// Recipients who never finished guided setup are left to the normal onboarding flow, which already offers
44+
// the Submit outcome.
45+
if (!isOnboardingCompleted) {
4846
return;
4947
}
50-
hasCreatedWorkspace.current = true;
5148

5249
// The deeplink delivers the same outcome as the Submit plan welcome modal, so record the modal as seen to
5350
// stop it from opening on top of the workspace we're about to create.
5451
setSubmitMigrationModalShown();
5552

56-
// Recipients who never finished guided setup still need it marked complete, otherwise they would be pulled
57-
// into onboarding on their next sign-in. When the user already owns a Submit workspace,
53+
// Guided setup is already done, so it must not run again. When the user already owns a Submit workspace,
5854
// useAutoCreateSubmitWorkspace skips creation and navigates to that workspace instead, which is what makes
5955
// repeat clicks of the link idempotent.
60-
autoCreateSubmitWorkspace(firstName ?? '', lastName ?? '', !isOnboardingCompleted);
56+
autoCreateSubmitWorkspace(firstName ?? '', lastName ?? '', false);
6157
}, [autoCreateSubmitWorkspace, firstName, hasLoadedApp, isOnboardingCompleted, isSupportalSession, lastName]);
6258

6359
return null;

src/libs/Navigation/guards/OnboardingGuard.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {isOnboardingFlowName} from '@libs/Navigation/helpers/isNavigatorName';
55
import {getOnboardingInitialPath} from '@userActions/Welcome/OnboardingFlow';
66

77
import CONFIG from '@src/CONFIG';
8-
import type {OnboardingIntent} from '@src/CONST';
98
import CONST from '@src/CONST';
109
import NAVIGATORS from '@src/NAVIGATORS';
1110
import ONYXKEYS from '@src/ONYXKEYS';
@@ -39,7 +38,6 @@ let hybridApp: {isSingleNewDotEntry?: boolean} | undefined;
3938
let onboardingPurposeSelected: OnyxEntry<OnboardingPurpose>;
4039
let onboardingCompanySize: OnyxEntry<OnboardingCompanySize>;
4140
let onboardingInitialPath: OnyxEntry<string>;
42-
let onboardingDeeplinkIntent: OnyxEntry<OnboardingIntent>;
4341
let hasNonPersonalPolicy: OnyxEntry<boolean>;
4442
let wasInvitedToNewDot: boolean | undefined;
4543

@@ -92,14 +90,6 @@ Onyx.connectWithoutView({
9290
},
9391
});
9492

95-
// A Submit deeplink creates the workspace outright, so the guard must not pull the user into onboarding on the way.
96-
Onyx.connectWithoutView({
97-
key: ONYXKEYS.ONBOARDING_DEEPLINK_INTENT,
98-
callback: (value) => {
99-
onboardingDeeplinkIntent = value;
100-
},
101-
});
102-
10393
Onyx.connectWithoutView({
10494
key: ONYXKEYS.HAS_NON_PERSONAL_POLICY,
10595
callback: (value) => {
@@ -186,7 +176,6 @@ const OnboardingGuard: NavigationGuard = {
186176
const isMigratedUser = tryNewDot?.hasBeenAddedToNudgeMigration ?? false;
187177
const isSingleEntry = hybridApp?.isSingleNewDotEntry ?? false;
188178
const isFirstTimeHybridAppTransition = (CONFIG.IS_HYBRID_APP && tryNewDot?.isHybridAppOnboardingCompleted !== true) ?? false;
189-
const hasSubmitDeeplinkIntent = onboardingDeeplinkIntent === CONST.ONBOARDING_INTENTS.SUBMIT;
190179
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
191180
const isInvitedOrGroupMember = (hasNonPersonalPolicy || wasInvitedToNewDot) ?? false;
192181

@@ -220,7 +209,6 @@ const OnboardingGuard: NavigationGuard = {
220209
isSingleEntry ||
221210
isFirstTimeHybridAppTransition ||
222211
isNavigatingWithReplace ||
223-
hasSubmitDeeplinkIntent ||
224212
context.isSupportalSession ||
225213
// Copilots should not be pushed through onboarding on behalf of the account they are accessing
226214
isActingAsDelegateSelector(account);
@@ -251,7 +239,6 @@ const OnboardingGuard: NavigationGuard = {
251239
isFirstTimeHybridAppTransition,
252240
isInvitedOrGroupMember,
253241
isNavigatingWithReplace,
254-
hasSubmitDeeplinkIntent,
255242
});
256243

257244
return {

src/libs/actions/Welcome/index.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Log from '@libs/Log';
66
import Navigation from '@libs/Navigation/Navigation';
77

88
import CONFIG from '@src/CONFIG';
9-
import type {OnboardingAccounting, OnboardingIntent} from '@src/CONST';
9+
import type {OnboardingAccounting} from '@src/CONST';
1010
import type {TranslationPaths} from '@src/languages/types';
1111
import ONYXKEYS from '@src/ONYXKEYS';
1212
import ROUTES from '@src/ROUTES';
@@ -52,14 +52,6 @@ function setOnboardingPurposeSelected(value: OnboardingPurpose) {
5252
Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, value ?? null);
5353
}
5454

55-
/**
56-
* Records the onboarding outcome requested by the deeplink this session was opened with, so the onboarding flow can
57-
* route straight to it. Cleared once acted on, and by the Onyx wipe on sign-out.
58-
*/
59-
function setOnboardingDeeplinkIntent(value: OnboardingIntent | null) {
60-
Onyx.set(ONYXKEYS.ONBOARDING_DEEPLINK_INTENT, value);
61-
}
62-
6355
function setOnboardingCompanySize(value: OnboardingCompanySize) {
6456
Onyx.set(ONYXKEYS.ONBOARDING_COMPANY_SIZE, value);
6557
}
@@ -225,7 +217,6 @@ export {
225217
onServerDataReady,
226218
dismissProductTraining,
227219
setOnboardingPurposeSelected,
228-
setOnboardingDeeplinkIntent,
229220
updateOnboardingLastVisitedPath,
230221
resetAllChecks,
231222
setOnboardingAdminsChatReportID,

0 commit comments

Comments
 (0)