diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 7d3d0edef36e..bc2791c8ce32 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -104,6 +104,8 @@ const ONYXKEYS = {
/** Store the information of magic code */
VALIDATE_ACTION_CODE: 'validate_action_code',
+ KEY_JOINABLE_POLICIES: 'keyJoinablePolicies',
+
/** Information about the current session (authToken, accountID, email, loading, error) */
SESSION: 'session',
STASHED_SESSION: 'stashedSession',
@@ -907,6 +909,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList;
[ONYXKEYS.PENDING_CONTACT_ACTION]: OnyxTypes.PendingContactAction;
[ONYXKEYS.VALIDATE_ACTION_CODE]: OnyxTypes.ValidateMagicCodeAction;
+ [ONYXKEYS.KEY_JOINABLE_POLICIES]: OnyxTypes.JoinablePolicies;
[ONYXKEYS.SESSION]: OnyxTypes.Session;
[ONYXKEYS.USER_METADATA]: OnyxTypes.UserMetadata;
[ONYXKEYS.STASHED_SESSION]: OnyxTypes.Session;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 45501bf46374..d3e022a678f8 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -1325,6 +1325,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),
@@ -1337,6 +1341,10 @@ const ROUTES = {
route: 'onboarding/purpose',
getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/purpose`, backTo),
},
+ ONBOARDING_WORKSPACES: {
+ route: 'onboarding/workspaces',
+ getRoute: (backTo?: string) => getUrlWithBackToParam(`onboarding/workspaces`, backTo),
+ },
WELCOME_VIDEO_ROOT: 'onboarding/welcome-video',
EXPLANATION_MODAL_ROOT: 'onboarding/explanation',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index dea0f028e1a0..75f38569faf7 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -566,8 +566,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: {
diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
index cc2a7314f570..a02f3365cf96 100644
--- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx
@@ -63,6 +63,9 @@ type ValidateCodeFormProps = {
/** Function to clear error of the form */
clearError: () => void;
+ /** Whether to show the verify button (hidden in private domain onboarding) */
+ hideButton?: boolean;
+
/** Function is called when validate code modal is mounted and on magic code resend */
sendValidateCode: () => void;
};
@@ -78,6 +81,7 @@ function BaseValidateCodeForm({
clearError,
sendValidateCode,
buttonStyles,
+ hideButton,
}: ValidateCodeFormProps) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
@@ -259,15 +263,17 @@ function BaseValidateCodeForm({
onClose={() => clearError()}
style={buttonStyles}
>
-
+ {!hideButton && (
+
+ )}
>
);
diff --git a/src/hooks/useOnboardingFlow.ts b/src/hooks/useOnboardingFlow.ts
index 5ccd3bab9378..ce0497c804eb 100644
--- a/src/hooks/useOnboardingFlow.ts
+++ b/src/hooks/useOnboardingFlow.ts
@@ -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, hasCompletedHybridAppOnboardingFlowSelector} from '@libs/onboardingSelectors';
import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow';
@@ -21,6 +22,9 @@ function useOnboardingFlowRouter() {
selector: hasCompletedHybridAppOnboardingFlowSelector,
});
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const isPrivateDomain = !!session?.email && !LoginUtils.isEmailPublicDomain(session?.email);
+
useEffect(() => {
if (isLoadingOnyxValue(isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata)) {
return;
@@ -35,15 +39,15 @@ 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, isHybridAppOnboardingCompleted, isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata]);
+ }, [isOnboardingCompleted, isHybridAppOnboardingCompleted, isOnboardingCompletedMetadata, isHybridAppOnboardingCompletedMetadata, isPrivateDomain]);
return {isOnboardingCompleted, isHybridAppOnboardingCompleted};
}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 03bbaf8ca8ab..69c46038fd56 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -187,6 +187,7 @@ import type {
WelcomeToRoomParams,
WeSentYouMagicSignInLinkParams,
WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams,
+ WorkspaceYouMayJoin,
YourPlanPriceParams,
ZipCodeExampleFormatParams,
} from './params';
@@ -470,6 +471,7 @@ const translations = {
links: 'Links',
days: 'days',
rename: 'Rename',
+ skip: 'Skip',
},
location: {
useCurrent: 'Use current location',
@@ -1733,6 +1735,10 @@ 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.",
whereYouWork: 'Where do you work?',
errorSelection: 'Please make a selection to continue.',
purpose: {
@@ -2598,6 +2604,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.",
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 96921bd40388..f53ec8055a7c 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -186,6 +186,7 @@ import type {
WelcomeToRoomParams,
WeSentYouMagicSignInLinkParams,
WorkspaceOwnerWillNeedToAddOrUpdatePaymentCardParams,
+ WorkspaceYouMayJoin,
YourPlanPriceParams,
ZipCodeExampleFormatParams,
} from './params';
@@ -341,6 +342,7 @@ const translations = {
semicolon: 'el punto y coma',
please: 'Por favor',
rename: 'Renombrar',
+ skip: 'Saltarse',
contactUs: 'contáctenos',
pleaseEnterEmailOrPhoneNumber: 'Por favor, escribe un email o número de teléfono',
fixTheErrors: 'corrige los errores',
@@ -1734,6 +1736,10 @@ 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.',
+ 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: {
@@ -2628,6 +2634,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.',
diff --git a/src/languages/params.ts b/src/languages/params.ts
index e9f0c4370357..774a66c36f16 100644
--- a/src/languages/params.ts
+++ b/src/languages/params.ts
@@ -539,6 +539,11 @@ type ImportedTypesParams = {
importedTypes: string[];
};
+type WorkspaceYouMayJoin = {
+ domain: string;
+ email: string;
+};
+
type FileLimitParams = {
fileLimit: number;
};
@@ -746,4 +751,5 @@ export type {
OptionalParam,
AssignCardParams,
ImportedTypesParams,
+ WorkspaceYouMayJoin,
};
diff --git a/src/libs/API/parameters/JoinAccessiblePolicyParams.ts b/src/libs/API/parameters/JoinAccessiblePolicyParams.ts
new file mode 100644
index 000000000000..e241a38eab5c
--- /dev/null
+++ b/src/libs/API/parameters/JoinAccessiblePolicyParams.ts
@@ -0,0 +1,5 @@
+type JoinAccessiblePolicyParams = {
+ policyID: string;
+};
+
+export default JoinAccessiblePolicyParams;
diff --git a/src/libs/API/parameters/ValidateUserAndGetAccessiblePoliciesParams.ts b/src/libs/API/parameters/ValidateUserAndGetAccessiblePoliciesParams.ts
new file mode 100644
index 000000000000..cff9c7e35524
--- /dev/null
+++ b/src/libs/API/parameters/ValidateUserAndGetAccessiblePoliciesParams.ts
@@ -0,0 +1,5 @@
+type ValidateUserAndGetAccessiblePoliciesParams = {
+ validationCode: string;
+};
+
+export default ValidateUserAndGetAccessiblePoliciesParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 26da6b2f6f03..d5aa7d1a33d8 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -345,5 +345,7 @@ 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 TogglePlatformMuteParams} from './TogglePlatformMuteParams';
+export type {default as JoinAccessiblePolicyParams} from './JoinAccessiblePolicyParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 0350d59685ce..d31bf56697a9 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -241,6 +241,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',
@@ -436,6 +437,7 @@ const WRITE_COMMANDS = {
SET_INVOICING_TRANSFER_BANK_ACCOUNT: 'SetInvoicingTransferBankAccount',
UPDATE_INVOICE_COMPANY_NAME: 'UpdateInvoiceCompanyName',
UPDATE_INVOICE_COMPANY_WEBSITE: 'UpdateInvoiceCompanyWebsite',
+ VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES: 'ValidateUserAndGetAccessiblePolicies',
} as const;
type WriteCommand = ValueOf;
@@ -672,6 +674,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;
@@ -883,6 +886,8 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_INVOICING_TRANSFER_BANK_ACCOUNT]: Parameters.SetInvoicingTransferBankAccountParams;
[WRITE_COMMANDS.UPDATE_INVOICE_COMPANY_NAME]: Parameters.UpdateInvoiceCompanyNameParams;
[WRITE_COMMANDS.UPDATE_INVOICE_COMPANY_WEBSITE]: Parameters.UpdateInvoiceCompanyWebsiteParams;
+
+ [WRITE_COMMANDS.JOIN_ACCESSIBLE_POLICY]: Parameters.JoinAccessiblePolicyParams;
};
const READ_COMMANDS = {
diff --git a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx
index 4aae43987797..cfe645a379cf 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx
@@ -12,7 +12,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 SCREENS from '@src/SCREENS';
import Overlay from './Overlay';
@@ -52,6 +54,14 @@ function OnboardingModalNavigator() {
name={SCREENS.ONBOARDING.PERSONAL_DETAILS}
component={OnboardingPersonalDetails}
/>
+
+
['config'] = {
path: ROUTES.ONBOARDING_PERSONAL_DETAILS.route,
exact: true,
},
+ [SCREENS.ONBOARDING.PRIVATE_DOMAIN]: {
+ path: ROUTES.ONBOARDING_PRIVATE_DOMAIN.route,
+ exact: true,
+ },
[SCREENS.ONBOARDING.EMPLOYEES]: {
path: ROUTES.ONBOARDING_EMPLOYEES.route,
exact: true,
@@ -126,6 +130,10 @@ const config: LinkingOptions['config'] = {
path: ROUTES.ONBOARDING_ACCOUNTING.route,
exact: true,
},
+ [SCREENS.ONBOARDING.WORKSPACES]: {
+ path: ROUTES.ONBOARDING_WORKSPACES.route,
+ exact: true,
+ },
},
},
[NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: {
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 3eae46ac2855..55ed536facf5 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1482,6 +1482,12 @@ type OnboardingModalNavigatorParamList = {
[SCREENS.ONBOARDING.PERSONAL_DETAILS]: {
backTo?: string;
};
+ [SCREENS.ONBOARDING.PRIVATE_DOMAIN]: {
+ backTo?: string;
+ };
+ [SCREENS.ONBOARDING.WORKSPACES]: {
+ backTo?: string;
+ };
[SCREENS.ONBOARDING.PURPOSE]: {
backTo?: string;
};
diff --git a/src/libs/NavigationUtils.ts b/src/libs/NavigationUtils.ts
index 2f967c2f9a5e..0a352aa61b94 100644
--- a/src/libs/NavigationUtils.ts
+++ b/src/libs/NavigationUtils.ts
@@ -23,6 +23,8 @@ const ONBOARDING_SCREEN_NAMES = new Set([
SCREENS.ONBOARDING_MODAL.ONBOARDING,
SCREENS.ONBOARDING.EMPLOYEES,
SCREENS.ONBOARDING.ACCOUNTING,
+ SCREENS.ONBOARDING.PRIVATE_DOMAIN,
+ SCREENS.ONBOARDING.WORKSPACES,
]);
const removePolicyIDParamFromState = (state: State) => {
diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts
index d5b2adc54de3..d8b567666771 100644
--- a/src/libs/actions/Policy/Member.ts
+++ b/src/libs/actions/Policy/Member.ts
@@ -517,7 +517,7 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR
function requestWorkspaceOwnerChange(policyID: string) {
const policy = getPolicy(policyID);
- const ownershipChecks = {...policyOwnershipChecks?.[policyID]} ?? {};
+ const ownershipChecks = {...policyOwnershipChecks?.[policyID]};
const changeOwnerErrors = Object.keys(policy?.errorFields?.changeOwner ?? {});
@@ -729,6 +729,36 @@ function inviteMemberToWorkspace(policyID: string, inviterEmail: string) {
API.write(WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK, params, {optimisticData, failureData});
}
+/**
+ * Add member to the selected private domain workspace based on policyID
+ */
+function addMemberToPrivateDomainWorkspace(policyID: string) {
+ const memberJoinKey = `${ONYXKEYS.COLLECTION.POLICY_JOIN_MEMBER}${policyID}` as const;
+
+ const optimisticMembersState = {policyID};
+ const failureMembersState = {policyID};
+
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: memberJoinKey,
+ value: optimisticMembersState,
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: memberJoinKey,
+ value: {...failureMembersState, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.people.error.genericAdd')},
+ },
+ ];
+
+ const params = {policyID};
+
+ API.write(WRITE_COMMANDS.JOIN_ACCESSIBLE_POLICY, params, {optimisticData, failureData});
+}
+
/**
* Removes an error after trying to delete a member
*/
@@ -920,6 +950,7 @@ export {
openWorkspaceMembersPage,
setWorkspaceInviteMembersDraft,
inviteMemberToWorkspace,
+ addMemberToPrivateDomainWorkspace,
acceptJoinRequest,
declineJoinRequest,
isApprover,
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index eaccbb8497ac..e02004465d7c 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -1361,6 +1361,37 @@ function setIsDebugModeEnabled(isDebugModeEnabled: boolean) {
Onyx.merge(ONYXKEYS.USER, {isDebugModeEnabled});
}
+/**
+ * Check which policies the user can join
+ */
+function validateUserAndGetAccessiblePolicies(validationCode: string) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.KEY_JOINABLE_POLICIES,
+ value: {isLoading: true, errors: null},
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.KEY_JOINABLE_POLICIES,
+ value: {isLoading: false, errors: null},
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.KEY_JOINABLE_POLICIES,
+ value: {isLoading: false},
+ },
+ ];
+
+ API.write(WRITE_COMMANDS.VALIDATE_USER_AND_GET_ACCESSIBLE_POLICIES, {validationCode}, {optimisticData, successData, failureData});
+}
+
export {
clearFocusModeNotification,
closeAccount,
@@ -1402,4 +1433,5 @@ export {
subscribeToActiveGuides,
dismissGBRTooltip,
setIsDebugModeEnabled,
+ validateUserAndGetAccessiblePolicies,
};
diff --git a/src/libs/actions/Welcome/OnboardingFlow.ts b/src/libs/actions/Welcome/OnboardingFlow.ts
index 9b7dfc894b6a..794bbf5d4514 100644
--- a/src/libs/actions/Welcome/OnboardingFlow.ts
+++ b/src/libs/actions/Welcome/OnboardingFlow.ts
@@ -37,9 +37,9 @@ Onyx.connect({
/**
* Start a new onboarding flow or continue from the last visited onboarding page.
*/
-function startOnboardingFlow() {
+function startOnboardingFlow(isPrivateDomain?: boolean) {
const currentRoute = navigationRef.getCurrentRoute();
- const {adaptedState} = getAdaptedStateFromPath(getOnboardingInitialPath(), linkingConfig.config, false);
+ const {adaptedState} = getAdaptedStateFromPath(getOnboardingInitialPath(isPrivateDomain), linkingConfig.config, false);
const focusedRoute = findFocusedRoute(adaptedState as PartialState>);
if (focusedRoute?.name === currentRoute?.name) {
return;
@@ -51,7 +51,7 @@ function startOnboardingFlow() {
} as PartialState);
}
-function getOnboardingInitialPath(): string {
+function getOnboardingInitialPath(isPrivateDomain?: boolean): string {
const state = getStateFromPath(onboardingInitialPath, linkingConfig.config);
const isVsb = onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB;
@@ -59,6 +59,11 @@ function getOnboardingInitialPath(): string {
Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.MANAGE_TEAM);
return `/${ROUTES.ONBOARDING_EMPLOYEES.route}`;
}
+
+ if (isPrivateDomain) {
+ return `/${ROUTES.ONBOARDING_PERSONAL_DETAILS.route}`;
+ }
+
const isIndividual = onboardingValues.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.INDIVIDUAL;
if (isIndividual) {
Onyx.set(ONYXKEYS.ONBOARDING_CUSTOM_CHOICES, [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, CONST.ONBOARDING_CHOICES.EMPLOYER, CONST.ONBOARDING_CHOICES.CHAT_SPLIT]);
diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
index 54b485bd54dd..5a356f557bdb 100644
--- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
+++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
@@ -18,6 +18,7 @@ import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ErrorUtils from '@libs/ErrorUtils';
+import * as LoginUtils from '@libs/LoginUtils';
import navigateAfterOnboarding from '@libs/navigateAfterOnboarding';
import Navigation from '@libs/Navigation/Navigation';
import * as ValidationUtils from '@libs/ValidationUtils';
@@ -26,6 +27,7 @@ import * as Report from '@userActions/Report';
import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/DisplayNameForm';
import type {BaseOnboardingPersonalDetailsProps} from './types';
@@ -42,6 +44,8 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
const {inputCallbackRef} = useAutoFocusInput();
const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false);
const {isOffline} = useNetwork();
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const isPrivateDomain = !!session?.email && !LoginUtils.isEmailPublicDomain(session?.email);
const {canUseDefaultRooms} = usePermissions();
const {activeWorkspaceID} = useActiveWorkspace();
@@ -49,13 +53,18 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
Welcome.setOnboardingErrorMessage('');
}, []);
- const completeEngagement = useCallback(
+ const handleSubmit = useCallback(
(values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => {
const firstName = values.firstName.trim();
const lastName = values.lastName.trim();
PersonalDetails.setDisplayName(firstName, lastName);
+ if (isPrivateDomain) {
+ Navigation.navigate(ROUTES.ONBOARDING_PRIVATE_DOMAIN.getRoute(route.params.backTo));
+ return;
+ }
+
if (!onboardingPurposeSelected) {
return;
}
@@ -74,7 +83,17 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
navigateAfterOnboarding(isSmallScreenWidth, shouldUseNarrowLayout, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, route.params?.backTo);
},
- [onboardingPurposeSelected, onboardingAdminsChatReportID, onboardingPolicyID, route.params?.backTo, activeWorkspaceID, canUseDefaultRooms, isSmallScreenWidth, shouldUseNarrowLayout],
+ [
+ onboardingPurposeSelected,
+ onboardingAdminsChatReportID,
+ onboardingPolicyID,
+ route.params?.backTo,
+ activeWorkspaceID,
+ canUseDefaultRooms,
+ isSmallScreenWidth,
+ shouldUseNarrowLayout,
+ isPrivateDomain,
+ ],
);
const validate = (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => {
@@ -120,14 +139,14 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
>
{}, []);
+
+ const sendValidateCode = useCallback(() => {
+ if (!credentials?.login) {
+ return;
+ }
+ User.resendValidateCode(credentials.login);
+ }, [credentials?.login]);
+
+ useEffect(() => {
+ sendValidateCode();
+ }, [sendValidateCode]);
+
+ return (
+
+
+
+ {translate('onboarding.peopleYouMayKnow')}
+ {translate('onboarding.workspaceYouMayJoin', {domain, email})}
+ {
+ User.validateUserAndGetAccessiblePolicies(code);
+ }}
+ sendValidateCode={sendValidateCode}
+ clearError={clearError}
+ // validateError={null}
+ hideButton
+ />
+
+
+
+
+ );
+}
+
+BaseOnboardingPrivateDomain.displayName = 'BaseOnboardingPrivateDomain';
+
+export default BaseOnboardingPrivateDomain;
diff --git a/src/pages/OnboardingPrivateDomain/index.native.tsx b/src/pages/OnboardingPrivateDomain/index.native.tsx
new file mode 100644
index 000000000000..ddcefe4ba98b
--- /dev/null
+++ b/src/pages/OnboardingPrivateDomain/index.native.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import BaseOnboardingPrivateDomain from './BaseOnboardingPrivateDomain';
+import type {OnboardingPrivateDomainProps} from './types';
+
+function OnboardingPrivateDomain({...rest}: OnboardingPrivateDomainProps) {
+ return (
+
+ );
+}
+
+OnboardingPrivateDomain.displayName = 'OnboardingPrivateDomain';
+
+export default OnboardingPrivateDomain;
diff --git a/src/pages/OnboardingPrivateDomain/index.tsx b/src/pages/OnboardingPrivateDomain/index.tsx
new file mode 100644
index 000000000000..720277c681c0
--- /dev/null
+++ b/src/pages/OnboardingPrivateDomain/index.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import {View} from 'react-native';
+import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
+import useThemeStyles from '@hooks/useThemeStyles';
+import BaseOnboardingPrivateDomain from './BaseOnboardingPrivateDomain';
+import type {OnboardingPrivateDomainProps} from './types';
+
+function OnboardingPrivateDomain({...rest}: OnboardingPrivateDomainProps) {
+ const styles = useThemeStyles();
+
+ return (
+
+
+
+
+
+ );
+}
+
+OnboardingPrivateDomain.displayName = 'OnboardingPrivateDomain';
+
+export default OnboardingPrivateDomain;
diff --git a/src/pages/OnboardingPrivateDomain/types.ts b/src/pages/OnboardingPrivateDomain/types.ts
new file mode 100644
index 000000000000..5fe6c77617be
--- /dev/null
+++ b/src/pages/OnboardingPrivateDomain/types.ts
@@ -0,0 +1,15 @@
+import type {RouteProp} from '@react-navigation/native';
+import type {StackScreenProps} from '@react-navigation/stack';
+import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types';
+import type SCREENS from '@src/SCREENS';
+
+type OnboardingPrivateDomainProps = Record & StackScreenProps;
+
+type BaseOnboardingPrivateDomainProps = {
+ /* Whether to use native styles tailored for native devices */
+ shouldUseNativeStyles: boolean;
+
+ route: RouteProp;
+};
+
+export type {OnboardingPrivateDomainProps, BaseOnboardingPrivateDomainProps};
diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx
index 3b05c6bb40a8..c05bb08faf9a 100644
--- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx
+++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx
@@ -16,10 +16,12 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
+import * as LoginUtils from '@libs/LoginUtils';
import Navigation from '@libs/Navigation/Navigation';
import OnboardingRefManager from '@libs/OnboardingRefManager';
import type {TOnboardingRef} from '@libs/OnboardingRefManager';
import variables from '@styles/variables';
+import * as Report from '@userActions/Report';
import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import type {OnboardingPurposeType} from '@src/CONST';
@@ -49,12 +51,13 @@ const menuIcons = {
function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, route}: BaseOnboardingPurposeProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
+ const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID);
const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
const {windowHeight} = useWindowDimensions();
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
- const {isSmallScreenWidth} = useResponsiveLayout();
+ const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
const theme = useTheme();
const [onboardingErrorMessage, onboardingErrorMessageResult] = useOnyx(ONYXKEYS.ONBOARDING_ERROR_MESSAGE);
@@ -65,6 +68,8 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro
const [customChoices = []] = useOnyx(ONYXKEYS.ONBOARDING_CUSTOM_CHOICES);
const onboardingChoices = getOnboardingChoices(customChoices);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const isPrivateDomain = !!session?.email && !LoginUtils.isEmailPublicDomain(session?.email);
const menuItems: MenuItemProps[] = onboardingChoices.map((choice) => {
const translationKey = `onboarding.purpose.${choice}` as const;
@@ -81,11 +86,23 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro
onPress: () => {
Welcome.setOnboardingPurposeSelected(choice);
Welcome.setOnboardingErrorMessage('');
-
if (choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM) {
Navigation.navigate(ROUTES.ONBOARDING_EMPLOYEES.getRoute(route.params?.backTo));
return;
}
+ // TODO differentiate btwn a user joining a private domain but pressed "skip", vs a user on the onboarding purpose screen for the first time
+ if (isPrivateDomain) {
+ Report.completeOnboarding(choice, CONST.ONBOARDING_MESSAGES[choice], onboardingPolicyID);
+ Welcome.setOnboardingAdminsChatReportID();
+ Welcome.setOnboardingPolicyID(onboardingPolicyID);
+ Navigation.dismissModal();
+
+ // Only navigate to concierge chat when central pane is visible
+ // Otherwise stay on the chats screen.
+ if (!shouldUseNarrowLayout && !route.params?.backTo) {
+ Report.navigateToConciergeChat();
+ }
+ }
Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(route.params?.backTo));
},
};
@@ -110,7 +127,7 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro
diff --git a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx
new file mode 100644
index 000000000000..94ae69c90719
--- /dev/null
+++ b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx
@@ -0,0 +1,149 @@
+import React, {useMemo, useState} from 'react';
+import {View} from 'react-native';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Expensicons from '@components/Icon/Expensicons';
+import OfflineIndicator from '@components/OfflineIndicator';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import UserListItem from '@components/SelectionList/UserListItem';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useThemeStyles from '@hooks/useThemeStyles';
+import navigateAfterJoinRequest from '@libs/navigateAfterJoinRequest';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import * as MemberAction from '@userActions/Policy/Member';
+import * as Report from '@userActions/Report';
+import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
+import type {BaseOnboardingWorkspacesProps} from './types';
+
+// TODO remove
+const response = {
+ D35BAB0CFAC69F52: {
+ policyID: 'D35BAB0CFAC69F52',
+ policyOwner: 'bob@domain.com',
+ policyName: 'Health Coverage',
+ employeeCount: 150,
+ hasPendingAccess: false,
+ automaticJoiningEnabled: true,
+ },
+ E56CAB1DFBC78A13: {
+ policyOwner: 'alice@domain.com',
+ policyID: 'E56CAB1DFBC78A13',
+ policyName: 'Travel Support',
+ employeeCount: 50,
+ hasPendingAccess: true,
+ automaticJoiningEnabled: false,
+ },
+ F67DAC2EABC89B24: {
+ policyOwner: 'eve@domain.com',
+ policyID: 'F67DAC2EABC89B24',
+ policyName: 'Tech Equipment',
+ employeeCount: 200,
+ hasPendingAccess: false,
+ automaticJoiningEnabled: false,
+ },
+};
+
+function BaseOnboardingWorkspaces({shouldUseNativeStyles, route}: BaseOnboardingWorkspacesProps) {
+ const {isOffline} = useNetwork();
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to show offline indicator on small screen only
+ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
+ const {isSmallScreenWidth, onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout();
+ const [selectedPolicyID, setSelectedPolicyID] = useState();
+
+ const handleJoinWorkspace = (policyID: string, automaticJoiningEnabled: boolean) => {
+ if (automaticJoiningEnabled) {
+ MemberAction.addMemberToPrivateDomainWorkspace(policyID);
+ navigateAfterJoinRequest();
+ Report.navigateToConciergeChat();
+ } else {
+ // TODO
+ Report.navigateToConciergeChat();
+ }
+ };
+
+ const policyIDItems = useMemo(() => {
+ return Object.values(response).map((policyInfo) => {
+ return {
+ text: policyInfo.policyName,
+ alternateText: `${policyInfo.employeeCount} ${translate('common.members').charAt(0).toLowerCase() + translate('common.members').slice(1)}· ${policyInfo.policyOwner}`,
+ keyForList: policyInfo.policyID,
+ isSelected: policyInfo.policyID === selectedPolicyID,
+ rightElement: (
+