Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give users on a domain the ability to join their colleagues when the company is already using Expensify #51681

Merged
merged 25 commits into from
Dec 13, 2024
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ad6007b
onboarding flow without errors
allroundexperts Nov 3, 2024
fa273c3
Merge branch 'main' into feat-48189
allroundexperts Nov 6, 2024
7c5c611
working version
allroundexperts Nov 6, 2024
3cba659
Merge branch 'main' into feat-48189
allroundexperts Nov 6, 2024
938bdf2
WIP: ad6007b6398 onboarding flow without errors
allroundexperts Nov 9, 2024
a107528
fix: handle comments
allroundexperts Nov 10, 2024
79cdc1c
merge with main
allroundexperts Nov 13, 2024
4737f1a
add onboarding wrapper
allroundexperts Nov 13, 2024
dd29fd8
Merge branch 'main' into feat-48189
allroundexperts Nov 24, 2024
467f79d
Merge branch 'main' into feat-48189
allroundexperts Nov 26, 2024
9672b4a
fix errors
allroundexperts Nov 27, 2024
5a8d697
fix lint errors
allroundexperts Nov 27, 2024
cc7ce8c
complete onboarding
allroundexperts Nov 27, 2024
3fa2478
Merge branch 'main' into feat-48189
allroundexperts Nov 27, 2024
e633f90
merge with main
allroundexperts Dec 2, 2024
b2859e0
Merge branch 'main' into feat-48189
allroundexperts Dec 5, 2024
dc13807
make the whole row not clickable
allroundexperts Dec 5, 2024
ebbbbdd
merge with main
allroundexperts Dec 9, 2024
1572b2e
merge with main
allroundexperts Dec 9, 2024
44b73c4
Merge branch 'main' into feat-48189
allroundexperts Dec 12, 2024
cf70577
handle comments
allroundexperts Dec 12, 2024
3c497ac
fix: lint checks
allroundexperts Dec 12, 2024
9bf5636
disable private flow
allroundexperts Dec 12, 2024
0dd74d0
remove unwanted files
allroundexperts Dec 13, 2024
c71dacd
Merge branch 'main' into feat-48189
marcaaron Dec 13, 2024
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
8 changes: 8 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
@@ -104,6 +104,12 @@ const ONYXKEYS = {
/** Store the information of magic code */
VALIDATE_ACTION_CODE: 'validate_action_code',

/** A list of policies that a user can join */
JOINABLE_POLICIES: 'joinablePolicies',

/** Flag to indicate if the joinablePolicies are loading */
JOINABLE_POLICIES_LOADING: 'joinablePoliciesLoading',

/** Information about the current session (authToken, accountID, email, loading, error) */
SESSION: 'session',
STASHED_SESSION: 'stashedSession',
@@ -903,6 +909,8 @@ type OnyxValuesMapping = {
[ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList;
[ONYXKEYS.PENDING_CONTACT_ACTION]: OnyxTypes.PendingContactAction;
[ONYXKEYS.VALIDATE_ACTION_CODE]: OnyxTypes.ValidateMagicCodeAction;
[ONYXKEYS.JOINABLE_POLICIES]: OnyxTypes.JoinablePolicies;
[ONYXKEYS.JOINABLE_POLICIES_LOADING]: boolean;
[ONYXKEYS.SESSION]: OnyxTypes.Session;
[ONYXKEYS.USER_METADATA]: OnyxTypes.UserMetadata;
[ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session;
8 changes: 8 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
@@ -1373,6 +1373,10 @@ const ROUTES = {
route: 'onboarding/personal-details',
getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/personal-details`, backTo),
},
ONBOARDING_PRIVATE_DOMAIN: {
route: 'onboarding/private-domain',
getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/private-domain`, backTo),
},
ONBOARDING_EMPLOYEES: {
route: 'onboarding/employees',
getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/employees`, backTo),
@@ -1385,6 +1389,10 @@ const ROUTES = {
route: 'onboarding/purpose',
getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/purpose`, backTo),
},
ONBOARDING_WORKSPACES: {
route: 'onboarding/join-workspaces',
getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/join-workspaces`, backTo),
},
WELCOME_VIDEO_ROOT: 'onboarding/welcome-video',
EXPLANATION_MODAL_ROOT: 'onboarding/explanation',
MIGRATED_USER_WELCOME_MODAL: 'onboarding/migrated-user-welcome',
2 changes: 2 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
@@ -579,8 +579,10 @@ const SCREENS = {
ONBOARDING: {
PERSONAL_DETAILS: 'Onboarding_Personal_Details',
PURPOSE: 'Onboarding_Purpose',
PRIVATE_DOMAIN: 'Onboarding_Private_Domain',
EMPLOYEES: 'Onboarding_Employees',
ACCOUNTING: 'Onboarding_Accounting',
WORKSPACES: 'Onboarding_Workspaces',
},

WELCOME_VIDEO: {
23 changes: 23 additions & 0 deletions src/components/OnboardingWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import {View} from 'react-native';
import useThemeStyles from '@hooks/useThemeStyles';
import FocusTrapForScreens from './FocusTrap/FocusTrapForScreen';

type OnboardingWrapperProps = {
/** Rendered child component */
children: React.ReactNode;
};

function OnboardingWrapper({children}: OnboardingWrapperProps) {
const styles = useThemeStyles();

return (
<FocusTrapForScreens>
<View style={styles.h100}>{children}</View>
</FocusTrapForScreens>
);
}

OnboardingWrapper.displayName = 'OnboardingWrapper';

export default OnboardingWrapper;
Original file line number Diff line number Diff line change
@@ -64,6 +64,9 @@ type ValidateCodeFormProps = {
/** Function to clear error of the form */
clearError: () => void;

/** Whether to show the verify button */
hideSubmitButton?: boolean;

/** Function is called when validate code modal is mounted and on magic code resend */
sendValidateCode: () => void;

@@ -82,6 +85,7 @@ function BaseValidateCodeForm({
clearError,
sendValidateCode,
buttonStyles,
hideSubmitButton,
isLoading,
}: ValidateCodeFormProps) {
const {translate} = useLocalize();
@@ -264,16 +268,18 @@ function BaseValidateCodeForm({
onClose={() => clearError()}
style={buttonStyles}
>
<Button
isDisabled={isOffline}
text={translate('common.verify')}
onPress={validateAndSubmitForm}
style={[styles.mt4]}
success
large
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
isLoading={account?.isLoading || isLoading}
/>
{!hideSubmitButton && (
<Button
isDisabled={isOffline}
text={translate('common.verify')}
onPress={validateAndSubmitForm}
style={[styles.mt4]}
success
large
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
isLoading={account?.isLoading || isLoading}
/>
)}
</OfflineWithFeedback>
</>
);
9 changes: 7 additions & 2 deletions src/hooks/useOnboardingFlow.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useEffect} from 'react';
import {NativeModules} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import * as LoginUtils from '@libs/LoginUtils';
import Navigation from '@libs/Navigation/Navigation';
import {hasCompletedGuidedSetupFlowSelector, tryNewDotOnyxSelector} from '@libs/onboardingSelectors';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
@@ -25,6 +26,9 @@ function useOnboardingFlowRouter() {

const [dismissedProductTraining, dismissedProductTrainingMetadata] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING);

const [session] = useOnyx(ONYXKEYS.SESSION);
const isPrivateDomain = !!session?.email && !LoginUtils.isEmailPublicDomain(session?.email);
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume this should still be false and use isUserOnPrivateDomain method


const [isSingleNewDotEntry, isSingleNewDotEntryMetadata] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY);
useEffect(() => {
if (isLoadingOnyxValue(isOnboardingCompletedMetadata, tryNewDotdMetadata, dismissedProductTrainingMetadata)) {
@@ -57,13 +61,13 @@ function useOnboardingFlowRouter() {
// But if the hybrid app onboarding is completed, but NewDot onboarding is not completed, we start NewDot onboarding flow
// This is a special case when user created an account from NewDot without finishing the onboarding flow and then logged in from OldDot
if (isHybridAppOnboardingCompleted === true && isOnboardingCompleted === false) {
OnboardingFlow.startOnboardingFlow();
OnboardingFlow.startOnboardingFlow(isPrivateDomain);
}
}

// If the user is not transitioning from OldDot to NewDot, we should start NewDot onboarding flow if it's not completed yet
if (!NativeModules.HybridAppModule && isOnboardingCompleted === false) {
OnboardingFlow.startOnboardingFlow();
OnboardingFlow.startOnboardingFlow(isPrivateDomain);
}
}, [
isOnboardingCompleted,
@@ -76,6 +80,7 @@ function useOnboardingFlowRouter() {
dismissedProductTrainingMetadata,
dismissedProductTraining?.migratedUserWelcomeModal,
dismissedProductTraining,
isPrivateDomain,
]);

return {isOnboardingCompleted, isHybridAppOnboardingCompleted};
12 changes: 12 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
@@ -192,7 +192,9 @@ import type {
WelcomeNoteParams,
WelcomeToRoomParams,
WeSentYouMagicSignInLinkParams,
WorkspaceMemberList,
WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams,
WorkspaceYouMayJoin,
YourPlanPriceParams,
ZipCodeExampleFormatParams,
} from './params';
@@ -478,6 +480,7 @@ const translations = {
links: 'Links',
days: 'days',
rename: 'Rename',
skip: 'Skip',
chatWithAccountManager: ({accountManagerDisplayName}: ChatWithAccountManagerParams) => `Need something specific? Chat with your account manager, ${accountManagerDisplayName}.`,
chatNow: 'Chat now',
},
@@ -1753,6 +1756,11 @@ const translations = {
},
getStarted: 'Get started',
whatsYourName: "What's your name?",
peopleYouMayKnow: 'People you may know are already here! Verify your email to join them.',
workspaceYouMayJoin: ({domain, email}: WorkspaceYouMayJoin) => `Someone from ${domain} has already created a workspace. Please enter the magic code sent to ${email}.`,
joinAWorkspace: 'Join a workspace',
listOfWorkspaces: "Here's the list of workspaces you can join. Don't worry, you can always join them later if you prefer.",
workspaceMemberList: ({employeeCount, policyOwner}: WorkspaceMemberList) => `${employeeCount} member${employeeCount > 1 ? 's' : ''} • ${policyOwner}`,
whereYouWork: 'Where do you work?',
errorSelection: 'Please make a selection to continue.',
purpose: {
@@ -2766,6 +2774,10 @@ const translations = {
noAccountsFound: 'No accounts found',
noAccountsFoundDescription: 'Add the account in QuickBooks Online and sync the connection again.',
},
workspaceList: {
joinNow: 'Join now',
askToJoin: 'Ask to join',
},
xero: {
organization: 'Xero organization',
organizationDescription: "Choose the Xero organization that you'd like to import data from.",
12 changes: 12 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
@@ -192,7 +192,9 @@ import type {
WelcomeNoteParams,
WelcomeToRoomParams,
WeSentYouMagicSignInLinkParams,
WorkspaceMemberList,
WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams,
WorkspaceYouMayJoin,
YourPlanPriceParams,
ZipCodeExampleFormatParams,
} from './params';
@@ -349,6 +351,7 @@ const translations = {
semicolon: 'el punto y coma',
please: 'Por favor',
rename: 'Renombrar',
skip: 'Saltarse',
contactUs: 'contáctenos',
pleaseEnterEmailOrPhoneNumber: 'Por favor, escribe un correo electrónico o número de teléfono',
fixTheErrors: 'corrige los errores',
@@ -1756,6 +1759,11 @@ const translations = {
},
getStarted: 'Comenzar',
whatsYourName: '¿Cómo te llamas?',
peopleYouMayKnow: 'Las personas que tal vez conozcas ya están aquí. Verifica tu correo electrónico para unirte a ellos.',
workspaceMemberList: ({employeeCount, policyOwner}: WorkspaceMemberList) => `${employeeCount} miembro${employeeCount > 1 ? 's' : ''} • ${policyOwner}`,
workspaceYouMayJoin: ({domain, email}: WorkspaceYouMayJoin) => `Alguien de ${domain} ya ha creado un espacio de trabajo. Por favor, introduce el código mágico enviado a ${email}.`,
joinAWorkspace: 'Unirse a un espacio de trabajo',
listOfWorkspaces: 'Aquí está la lista de espacios de trabajo a los que puedes unirte. No te preocupes, siempre puedes unirte a ellos más tarde si lo prefieres.',
whereYouWork: '¿Dónde trabajas?',
errorSelection: 'Por favor selecciona una opción para continuar.',
purpose: {
@@ -2797,6 +2805,10 @@ const translations = {
noAccountsFound: 'No se ha encontrado ninguna cuenta',
noAccountsFoundDescription: 'Añade la cuenta en QuickBooks Online y sincroniza de nuevo la conexión.',
},
workspaceList: {
joinNow: 'Únete ahora',
askToJoin: 'Pedir unirse',
},
xero: {
organization: 'Organización Xero',
organizationDescription: 'Seleccione la organización en Xero desde la que está importando los datos.',
12 changes: 12 additions & 0 deletions src/languages/params.ts
Original file line number Diff line number Diff line change
@@ -543,6 +543,16 @@ type ImportedTypesParams = {
importedTypes: string[];
};

type WorkspaceYouMayJoin = {
domain: string;
email: string;
};

type WorkspaceMemberList = {
employeeCount: number;
policyOwner: string;
};

type FileLimitParams = {
fileLimit: number;
};
@@ -771,6 +781,8 @@ export type {
OptionalParam,
AssignCardParams,
ImportedTypesParams,
WorkspaceYouMayJoin,
WorkspaceMemberList,
ImportPerDiemRatesSuccessfullDescriptionParams,
CurrencyCodeParams,
CompanyNameParams,
5 changes: 5 additions & 0 deletions src/libs/API/parameters/JoinAccessiblePolicyParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type JoinAccessiblePolicyParams = {
policyID: string;
};

export default JoinAccessiblePolicyParams;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type ValidateUserAndGetAccessiblePoliciesParams = {
validateCode: string;
};

export default ValidateUserAndGetAccessiblePoliciesParams;
2 changes: 2 additions & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
@@ -347,10 +347,12 @@ export type {default as ConnectPolicyToQuickBooksDesktopParams} from './ConnectP
export type {default as UpdateInvoiceCompanyNameParams} from './UpdateInvoiceCompanyNameParams';
export type {default as UpdateInvoiceCompanyWebsiteParams} from './UpdateInvoiceCompanyWebsiteParams';
export type {default as UpdateQuickbooksDesktopExpensesExportDestinationTypeParams} from './UpdateQuickbooksDesktopExpensesExportDestinationTypeParams';
export type {default as ValidateUserAndGetAccessiblePoliciesParams} from './ValidateUserAndGetAccessiblePoliciesParams';
export type {default as UpdateQuickbooksDesktopCompanyCardExpenseAccountTypeParams} from './UpdateQuickbooksDesktopCompanyCardExpenseAccountTypeParams';
export type {default as TogglePolicyPerDiemParams} from './TogglePolicyPerDiemParams';
export type {default as OpenPolicyPerDiemRatesPageParams} from './OpenPolicyPerDiemRatesPageParams';
export type {default as TogglePlatformMuteParams} from './TogglePlatformMuteParams';
export type {default as JoinAccessiblePolicyParams} from './JoinAccessiblePolicyParams';
export type {default as ImportPerDiemRatesParams} from './ImportPerDiemRatesParams';
export type {default as ExportPerDiemCSVParams} from './ExportPerDiemCSVParams';
export type {default as DismissProductTrainingParams} from './DismissProductTraining';
4 changes: 4 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
@@ -242,6 +242,7 @@ const WRITE_COMMANDS = {
SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT: 'SetPolicyForeignCurrencyDefaultTax',
SET_POLICY_CUSTOM_TAX_NAME: 'SetPolicyCustomTaxName',
JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink',
JOIN_ACCESSIBLE_POLICY: 'JoinAccessiblePolicy',
ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest',
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
CREATE_POLICY_TAX: 'CreatePolicyTax',
@@ -438,6 +439,7 @@ const WRITE_COMMANDS = {
SELF_TOUR_VIEWED: 'SelfTourViewed',
UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName',
UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite',
VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES: 'ValidateUserAndGetAccessiblePolicies',
DISMISS_PRODUCT_TRAINING: 'DismissProductTraining',
} as const;

@@ -676,6 +678,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SEARCH]: Parameters.SearchParams;
[WRITE_COMMANDS.SET_POLICY_CATEGORY_TAX]: Parameters.SetPolicyCategoryTaxParams;
[WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[WRITE_COMMANDS.VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES]: Parameters.ValidateUserAndGetAccessiblePoliciesParams;
[WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
[WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
[WRITE_COMMANDS.SET_POLICY_TAXES_CURRENCY_DEFAULT]: Parameters.SetPolicyCurrencyDefaultParams;
@@ -889,6 +892,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.UPDATE_INVOICE_COMPANY_NAME]: Parameters.UpdateInvoiceCompanyNameParams;
[WRITE_COMMANDS.UPDATE_INVOICE_COMPANY_WEBSITE]: Parameters.UpdateInvoiceCompanyWebsiteParams;

[WRITE_COMMANDS.JOIN_ACCESSIBLE_POLICY]: Parameters.JoinAccessiblePolicyParams;
// Dismis Product Training
[WRITE_COMMANDS.DISMISS_PRODUCT_TRAINING]: Parameters.DismissProductTrainingParams;
};
Original file line number Diff line number Diff line change
@@ -15,7 +15,9 @@ import OnboardingRefManager from '@libs/OnboardingRefManager';
import OnboardingAccounting from '@pages/OnboardingAccounting';
import OnboardingEmployees from '@pages/OnboardingEmployees';
import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails';
import OnboardingPrivateDomain from '@pages/OnboardingPrivateDomain';
import OnboardingPurpose from '@pages/OnboardingPurpose';
import OnboardingWorkspaces from '@pages/OnboardingWorkspaces';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import SCREENS from '@src/SCREENS';
@@ -74,6 +76,14 @@ function OnboardingModalNavigator() {
name={SCREENS.ONBOARDING.PERSONAL_DETAILS}
component={OnboardingPersonalDetails}
/>
<Stack.Screen
name={SCREENS.ONBOARDING.PRIVATE_DOMAIN}
component={OnboardingPrivateDomain}
/>
<Stack.Screen
name={SCREENS.ONBOARDING.WORKSPACES}
component={OnboardingWorkspaces}
/>
<Stack.Screen
name={SCREENS.ONBOARDING.EMPLOYEES}
component={OnboardingEmployees}
5 changes: 4 additions & 1 deletion src/libs/Navigation/NavigationRoot.tsx
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ import useThemePreference from '@hooks/useThemePreference';
import Firebase from '@libs/Firebase';
import {FSPage} from '@libs/Fullstory';
import Log from '@libs/Log';
import * as LoginUtils from '@libs/LoginUtils';
import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors';
import {getPathFromURL} from '@libs/Url';
import {updateLastVisitedPath} from '@userActions/App';
@@ -92,6 +93,8 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady, sh
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {setActiveWorkspaceID} = useActiveWorkspace();
const [user] = useOnyx(ONYXKEYS.USER);
const [session] = useOnyx(ONYXKEYS.SESSION);
const isPrivateDomain = !!session?.email && !LoginUtils.isEmailPublicDomain(session?.email);

const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {
selector: hasCompletedGuidedSetupFlowSelector,
@@ -105,7 +108,7 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady, sh
// If the user haven't completed the flow, we want to always redirect them to the onboarding flow.
// We also make sure that the user is authenticated.
if (!NativeModules.HybridAppModule && !isOnboardingCompleted && authenticated && !shouldShowRequire2FAModal) {
const {adaptedState} = getAdaptedStateFromPath(getOnboardingInitialPath(), linkingConfig.config);
const {adaptedState} = getAdaptedStateFromPath(getOnboardingInitialPath(isPrivateDomain), linkingConfig.config);
return adaptedState;
}

Loading