Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
622a794
Skip the onboarding "add your work email" screen for validated accounts
blimpich May 20, 2026
6035759
Add unit test for validated-account skip in getOnboardingInitialPath
blimpich May 20, 2026
494a534
Hide work-email merge-validation screen from public-domain users
blimpich May 20, 2026
f8e2481
Apply prettier formatting
blimpich May 20, 2026
1b6cd6a
Block validation-screen URL from resuming onboarding for public-domai…
blimpich May 20, 2026
4a3a626
Render null on validation screen for public-domain accounts
blimpich May 20, 2026
db992cc
Hide PrivateDomain merge screen from public-domain users
blimpich May 20, 2026
57df1f6
Shorten comments per review feedback
blimpich May 20, 2026
4e6bb42
Extract VSB/SMB/PURPOSE redirect into a shared callback
blimpich May 20, 2026
e09837d
Set validated:false in work-email-validation test setup
blimpich May 20, 2026
6ca970c
Merge branch 'main' into blimpich-onboardingHideWorkEmailFromValidated
blimpich May 20, 2026
3cc8e4a
Narrow public-domain guards to only the PrivateDomain screen
blimpich May 20, 2026
7de9b52
Test public-domain user reaches work email validation screen
blimpich May 20, 2026
ecc1973
Test PrivateDomain redirects public-domain users by qualifier
blimpich May 20, 2026
305d737
Test getOnboardingInitialPath URL guard narrowing
blimpich May 20, 2026
c3ee090
Gate PRIVATE_DOMAIN skip on validated, not just public-domain
blimpich May 20, 2026
13268e4
Test staging behavior preserved for unvalidated public-domain users
blimpich May 20, 2026
1b2393b
Run prettier on BaseOnboardingWorkEmailValidation
blimpich May 21, 2026
b45ad2a
Gate private-domain URL guard on isAccountValidated
blimpich May 22, 2026
3c6c84e
Narrow useOnyx ACCOUNT subscription to fields actually read
blimpich May 27, 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
2 changes: 2 additions & 0 deletions src/hooks/useOnboardingFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function useOnboardingFlowRouter() {
currentOnboardingPurposeSelected: onboardingPurposeSelected,
onboardingInitialPath,
onboardingValues,
isAccountValidated: !!account?.validated,
});
});
}
Expand All @@ -125,6 +126,7 @@ function useOnboardingFlowRouter() {
onboardingValues,
account?.isFromPublicDomain,
account?.hasAccessibleDomainPolicies,
account?.validated,
onboardingCompanySize,
onboardingPurposeSelected,
onboardingInitialPath,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/guards/OnboardingGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ function getOnboardingRoute(): Route {
currentOnboardingPurposeSelected: onboardingPurposeSelected,
onboardingInitialPath,
onboardingValues: onboarding,
isAccountValidated: !!account?.validated,
}) as Route;
}

Expand Down
17 changes: 16 additions & 1 deletion src/libs/actions/Welcome/OnboardingFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type GetOnboardingInitialPathParamsType = {
currentOnboardingCompanySize: OnyxEntry<OnboardingCompanySize>;
onboardingInitialPath: OnyxEntry<string>;
onboardingValues: OnyxEntry<Onboarding>;
isAccountValidated?: boolean;
};

type OnboardingTaskLinks = Partial<{
Expand Down Expand Up @@ -107,6 +108,7 @@ function getOnboardingInitialPath(getOnboardingInitialPathParams: GetOnboardingI
currentOnboardingCompanySize,
onboardingInitialPath = '',
onboardingValues,
isAccountValidated,
} = getOnboardingInitialPathParams;
const state = getStateFromPath(onboardingInitialPath, linkingConfig.config);
const currentOnboardingValues = onboardingValuesParam ?? onboardingValues;
Expand All @@ -126,10 +128,23 @@ function getOnboardingInitialPath(getOnboardingInitialPathParams: GetOnboardingI
if (isIndividual) {
Onyx.set(ONYXKEYS.ONBOARDING_CUSTOM_CHOICES, [CONST.ONBOARDING_CHOICES.EMPLOYER, CONST.ONBOARDING_CHOICES.TRACK_BUSINESS, CONST.ONBOARDING_CHOICES.TRACK_PERSONAL]);
}
if (isUserFromPublicDomain && !onboardingValuesParam?.isMergeAccountStepCompleted) {
// A validated account has no reason to be on the onboarding "add work email" screen.
if (isUserFromPublicDomain && !onboardingValuesParam?.isMergeAccountStepCompleted && !isAccountValidated) {
return `/${ROUTES.ONBOARDING_WORK_EMAIL.route}`;
}

// PRIVATE_DOMAIN ("People you may know are already here") only makes sense for users on a private domain. Only redirect
// validated accounts; unvalidated users mid-AddWorkEmail can legitimately land here while isFromPublicDomain is stale.
if (isUserFromPublicDomain && isAccountValidated && onboardingInitialPath.includes(ROUTES.ONBOARDING_PRIVATE_DOMAIN.route)) {
if (isVsb) {
return `/${ROUTES.ONBOARDING_ACCOUNTING.route}`;
}
if (isSmb) {
return `/${ROUTES.ONBOARDING_EMPLOYEES.route}`;
}
return `/${ROUTES.ONBOARDING_PURPOSE.route}`;
}

if (!isUserFromPublicDomain && hasAccessiblePolicies) {
if (onboardingInitialPath) {
return onboardingInitialPath;
Expand Down
64 changes: 41 additions & 23 deletions src/pages/OnboardingPrivateDomain/BaseOnboardingPrivateDomain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard
const {translate} = useLocalize();
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {
selector: (acc) => ({
validated: acc?.validated,
isFromPublicDomain: acc?.isFromPublicDomain,
}),
});
const {isBetaEnabled} = usePermissions();
const canUseSubmit2026 = isBetaEnabled(CONST.BETAS.SUBMIT_2026);

Expand Down Expand Up @@ -65,14 +71,41 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard
Navigation.goBack(routeToNavigate);
}, [route.params?.backTo, onboardingValues]);

const navigateToNextOnboardingStep = useCallback(
(backTo: string | undefined, options?: {forceReplace?: boolean}) => {
if (isVsb) {
Navigation.navigate(ROUTES.ONBOARDING_ACCOUNTING.getRoute(backTo), options);
return;
}
if (isSmb) {
Navigation.navigate(ROUTES.ONBOARDING_EMPLOYEES.getRoute(backTo), options);
return;
}
Navigation.navigate(ROUTES.ONBOARDING_PURPOSE.getRoute(backTo), options);
},
[isVsb, isSmb],
);

// Only validated public-domain users are blocked from this screen — for them the "people on YOUR domain" copy would reference gmail.com.
// An unvalidated public-domain user who just submitted a work email may land here before isFromPublicDomain updates; that's the staging happy path.
const shouldBlockPublicDomain = !!account?.validated && !!account?.isFromPublicDomain;

useEffect(() => {
if (shouldBlockPublicDomain) {
return;
}
if (isValidated) {
return;
}
sendValidateCode();
}, [sendValidateCode, isValidated]);
}, [sendValidateCode, isValidated, shouldBlockPublicDomain]);

useEffect(() => {
if (shouldBlockPublicDomain) {
navigateToNextOnboardingStep(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(), {forceReplace: true});
return;
}

if (!isValidated) {
return;
}
Expand All @@ -85,17 +118,13 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard
// When validation succeeded but there are no joinable workspaces and the API call has completed,
// navigate to the next onboarding step (same as the skip button behavior).
if (getAccessiblePoliciesAction?.loading === false) {
if (isVsb) {
Navigation.navigate(ROUTES.ONBOARDING_ACCOUNTING.getRoute(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute()), {forceReplace: true});
return;
}
if (isSmb) {
Navigation.navigate(ROUTES.ONBOARDING_EMPLOYEES.getRoute(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute()), {forceReplace: true});
return;
}
Navigation.navigate(ROUTES.ONBOARDING_PURPOSE.getRoute(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute()), {forceReplace: true});
navigateToNextOnboardingStep(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(), {forceReplace: true});
}
}, [isValidated, joinablePoliciesLength, getAccessiblePoliciesAction?.loading, isVsb, isSmb]);
}, [isValidated, joinablePoliciesLength, getAccessiblePoliciesAction?.loading, shouldBlockPublicDomain, navigateToNextOnboardingStep]);

if (shouldBlockPublicDomain) {
return null;
}

return (
<ScreenWrapper
Expand Down Expand Up @@ -138,18 +167,7 @@ function BaseOnboardingPrivateDomain({shouldUseNativeStyles, route}: BaseOnboard
validateError={getAccessiblePoliciesAction?.errors}
hasMagicCodeBeenSent={hasMagicCodeBeenSent}
shouldShowSkipButton
handleSkipButtonPress={() => {
if (isVsb) {
Navigation.navigate(ROUTES.ONBOARDING_ACCOUNTING.getRoute(route.params?.backTo));
return;
}

if (isSmb) {
Navigation.navigate(ROUTES.ONBOARDING_EMPLOYEES.getRoute(route.params?.backTo));
return;
}
Navigation.navigate(ROUTES.ONBOARDING_PURPOSE.getRoute(route.params?.backTo));
}}
handleSkipButtonPress={() => navigateToNextOnboardingStep(route.params?.backTo)}
buttonStyles={[styles.flex2, styles.justifyContentEnd]}
isLoading={getAccessiblePoliciesAction?.loading}
/>
Expand Down
58 changes: 40 additions & 18 deletions src/pages/OnboardingWorkEmail/BaseOnboardingWorkEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ function BaseOnboardingWorkEmail({shouldUseNativeStyles}: BaseOnboardingWorkEmai
const illustrations = useMemoizedLazyIllustrations(['EnvelopeReceipt', 'Gears', 'Profile']);
const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {
selector: (acc) => ({
validated: acc?.validated,
isFromPublicDomain: acc?.isFromPublicDomain,
}),
});
const [formValue] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_WORK_EMAIL_FORM);
const workEmail = formValue?.[INPUT_IDS.ONBOARDING_WORK_EMAIL];
const [onboardingErrorMessage] = useOnyx(ONYXKEYS.ONBOARDING_ERROR_MESSAGE_TRANSLATION_KEY);
Expand All @@ -69,34 +75,50 @@ function BaseOnboardingWorkEmail({shouldUseNativeStyles}: BaseOnboardingWorkEmai
}, []);

useEffect(() => {
if (onboardingValues?.shouldValidate === undefined && onboardingValues?.isMergeAccountStepCompleted === undefined) {
return;
}
setOnboardingErrorMessage(null);
const navigateToNextStep = (shouldSkipPrivateDomain = false) => {
if (isVsb) {
Navigation.navigate(ROUTES.ONBOARDING_ACCOUNTING.getRoute(), {forceReplace: true});
return;
}
if (isSmb) {
Navigation.navigate(ROUTES.ONBOARDING_EMPLOYEES.getRoute(), {forceReplace: true});
return;
}
if (!shouldSkipPrivateDomain && !onboardingValues?.isMergeAccountStepSkipped) {
Navigation.navigate(ROUTES.ONBOARDING_PRIVATE_DOMAIN.getRoute(), {forceReplace: true});
return;
}
Navigation.navigate(ROUTES.ONBOARDING_PURPOSE.getRoute(), {forceReplace: true});
};

if (onboardingValues?.shouldValidate) {
Navigation.navigate(ROUTES.ONBOARDING_WORK_EMAIL_VALIDATION.getRoute());
return;
}
// Once we verify that shouldValidate is false, we need to force replace the screen
// so that we don't navigate back on back button press
if (isVsb) {
Navigation.navigate(ROUTES.ONBOARDING_ACCOUNTING.getRoute(), {forceReplace: true});
// A validated account has no reason to be on the onboarding "add work email" screen. For a public-domain primary the
// PRIVATE_DOMAIN screen would reference gmail.com (etc.) so skip it.
if (account?.validated) {
navigateToNextStep(account?.isFromPublicDomain);
return;
}

if (isSmb) {
Navigation.navigate(ROUTES.ONBOARDING_EMPLOYEES.getRoute(), {forceReplace: true});
if (onboardingValues?.shouldValidate === undefined && onboardingValues?.isMergeAccountStepCompleted === undefined) {
return;
}
setOnboardingErrorMessage(null);

if (!onboardingValues?.isMergeAccountStepSkipped) {
Navigation.navigate(ROUTES.ONBOARDING_PRIVATE_DOMAIN.getRoute(), {forceReplace: true});
if (onboardingValues?.shouldValidate) {
Navigation.navigate(ROUTES.ONBOARDING_WORK_EMAIL_VALIDATION.getRoute());
return;
}

Navigation.navigate(ROUTES.ONBOARDING_PURPOSE.getRoute(), {forceReplace: true});
}, [onboardingValues?.shouldValidate, isVsb, isSmb, isFocused, onboardingValues?.isMergeAccountStepCompleted, onboardingValues?.isMergeAccountStepSkipped]);
navigateToNextStep();
}, [
account?.validated,
account?.isFromPublicDomain,
onboardingValues?.shouldValidate,
isVsb,
isSmb,
isFocused,
onboardingValues?.isMergeAccountStepCompleted,
onboardingValues?.isMergeAccountStepSkipped,
]);

const submitWorkEmail = useCallback((values: FormOnyxValues<typeof ONYXKEYS.FORMS.ONBOARDING_WORK_EMAIL_FORM>) => {
AddWorkEmail(values[INPUT_IDS.ONBOARDING_WORK_EMAIL].trim());
Expand Down
Loading
Loading