diff --git a/Mobile-Expensify b/Mobile-Expensify
index 1f36847737e6..0f12776c58b3 160000
--- a/Mobile-Expensify
+++ b/Mobile-Expensify
@@ -1 +1 @@
-Subproject commit 1f36847737e603a72b4b0662d94e8d22c0f16698
+Subproject commit 0f12776c58b3649f0e6d9a333a6c3ea6825bd937
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 8e5514cf9675..708e7035b8ec 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -111,8 +111,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009036711
- versionName "9.3.67-11"
+ versionCode 1009036712
+ versionName "9.3.67-12"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index eb57c9a622bc..0e6d7921274d 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -44,7 +44,7 @@
CFBundleVersion
- 9.3.67.11
+ 9.3.67.12
FullStory
OrgId
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 5cccd33662bc..b9dd5669ef3f 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
9.3.67
CFBundleVersion
- 9.3.67.11
+ 9.3.67.12
NSExtension
NSExtensionPointIdentifier
diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist
index 71b9012de36c..35519c3e3cff 100644
--- a/ios/ShareViewController/Info.plist
+++ b/ios/ShareViewController/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
9.3.67
CFBundleVersion
- 9.3.67.11
+ 9.3.67.12
NSExtension
NSExtensionAttributes
diff --git a/package-lock.json b/package-lock.json
index eb40b0a97f99..a2a8523f9498 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.3.67-11",
+ "version": "9.3.67-12",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.3.67-11",
+ "version": "9.3.67-12",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 3b1978d4b896..66043cf0cb9b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.3.67-11",
+ "version": "9.3.67-12",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index fb3627d456be..a61801d2960b 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -887,7 +887,6 @@ const CONST = {
BULK_DUPLICATE_REPORT: 'bulkDuplicateReport',
BULK_EDIT: 'bulkEdit',
NEW_MANUAL_EXPENSE_FLOW: 'newManualExpenseFlow',
- SUBMIT_2026: 'submit2026',
BULK_SUBMIT_APPROVE_PAY: 'bulkSubmitApprovePay',
},
BUTTON_STATES: {
@@ -3517,8 +3516,6 @@ const CONST = {
// Often referred to as "collect" workspaces
TEAM: 'team',
-
- SUBMIT: 'submit2026',
},
RULE_CONDITIONS: {
MATCHES: 'matches',
@@ -3537,7 +3534,6 @@ const CONST = {
ADMIN: 'admin',
AUDITOR: 'auditor',
USER: 'user',
- EDITOR: 'editor',
},
AUTO_REIMBURSEMENT_MAX_LIMIT_CENTS: 2000000,
diff --git a/src/components/SidePanel/SidePanelContextProvider.tsx b/src/components/SidePanel/SidePanelContextProvider.tsx
index c1dee29c6d69..87fb63d098a4 100644
--- a/src/components/SidePanel/SidePanelContextProvider.tsx
+++ b/src/components/SidePanel/SidePanelContextProvider.tsx
@@ -10,7 +10,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
import SidePanelActions from '@libs/actions/SidePanel';
import DateUtils from '@libs/DateUtils';
import focusComposerWithDelay from '@libs/focusComposerWithDelay';
-import {canEditWorkspaceSettings, shouldShowPolicy} from '@libs/PolicyUtils';
+import {isPolicyAdmin, shouldShowPolicy} from '@libs/PolicyUtils';
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
import variables from '@styles/variables';
import CONST from '@src/CONST';
@@ -82,7 +82,7 @@ function SidePanelContextProvider({children}: PropsWithChildren) {
const isRHPAdminsRoom = onboardingRHPVariant === CONST.ONBOARDING_RHP_VARIANT.RHP_ADMINS_ROOM;
const isRHPHomePage = onboardingRHPVariant === CONST.ONBOARDING_RHP_VARIANT.RHP_HOME_PAGE;
- const isUserAdmin = canEditWorkspaceSettings(activePolicy);
+ const isUserAdmin = isPolicyAdmin(activePolicy, sessionEmail);
const isPolicyActive = shouldShowPolicy(activePolicy, false, sessionEmail ?? '');
const adminsChatReportID = activePolicy?.chatReportIDAdmins?.toString();
diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx
index 801a8698faba..432dd71ff5ae 100644
--- a/src/components/WorkspaceMemberRoleList.tsx
+++ b/src/components/WorkspaceMemberRoleList.tsx
@@ -1,15 +1,12 @@
-import {emailSelector} from '@selectors/Session';
import React from 'react';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import useLocalize from '@hooks/useLocalize';
-import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
-import {isControlPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
+import {isControlPolicy} from '@libs/PolicyUtils';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import type {Policy} from '@src/types/onyx';
import HeaderWithBackButton from './HeaderWithBackButton';
@@ -35,7 +32,6 @@ type WorkspaceMemberRoleListProps = {
function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLoading = false, onSelectRole = () => {}}: WorkspaceMemberRoleListProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
- const [currentUserEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector});
const workspaceRoles: ListItemType[] = [
{
@@ -62,18 +58,7 @@ function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLo
];
const isPolicyControl = isControlPolicy(policy);
- // Only strict admins can assign the ADMIN role. Editors (e.g. Submit workspace owners) can
- // invite/manage members but must not be able to escalate anyone to admin.
- const canAssignAdminRole = isPolicyAdmin(policy, currentUserEmail);
- const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => {
- if (item.value === CONST.POLICY.ROLE.AUDITOR && !isPolicyControl) {
- return false;
- }
- if (item.value === CONST.POLICY.ROLE.ADMIN && !canAssignAdminRole) {
- return false;
- }
- return true;
- });
+ const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => isPolicyControl || item.value !== CONST.POLICY.ROLE.AUDITOR);
return (
<>
diff --git a/src/hooks/useAutoCreateSubmitWorkspace.ts b/src/hooks/useAutoCreateSubmitWorkspace.ts
deleted file mode 100644
index 7d97960bffcb..000000000000
--- a/src/hooks/useAutoCreateSubmitWorkspace.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import {useCallback, useMemo} from 'react';
-import type {OnyxCollection} from 'react-native-onyx';
-import {navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding';
-import {createDisplayName} from '@libs/PersonalDetailsUtils';
-import {canEditWorkspaceSettings, isGroupPolicy} from '@libs/PolicyUtils';
-import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy';
-import {completeOnboarding} from '@userActions/Report';
-import {setOnboardingAdminsChatReportID, setOnboardingPolicyID} from '@userActions/Welcome';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import type {Policy} from '@src/types/onyx';
-import useOnboardingWorkspaceCreationState from './useOnboardingWorkspaceCreationState';
-import useOnyx from './useOnyx';
-
-/**
- * Hook that provides a function to auto-create a Submit workspace for EMPLOYER
- * users during onboarding and complete the onboarding flow.
- *
- * Shared by BaseOnboardingPersonalDetails, BaseOnboardingPurpose, and BaseOnboardingWorkspaces.
- */
-function useAutoCreateSubmitWorkspace() {
- const {
- onboardingPolicyID,
- onboardingAdminsChatReportID,
- introSelected,
- isSelfTourViewed,
- betas,
- currentUserEmail,
- currentUserAccountID,
- localCurrencyCode,
- activePolicy,
- translate,
- formatPhoneNumber,
- isRestrictedPolicyCreation,
- hasActiveAdminPolicies,
- onboardingMessages,
- lastWorkspaceNumber,
- isSmallScreenWidth,
- } = useOnboardingWorkspaceCreationState();
-
- const groupPolicySelector = useMemo(
- () => (policies: OnyxCollection) => Object.values(policies ?? {}).some((policy) => isGroupPolicy(policy) && canEditWorkspaceSettings(policy)),
- [],
- );
- const [hasEditableGroupPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: groupPolicySelector});
-
- const autoCreateSubmitWorkspace = useCallback(
- (firstName: string, lastName: string) => {
- const shouldCreateWorkspace = !isRestrictedPolicyCreation && !onboardingPolicyID && !hasEditableGroupPolicy;
- const displayName = createDisplayName(currentUserEmail, {firstName, lastName}, formatPhoneNumber);
-
- const {adminsChatReportID: newAdminsChatReportID, policyID: newPolicyID} = shouldCreateWorkspace
- ? createWorkspace({
- policyOwnerEmail: undefined,
- makeMeAdmin: true,
- policyName: generateDefaultWorkspaceName(currentUserEmail, lastWorkspaceNumber, translate, displayName),
- policyID: generatePolicyID(),
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- currency: localCurrencyCode,
- file: undefined,
- shouldAddOnboardingTasks: false,
- introSelected,
- activePolicy,
- currentUserAccountIDParam: currentUserAccountID,
- currentUserEmailParam: currentUserEmail,
- shouldAddGuideWelcomeMessage: false,
- type: CONST.POLICY.TYPE.SUBMIT,
- betas,
- isSelfTourViewed,
- hasActiveAdminPolicies,
- })
- : {adminsChatReportID: onboardingAdminsChatReportID, policyID: onboardingPolicyID};
-
- completeOnboarding({
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- onboardingMessage: onboardingMessages[CONST.ONBOARDING_CHOICES.EMPLOYER],
- firstName,
- lastName,
- adminsChatReportID: newAdminsChatReportID,
- onboardingPolicyID: newPolicyID,
- introSelected,
- isSelfTourViewed,
- betas,
- });
-
- setOnboardingAdminsChatReportID();
- setOnboardingPolicyID();
-
- navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue(newPolicyID, isSmallScreenWidth);
- },
- [
- currentUserEmail,
- currentUserAccountID,
- lastWorkspaceNumber,
- translate,
- formatPhoneNumber,
- isRestrictedPolicyCreation,
- onboardingPolicyID,
- hasEditableGroupPolicy,
- onboardingAdminsChatReportID,
- localCurrencyCode,
- introSelected,
- activePolicy,
- isSelfTourViewed,
- onboardingMessages,
- betas,
- hasActiveAdminPolicies,
- isSmallScreenWidth,
- ],
- );
-
- return autoCreateSubmitWorkspace;
-}
-
-export default useAutoCreateSubmitWorkspace;
diff --git a/src/hooks/useAutoCreateTrackWorkspace.ts b/src/hooks/useAutoCreateTrackWorkspace.ts
index 99cf11eef483..78dc14f3edb9 100644
--- a/src/hooks/useAutoCreateTrackWorkspace.ts
+++ b/src/hooks/useAutoCreateTrackWorkspace.ts
@@ -1,3 +1,4 @@
+import {hasSeenTourSelector} from '@selectors/Onboarding';
import {useCallback, useMemo} from 'react';
import type {OnyxCollection} from 'react-native-onyx';
import isSidePanelReportSupported from '@components/SidePanel/isSidePanelReportSupported';
@@ -11,10 +12,17 @@ import {setOnboardingAdminsChatReportID, setOnboardingPolicyID} from '@userActio
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {OnboardingPurpose, OnboardingRHPVariant, Policy} from '@src/types/onyx';
+import useActivePolicy from './useActivePolicy';
import useArchivedReportsIdSet from './useArchivedReportsIdSet';
-import useOnboardingWorkspaceCreationState from './useOnboardingWorkspaceCreationState';
+import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
+import useHasActiveAdminPolicies from './useHasActiveAdminPolicies';
+import useLastWorkspaceNumber from './useLastWorkspaceNumber';
+import useLocalize from './useLocalize';
+import useOnboardingMessages from './useOnboardingMessages';
import useOnyx from './useOnyx';
import usePermissions from './usePermissions';
+import usePreferredPolicy from './usePreferredPolicy';
+import useResponsiveLayout from './useResponsiveLayout';
/**
* Hook that provides a function to auto-create a workspace for Track (PERSONAL_SPEND)
@@ -23,57 +31,57 @@ import usePermissions from './usePermissions';
* Shared by BaseOnboardingPersonalDetails and BaseOnboardingPurpose.
*/
function useAutoCreateTrackWorkspace() {
- const {
- onboardingPolicyID,
- onboardingAdminsChatReportID,
- introSelected,
- isSelfTourViewed,
- betas,
- currentUserEmail,
- currentUserAccountID,
- localCurrencyCode,
- activePolicy,
- translate,
- formatPhoneNumber,
- isRestrictedPolicyCreation,
- hasActiveAdminPolicies,
- onboardingMessages,
- lastWorkspaceNumber,
- isSmallScreenWidth,
- } = useOnboardingWorkspaceCreationState();
-
+ const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID);
+ const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID);
+ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
+ const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
+ const [betas] = useOnyx(ONYXKEYS.BETAS);
+ const [session] = useOnyx(ONYXKEYS.SESSION);
const paidGroupPolicySelector = useMemo(
- () => (policies: OnyxCollection) => Object.values(policies ?? {}).some((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, currentUserEmail)),
- [currentUserEmail],
+ () => (policies: OnyxCollection) => Object.values(policies ?? {}).some((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, session?.email)),
+ [session?.email],
);
const [hasPaidGroupAdminPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: paidGroupPolicySelector});
-
const [conciergeChatReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
+ const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const archivedReportsIdSet = useArchivedReportsIdSet();
const {isBetaEnabled} = usePermissions();
+ const {translate, formatPhoneNumber} = useLocalize();
+ const activePolicy = useActivePolicy();
+ const {isRestrictedPolicyCreation} = usePreferredPolicy();
+ const hasActiveAdminPolicies = useHasActiveAdminPolicies();
+ const lastWorkspaceNumber = useLastWorkspaceNumber();
+ const {onboardingMessages} = useOnboardingMessages();
+
+ // We use isSmallScreenWidth instead of shouldUseNarrowLayout because navigateAfterOnboarding
+ // relies on actual device screen width to handle navigation stack differences: on small screens,
+ // removing OnboardingModalNavigator redirects to HOME, requiring explicit navigation to the last
+ // accessed report. This behavior is tied to screen size, not responsive layout mode.
+ // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
+ const {isSmallScreenWidth} = useResponsiveLayout();
const mergedAccountConciergeReportID = !onboardingValues?.shouldRedirectToClassicAfterMerge && onboardingValues?.shouldValidate ? conciergeChatReportID : undefined;
const autoCreateTrackWorkspace = useCallback(
async (firstName: string, lastName: string, onboardingPurposeSelected: OnboardingPurpose) => {
const shouldCreateWorkspace = !isRestrictedPolicyCreation && !onboardingPolicyID && !hasPaidGroupAdminPolicy;
- const displayName = createDisplayName(currentUserEmail, {firstName, lastName}, formatPhoneNumber);
+ const displayName = createDisplayName(session?.email ?? '', {firstName, lastName}, formatPhoneNumber);
const {adminsChatReportID: newAdminsChatReportID, policyID: newPolicyID} = shouldCreateWorkspace
? createWorkspace({
policyOwnerEmail: undefined,
makeMeAdmin: true,
- policyName: generateDefaultWorkspaceName(currentUserEmail, lastWorkspaceNumber, translate, displayName),
+ policyName: generateDefaultWorkspaceName(session?.email ?? '', lastWorkspaceNumber, translate, displayName),
policyID: generatePolicyID(),
engagementChoice: CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE,
- currency: localCurrencyCode,
+ currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
file: undefined,
shouldAddOnboardingTasks: false,
introSelected,
activePolicy,
- currentUserAccountIDParam: currentUserAccountID,
- currentUserEmailParam: currentUserEmail,
+ currentUserAccountIDParam: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
+ currentUserEmailParam: session?.email ?? '',
shouldAddGuideWelcomeMessage: false,
onboardingPurposeSelected,
betas,
@@ -121,8 +129,8 @@ function useAutoCreateTrackWorkspace() {
}
},
[
- currentUserEmail,
- currentUserAccountID,
+ session?.email,
+ session?.accountID,
lastWorkspaceNumber,
translate,
formatPhoneNumber,
@@ -130,7 +138,7 @@ function useAutoCreateTrackWorkspace() {
onboardingPolicyID,
hasPaidGroupAdminPolicy,
onboardingAdminsChatReportID,
- localCurrencyCode,
+ currentUserPersonalDetails.localCurrencyCode,
introSelected,
activePolicy,
isSelfTourViewed,
diff --git a/src/hooks/useOnboardingWorkspaceCreationState.ts b/src/hooks/useOnboardingWorkspaceCreationState.ts
deleted file mode 100644
index cb09fb0b9a29..000000000000
--- a/src/hooks/useOnboardingWorkspaceCreationState.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import {hasSeenTourSelector} from '@selectors/Onboarding';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import useActivePolicy from './useActivePolicy';
-import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
-import useHasActiveAdminPolicies from './useHasActiveAdminPolicies';
-import useLastWorkspaceNumber from './useLastWorkspaceNumber';
-import useLocalize from './useLocalize';
-import useOnboardingMessages from './useOnboardingMessages';
-import useOnyx from './useOnyx';
-import usePreferredPolicy from './usePreferredPolicy';
-import useResponsiveLayout from './useResponsiveLayout';
-
-/**
- * Shared state for the onboarding workspace auto-creation hooks
- * (`useAutoCreateSubmitWorkspace`, `useAutoCreateTrackWorkspace`).
- *
- * Email and accountID come from `ONYXKEYS.SESSION` because session is hydrated
- * earlier in onboarding than personal details.
- */
-function useOnboardingWorkspaceCreationState() {
- const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID);
- const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID);
- const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
- const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
- const [betas] = useOnyx(ONYXKEYS.BETAS);
- const [session] = useOnyx(ONYXKEYS.SESSION);
-
- const currentUserEmail = session?.email ?? '';
- const currentUserAccountID = session?.accountID ?? CONST.DEFAULT_NUMBER_ID;
-
- const currentUserPersonalDetails = useCurrentUserPersonalDetails();
- const localCurrencyCode = currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD;
-
- const activePolicy = useActivePolicy();
- const {translate, formatPhoneNumber} = useLocalize();
- const {isRestrictedPolicyCreation} = usePreferredPolicy();
- const hasActiveAdminPolicies = useHasActiveAdminPolicies();
- const {onboardingMessages} = useOnboardingMessages();
- const lastWorkspaceNumber = useLastWorkspaceNumber();
-
- // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
- const {isSmallScreenWidth} = useResponsiveLayout();
-
- return {
- onboardingPolicyID,
- onboardingAdminsChatReportID,
- introSelected,
- isSelfTourViewed,
- betas,
- currentUserPersonalDetails,
- currentUserEmail,
- currentUserAccountID,
- localCurrencyCode,
- activePolicy,
- translate,
- formatPhoneNumber,
- isRestrictedPolicyCreation,
- hasActiveAdminPolicies,
- onboardingMessages,
- lastWorkspaceNumber,
- isSmallScreenWidth,
- };
-}
-
-export default useOnboardingWorkspaceCreationState;
diff --git a/src/languages/de.ts b/src/languages/de.ts
index ea410d8ef92d..e759fc39d1a4 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6988,10 +6988,6 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
label: 'Steuerung',
description: 'Für Organisationen mit erweiterten Anforderungen.',
},
- submit2026: {
- label: 'Einreichen',
- description: 'Für Mitarbeiter, die Ausgaben bei ihrem Arbeitgeber einreichen möchten.',
- },
},
description: 'Wähle ein passendes Abo für dich. Eine detaillierte Liste der Funktionen und Preise findest du in unserem',
subscriptionLink: 'Hilfeseite zu Plantypen und Preisen',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 57a6fbfc9a83..b444c7bd5832 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -7000,10 +7000,6 @@ const translations = {
label: 'Control',
description: 'For organizations with advanced requirements.',
},
- submit2026: {
- label: 'Submit',
- description: 'For employees looking to submit expenses to their employer.',
- },
},
description: "Choose a plan that's right for you. For a detailed list of features and pricing, check out our",
subscriptionLink: 'plan types and pricing help page',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 0fe1ab8d82a7..d9147fe1457c 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -6421,10 +6421,6 @@ ${amount} para ${merchant} - ${date}`,
label: 'Controlar',
description: 'Para organizaciones con requisitos avanzados.',
},
- submit2026: {
- label: 'Enviar',
- description: 'Para empleados que buscan enviar gastos a su empleador.',
- },
},
description: 'Elige el plan adecuado para ti. Para ver una lista detallada de funciones y precios, consulta nuestra',
subscriptionLink: 'página de ayuda sobre tipos de planes y precios',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index b3a2f6e89568..d017c7dad33e 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -7010,10 +7010,6 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
label: 'Contrôle',
description: 'Pour les organisations ayant des exigences avancées.',
},
- submit2026: {
- label: 'Soumettre',
- description: 'Pour les employés souhaitant soumettre des dépenses à leur employeur.',
- },
},
description: 'Choisissez l’offre qui vous convient. Pour une liste détaillée des fonctionnalités et des tarifs, consultez notre',
subscriptionLink: "page d'aide sur les types de forfaits et les tarifs",
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 888fa02f473b..2522cf5f2479 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6973,10 +6973,6 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
label: 'Controllo',
description: 'Per le organizzazioni con requisiti avanzati.',
},
- submit2026: {
- label: 'Invia',
- description: 'Per i dipendenti che desiderano inviare le spese al proprio datore di lavoro.',
- },
},
description: 'Scegli il piano più adatto a te. Per un elenco dettagliato di funzionalità e prezzi, consulta la nostra',
subscriptionLink: 'pagina di aiuto su tipi di piano e prezzi',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 7d72e906a2a7..9d48671624b6 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -6896,10 +6896,6 @@ ${reportName}
label: 'コントロール',
description: '高度な要件を持つ組織向け。',
},
- submit2026: {
- label: '提出',
- description: '雇用主に経費を提出したい従業員向け。',
- },
},
description: '自分に合ったプランをお選びください。機能と料金の詳細な一覧は、こちらのページをご覧ください',
subscriptionLink: 'プランの種類と料金のヘルプページ',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 92b3e39d7f5a..fcd20ebc30c5 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -6952,10 +6952,6 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
label: 'Beheer',
description: 'Voor organisaties met geavanceerde vereisten.',
},
- submit2026: {
- label: 'Indienen',
- description: 'Voor werknemers die onkosten bij hun werkgever willen indienen.',
- },
},
description: 'Kies een pakket dat bij je past. Voor een gedetailleerd overzicht van functies en prijzen, bekijk onze',
subscriptionLink: 'hulppagina voor abonnementstypen en prijzen',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index fafa4d486877..91a037b6a951 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -6945,10 +6945,6 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
label: 'Sterowanie',
description: 'Dla organizacji z zaawansowanymi wymaganiami.',
},
- submit2026: {
- label: 'Prześlij',
- description: 'Dla pracowników, którzy chcą przesyłać wydatki do pracodawcy.',
- },
},
description: 'Wybierz plan odpowiedni dla siebie. Szczegółową listę funkcji i cen znajdziesz w naszej',
subscriptionLink: 'strona pomocy dotycząca typów planów i cen',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 6db7a0023f7b..1c929bc939bc 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -6951,10 +6951,6 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
label: 'Controle',
description: 'Para organizações com requisitos avançados.',
},
- submit2026: {
- label: 'Enviar',
- description: 'Para funcionários que desejam enviar despesas ao empregador.',
- },
},
description: 'Escolha o plano ideal para você. Para ver a lista detalhada de recursos e preços, confira nosso',
subscriptionLink: 'página de ajuda sobre tipos de plano e preços',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index cd4e73d686fb..85754bfc6377 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -6778,10 +6778,6 @@ ${reportName}
label: '控制',
description: '适用于具有高级需求的组织。',
},
- submit2026: {
- label: '提交',
- description: '适用于希望向雇主提交费用的员工。',
- },
},
description: '选择适合您的方案。要查看详细的功能和价格列表,请访问我们的',
subscriptionLink: '方案类型和价格帮助页面',
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index f48318b86a78..9fa421f0f89c 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -881,32 +881,6 @@ function isPaidGroupPolicy(policy: OnyxInputOrEntry): boolean {
return policy?.type === CONST.POLICY.TYPE.TEAM || policy?.type === CONST.POLICY.TYPE.CORPORATE;
}
-function isSubmitPolicy(policy: OnyxInputOrEntry): boolean {
- return policy?.type === CONST.POLICY.TYPE.SUBMIT;
-}
-
-function isPolicyEditor(policy: OnyxEntry): boolean {
- return policy?.role === CONST.POLICY.ROLE.EDITOR;
-}
-
-/**
- * Returns true if the user can edit workspace settings — admins on any workspace, or editors on Submit workspaces.
- */
-function canEditWorkspaceSettings(policy: OnyxEntry): boolean {
- return isPolicyAdmin(policy) || isPolicyEditor(policy);
-}
-
-/**
- * Returns true for any group workspace: paid (Team/Corporate) or Submit.
- *
- * Note: not to be confused with `ReportUtils.isGroupPolicy(policyType: string)`,
- * which excludes Submit. Use this helper when Submit workspaces should be treated
- * like paid workspaces (e.g. access gating for shared workspace pages).
- */
-function isGroupPolicy(policy: OnyxInputOrEntry): boolean {
- return isPaidGroupPolicy(policy) || isSubmitPolicy(policy);
-}
-
function getOwnedPaidPolicies(policies: OnyxCollection | null, currentUserAccountID: number | undefined): Policy[] {
return Object.values(policies ?? {}).filter((policy): policy is Policy => isPolicyOwner(policy, currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID) && isPaidGroupPolicy(policy));
}
@@ -2190,10 +2164,6 @@ export {
isDelayedSubmissionEnabled,
getCorrectedAutoReportingFrequency,
isPaidGroupPolicy,
- isSubmitPolicy,
- isPolicyEditor,
- canEditWorkspaceSettings,
- isGroupPolicy,
isPendingDeletePolicy,
isPolicyAdmin,
isPolicyUser,
diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts
index 3c26d554354d..7193ad676243 100644
--- a/src/libs/SubscriptionUtils.ts
+++ b/src/libs/SubscriptionUtils.ts
@@ -539,7 +539,7 @@ function getSubscriptionPrice(
privateSubscriptionType: SubscriptionType | undefined,
hasTeam2025Pricing: boolean,
): number {
- if (!privateSubscriptionType || !plan || plan === CONST.POLICY.TYPE.SUBMIT) {
+ if (!privateSubscriptionType || !plan) {
return 0;
}
diff --git a/src/libs/actions/Policy/Member.ts b/src/libs/actions/Policy/Member.ts
index a59721afb0ca..4c8704f37d83 100644
--- a/src/libs/actions/Policy/Member.ts
+++ b/src/libs/actions/Policy/Member.ts
@@ -1083,55 +1083,28 @@ function inviteMemberToWorkspace(policyID: string, inviterEmail?: string) {
}
/**
- * Add member to the selected private domain workspace based on policyID.
- *
- * The optimistic merge is intentionally limited to `isLoading: true`. We can't
- * tell the policy's real type/role from the joinable-policy payload (it's not
- * exposed on `JoinablePolicy`), so writing speculative `type: SUBMIT` /
- * `role: EDITOR` would corrupt Team/Corporate policies joined via the same
- * private-domain flow. The `isLoading` flag is enough to suppress the brief
- * NotFoundPage flash in `WorkspaceInitialPage` / `AccessOrNotFoundWrapper`
- * until the backend response hydrates the policy with its actual shape.
+ * Add member to the selected private domain workspace based on policyID
*/
function joinAccessiblePolicy(policyID: string) {
const memberJoinKey = `${ONYXKEYS.COLLECTION.POLICY_JOIN_MEMBER}${policyID}` as const;
- const policyKey = `${ONYXKEYS.COLLECTION.POLICY}${policyID}` as const;
- const optimisticData: Array> = [
+ const optimisticData: Array> = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: memberJoinKey,
value: {policyID},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: policyKey,
- value: {isLoading: true},
- },
- ];
-
- const successData: Array> = [
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: policyKey,
- value: {isLoading: false},
- },
];
- const failureData: Array> = [
+ const failureData: Array> = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: memberJoinKey,
value: {policyID, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.people.error.genericAdd')},
},
- {
- onyxMethod: Onyx.METHOD.MERGE,
- key: policyKey,
- value: {isLoading: false},
- },
];
- API.write(WRITE_COMMANDS.JOIN_ACCESSIBLE_POLICY, {policyID}, {optimisticData, successData, failureData});
+ API.write(WRITE_COMMANDS.JOIN_ACCESSIBLE_POLICY, {policyID}, {optimisticData, failureData});
}
/**
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 888e10bc363f..17ed8758a302 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -214,7 +214,7 @@ type BuildPolicyDataOptions = {
onboardingPurposeSelected?: OnboardingPurpose;
shouldAddGuideWelcomeMessage?: boolean;
shouldCreateControlPolicy?: boolean;
- type?: typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE | typeof CONST.POLICY.TYPE.SUBMIT;
+ type?: typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE;
// TODO: Make it required once we complete refactoring the buildPolicyData function to use isSelfTourViewed. Refactor issue: https://github.com/Expensify/App/issues/66424
isSelfTourViewed?: boolean;
betas: OnyxEntry;
@@ -2290,7 +2290,7 @@ function createDraftInitialWorkspace(
makeMeAdmin = false,
currency = '',
file?: File,
- type: typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE | typeof CONST.POLICY.TYPE.SUBMIT = CONST.POLICY.TYPE.TEAM,
+ type: typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE = CONST.POLICY.TYPE.TEAM,
isAnnualSubscription = false,
) {
const {customUnits, outputCurrency} = buildOptimisticDistanceRateCustomUnits(currency);
@@ -2367,27 +2367,6 @@ type BuildPolicyDataKeys =
| typeof ONYXKEYS.NVP_LAST_PAYMENT_METHOD
| typeof ONYXKEYS.PERSONAL_DETAILS_LIST;
-function getRoleForNewWorkspaceMember(isSubmitWorkspace: boolean, makeMeAdmin: boolean): ValueOf {
- if (isSubmitWorkspace) {
- return CONST.POLICY.ROLE.EDITOR;
- }
- return makeMeAdmin ? CONST.POLICY.ROLE.ADMIN : CONST.POLICY.ROLE.USER;
-}
-
-function getApprovalModeForNewWorkspace(
- isSubmitWorkspace: boolean,
- shouldEnableWorkflowsByDefault: boolean,
- engagementChoice?: OnboardingPurpose,
-): ValueOf {
- if (isSubmitWorkspace) {
- return CONST.POLICY.APPROVAL_MODE.ADVANCED;
- }
- if (shouldEnableWorkflowsByDefault && engagementChoice !== CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE) {
- return CONST.POLICY.APPROVAL_MODE.BASIC;
- }
- return CONST.POLICY.APPROVAL_MODE.OPTIONAL;
-}
-
/**
* Generates onyx data for creating a new workspace
*
@@ -2450,9 +2429,7 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData feature.id === CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED && feature.enabled);
+ const areDistanceRatesEnabled = !!featuresMap?.find((feature) => feature.id === CONST.POLICY.MORE_FEATURES.ARE_DISTANCE_RATES_ENABLED && feature.enabled);
// WARNING: The data below should be kept in sync with the API so we create the policy with the correct configuration.
const optimisticData: Array<
@@ -2492,7 +2469,7 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData Categories with the side panel open so
- * the #admins room is visible in Concierge Anywhere.
- */
-function navigateToSubmitWorkspaceAfterOnboarding(policyID?: string, isSmallScreenWidth = false) {
- setDisableDismissOnEscape(false);
-
- if (!policyID) {
- Navigation.navigate(ROUTES.HOME);
- return;
- }
-
- setOnboardingRHPVariant(CONST.ONBOARDING_RHP_VARIANT.RHP_ADMINS_ROOM);
- Navigation.navigate(ROUTES.WORKSPACES_LIST.route);
- Navigation.setNavigationActionToMicrotaskQueue(() => {
- Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID));
- SidePanelActions.openSidePanel(!isSmallScreenWidth);
- });
-}
-
-function navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue(policyID?: string, isSmallScreenWidth = false) {
- Navigation.dismissModal();
- Navigation.setNavigationActionToMicrotaskQueue(() => {
- navigateToSubmitWorkspaceAfterOnboarding(policyID, isSmallScreenWidth);
- });
-}
-
-export {navigateAfterOnboarding, navigateAfterOnboardingWithMicrotaskQueue, navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue};
+export {navigateAfterOnboarding, navigateAfterOnboardingWithMicrotaskQueue};
diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
index 956f91be9169..85b3e67b4bc8 100644
--- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
+++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
@@ -10,7 +10,6 @@ import Text from '@components/Text';
import TextInput from '@components/TextInput';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet';
-import useAutoCreateSubmitWorkspace from '@hooks/useAutoCreateSubmitWorkspace';
import useAutoCreateTrackWorkspace from '@hooks/useAutoCreateTrackWorkspace';
import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
@@ -55,7 +54,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
const [onboardingPersonalDetailsForm] = useOnyx(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
const autoCreateTrackWorkspace = useAutoCreateTrackWorkspace();
- const autoCreateSubmitWorkspace = useAutoCreateSubmitWorkspace();
// When we merge public email with work email, we now want to navigate to the
// concierge chat report of the new work email and not the last accessed report.
@@ -66,7 +64,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
const {inputCallbackRef} = useAutoFocusInput();
const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false);
const {isBetaEnabled} = usePermissions();
- const canUseSubmit2026 = isBetaEnabled(CONST.BETAS.SUBMIT_2026);
const onboardingStep = useOnboardingStepCounter(SCREENS.ONBOARDING.PERSONAL_DETAILS);
const isPrivateDomainAndHasAccessiblePolicies = !account?.isFromPublicDomain && !!account?.hasAccessibleDomainPolicies;
@@ -134,16 +131,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
clearPersonalDetailsDraft();
setPersonalDetails(firstName, lastName);
- if (onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.EMPLOYER && canUseSubmit2026) {
- if (isPrivateDomainAndHasAccessiblePolicies && isValidated) {
- Navigation.navigate(ROUTES.ONBOARDING_WORKSPACES.getRoute(route.params?.backTo));
- return;
- }
- updateDisplayName(firstName, lastName, formatPhoneNumber, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? '');
- autoCreateSubmitWorkspace(firstName, lastName);
- return;
- }
-
if (isPrivateDomainAndHasAccessiblePolicies && (!onboardingPurposeSelected || isVsb || isSmb)) {
const nextRoute = isValidated ? ROUTES.ONBOARDING_WORKSPACES : ROUTES.ONBOARDING_PRIVATE_DOMAIN;
Navigation.navigate(nextRoute.getRoute(route.params?.backTo));
@@ -170,14 +157,12 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
session?.email,
isPrivateDomainAndHasAccessiblePolicies,
onboardingPurposeSelected,
- canUseSubmit2026,
isVsb,
isSmb,
completeOnboarding,
isValidated,
route.params?.backTo,
autoCreateTrackWorkspace,
- autoCreateSubmitWorkspace,
],
);
diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx
index c33ec373f627..2643a641699a 100644
--- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx
+++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx
@@ -9,21 +9,18 @@ import type {MenuItemProps} from '@components/MenuItem';
import MenuItemList from '@components/MenuItemList';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
-import useAutoCreateSubmitWorkspace from '@hooks/useAutoCreateSubmitWorkspace';
import useAutoCreateTrackWorkspace from '@hooks/useAutoCreateTrackWorkspace';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useOnboardingMessages from '@hooks/useOnboardingMessages';
import useOnboardingStepCounter from '@hooks/useOnboardingStepCounter';
import useOnyx from '@hooks/useOnyx';
-import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import OnboardingRefManager from '@libs/OnboardingRefManager';
import type {TOnboardingRef} from '@libs/OnboardingRefManager';
-import {isCurrentUserValidated} from '@libs/UserUtils';
import variables from '@styles/variables';
import {completeOnboarding} from '@userActions/Report';
import {setOnboardingErrorMessage, setOnboardingPurposeSelected} from '@userActions/Welcome';
@@ -77,12 +74,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
const [betas] = useOnyx(ONYXKEYS.BETAS);
- const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
- const [session] = useOnyx(ONYXKEYS.SESSION);
- const {isBetaEnabled} = usePermissions();
- const canUseSubmit2026 = isBetaEnabled(CONST.BETAS.SUBMIT_2026);
- const isValidated = isCurrentUserValidated(loginList, session?.email);
- const autoCreateSubmitWorkspace = useAutoCreateSubmitWorkspace();
const autoCreateTrackWorkspace = useAutoCreateTrackWorkspace();
const paddingHorizontal = onboardingIsMediumOrLargerScreenWidth ? styles.ph8 : styles.ph5;
@@ -111,23 +102,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro
return;
}
- if (choice === CONST.ONBOARDING_CHOICES.EMPLOYER && canUseSubmit2026) {
- if (isPrivateDomainAndHasAccessiblePolicies && isValidated) {
- Navigation.navigate(
- personalDetailsForm?.firstName ? ROUTES.ONBOARDING_WORKSPACES.getRoute(route.params?.backTo) : ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(route.params?.backTo),
- );
- return;
- }
-
- if (personalDetailsForm?.firstName) {
- autoCreateSubmitWorkspace(personalDetailsForm.firstName, personalDetailsForm.lastName ?? '');
- return;
- }
-
- Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(route.params?.backTo));
- return;
- }
-
if (isPrivateDomainAndHasAccessiblePolicies && personalDetailsForm?.firstName) {
if (choice === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND) {
autoCreateTrackWorkspace(personalDetailsForm.firstName, personalDetailsForm.lastName ?? '', choice);
diff --git a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx
index 6d81539a268d..1814b46388d6 100644
--- a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx
+++ b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx
@@ -9,7 +9,6 @@ import SelectionList from '@components/SelectionList';
import BareUserListItem from '@components/SelectionList/ListItem/BareUserListItem';
import Text from '@components/Text';
import useArchivedReportsIdSet from '@hooks/useArchivedReportsIdSet';
-import useAutoCreateSubmitWorkspace from '@hooks/useAutoCreateSubmitWorkspace';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
@@ -20,7 +19,7 @@ import usePermissions from '@hooks/usePermissions';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import {navigateAfterOnboardingWithMicrotaskQueue, navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding';
+import {navigateAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding';
import Navigation from '@libs/Navigation/Navigation';
import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils';
import {isCurrentUserValidated} from '@libs/UserUtils';
@@ -70,34 +69,20 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding
const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
const isVsb = onboardingValues?.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.VSB;
const isSmb = onboardingValues?.signupQualifier === CONST.ONBOARDING_SIGNUP_QUALIFIERS.SMB;
- const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED);
- const canUseSubmit2026 = isBetaEnabled(CONST.BETAS.SUBMIT_2026);
- const isEmployerWithSubmit = onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.EMPLOYER && canUseSubmit2026;
- const autoCreateSubmitWorkspace = useAutoCreateSubmitWorkspace();
const shouldHideBackButton = onboardingValues?.shouldValidate === false && route.params?.backTo === ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute();
const onboardingStep = useOnboardingStepCounter(SCREENS.ONBOARDING.WORKSPACES);
const handleJoinWorkspace = (policy: JoinablePolicy) => {
- // Only mirror the EMPLOYER ("Get paid back by my employer") + Submit-2026 onboarding flow
- // when the user actually picked EMPLOYER, or when no Purpose was selected (private-domain
- // users who reach this screen without going through the Purpose step). Users on the Submit
- // beta who picked a different Purpose (e.g. MANAGE_TEAM) must not be re-routed through
- // the Submit flow.
- const shouldUseSubmitFlow = canUseSubmit2026 && (!onboardingPurposeSelected || onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.EMPLOYER);
-
if (policy.automaticJoiningEnabled) {
joinAccessiblePolicy(policy.policyID);
} else {
askToJoinPolicy(policy.policyID);
}
-
- const engagementChoice = shouldUseSubmitFlow ? CONST.ONBOARDING_CHOICES.EMPLOYER : CONST.ONBOARDING_CHOICES.LOOKING_AROUND;
completeOnboarding({
- engagementChoice,
- onboardingMessage: onboardingMessages[engagementChoice],
+ engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND,
+ onboardingMessage: onboardingMessages[CONST.ONBOARDING_CHOICES.LOOKING_AROUND],
firstName: onboardingPersonalDetails?.firstName ?? '',
lastName: onboardingPersonalDetails?.lastName ?? '',
- onboardingPolicyID: shouldUseSubmitFlow && policy.automaticJoiningEnabled ? policy.policyID : undefined,
companySize: onboardingCompanySize,
introSelected,
isSelfTourViewed,
@@ -106,11 +91,6 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding
setOnboardingAdminsChatReportID();
setOnboardingPolicyID(policy.policyID);
- if (shouldUseSubmitFlow && policy.automaticJoiningEnabled) {
- navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue(policy.policyID, isSmallScreenWidth);
- return;
- }
-
navigateAfterOnboardingWithMicrotaskQueue(
isSmallScreenWidth,
isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS),
@@ -166,11 +146,6 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding
});
const skipJoiningWorkspaces = () => {
- if (isEmployerWithSubmit) {
- autoCreateSubmitWorkspace(onboardingPersonalDetails?.firstName ?? '', onboardingPersonalDetails?.lastName ?? '');
- return;
- }
-
if (isVsb) {
Navigation.navigate(ROUTES.ONBOARDING_ACCOUNTING.getRoute(route.params?.backTo));
return;
diff --git a/src/pages/workspace/AccessOrNotFoundWrapper.tsx b/src/pages/workspace/AccessOrNotFoundWrapper.tsx
index 636662d1062d..87fab021f830 100644
--- a/src/pages/workspace/AccessOrNotFoundWrapper.tsx
+++ b/src/pages/workspace/AccessOrNotFoundWrapper.tsx
@@ -14,7 +14,7 @@ import {openWorkspace} from '@libs/actions/Policy/Policy';
import {isValidMoneyRequestType} from '@libs/IOUUtils';
import goBackFromWorkspaceSettingPages from '@libs/Navigation/helpers/goBackFromWorkspaceSettingPages';
import Navigation from '@libs/Navigation/Navigation';
-import {canEditWorkspaceSettings, canSendInvoice, isControlPolicy, isGroupPolicy, isPolicyAccessible, isPolicyFeatureEnabled as isPolicyFeatureEnabledUtil} from '@libs/PolicyUtils';
+import {canSendInvoice, isControlPolicy, isPaidGroupPolicy, isPolicyAccessible, isPolicyAdmin, isPolicyFeatureEnabled as isPolicyFeatureEnabledUtil} from '@libs/PolicyUtils';
import {canCreateRequest} from '@libs/ReportUtils';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
@@ -30,9 +30,9 @@ import callOrReturn from '@src/types/utils/callOrReturn';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
const ACCESS_VARIANTS = {
- [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => isGroupPolicy(policy),
+ [CONST.POLICY.ACCESS_VARIANTS.PAID]: (policy: OnyxEntry) => isPaidGroupPolicy(policy),
[CONST.POLICY.ACCESS_VARIANTS.CONTROL]: (policy: OnyxEntry) => isControlPolicy(policy),
- [CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry) => canEditWorkspaceSettings(policy),
+ [CONST.POLICY.ACCESS_VARIANTS.ADMIN]: (policy: OnyxEntry, login: string) => isPolicyAdmin(policy, login),
[CONST.IOU.ACCESS_VARIANTS.CREATE]: (
policy: OnyxEntry,
login: string,
@@ -164,8 +164,7 @@ function AccessOrNotFoundWrapper({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPolicyIDInRoute, policyID]);
- const isPolicyEmpty = !Object.entries(policy ?? {}).length || !policy?.id;
- const shouldShowFullScreenLoadingIndicator = !isMoneyRequest && (isLoadingReportData !== false || !!policy?.isLoading) && isPolicyEmpty;
+ const shouldShowFullScreenLoadingIndicator = !isMoneyRequest && isLoadingReportData !== false && (!Object.entries(policy ?? {}).length || !policy?.id);
const isFeatureEnabled = featureName ? isPolicyFeatureEnabledUtil(policy, featureName) : true;
diff --git a/src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx b/src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx
index cb97854f9c0b..d5de898ef1c1 100644
--- a/src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx
+++ b/src/pages/workspace/DynamicWorkspaceOverviewPlanTypePage.tsx
@@ -55,17 +55,7 @@ function DynamicWorkspaceOverviewPlanTypePage({policy}: WithPolicyProps) {
}, [policy?.type]);
const workspacePlanTypes = Object.values(CONST.POLICY.TYPE)
- .filter((type) => {
- if (type === CONST.POLICY.TYPE.PERSONAL) {
- return false;
- }
- // Guard: don't leak the SUBMIT plan type into the plan-type list for paid workspaces.
- // Submit-specific plan-type UX (exposing SUBMIT for Submit policies) ships in #87263.
- if (type === CONST.POLICY.TYPE.SUBMIT) {
- return false;
- }
- return true;
- })
+ .filter((type) => type !== CONST.POLICY.TYPE.PERSONAL)
.map((policyType) => ({
value: policyType,
text: translate(`workspace.planTypePage.planTypes.${policyType as PersonalPolicyTypeExcludedProps}.label`),
@@ -91,14 +81,6 @@ function DynamicWorkspaceOverviewPlanTypePage({policy}: WithPolicyProps) {
) : null;
const handleUpdatePlan = () => {
- // Submit policies don't expose SUBMIT in the option list, but the editor can
- // still pick Team/Corporate. Route any selection from a Submit policy to the
- // upgrade screen — the polished Submit-specific upgrade UX ships in #87263.
- if (policyID && policy?.type === CONST.POLICY.TYPE.SUBMIT && (currentPlan === CONST.POLICY.TYPE.TEAM || currentPlan === CONST.POLICY.TYPE.CORPORATE)) {
- Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID));
- return;
- }
-
if (policyID && policy?.type === CONST.POLICY.TYPE.TEAM && currentPlan === CONST.POLICY.TYPE.CORPORATE) {
Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID));
return;
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 19d3961f93bf..ad9bc4901466 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -14,6 +14,7 @@ import ScrollView from '@components/ScrollView';
import useCardFeedErrors from '@hooks/useCardFeedErrors';
import useConfirmReadyToOpenApp from '@hooks/useConfirmReadyToOpenApp';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
+import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useGetReceiptPartnersIntegrationData from '@hooks/useGetReceiptPartnersIntegrationData';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
@@ -32,14 +33,14 @@ import {clearErrors, openPolicyInitialPage, removeWorkspace} from '@libs/actions
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import {
- canEditWorkspaceSettings,
canPolicyAccessFeature,
shouldShowPolicy as checkIfShouldShowPolicy,
goBackFromInvalidPolicy,
hasAccountingFeatureConnection,
hasPolicyCategoriesError,
- isGroupPolicy,
+ isPaidGroupPolicy,
isPendingDeletePolicy,
+ isPolicyAdmin,
isTimeTrackingEnabled,
shouldShowEmployeeListError,
shouldShowSyncError,
@@ -94,7 +95,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
const {translate} = useLocalize();
const {convertToDisplayString} = useCurrencyListActions();
const {isBetaEnabled} = usePermissions();
-
+ const {login} = useCurrentUserPersonalDetails();
const isFocused = useIsFocused();
const activeRoute = useNavigationState((state) => findFocusedRoute(state)?.name);
const waitForNavigate = useWaitForNavigation();
@@ -133,8 +134,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
const policyName = policy?.name ?? '';
const hasPolicyCreationError = policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && !isEmptyObject(policy.errors);
- const shouldShowProtectedItems = canEditWorkspaceSettings(policy);
-
+ const shouldShowProtectedItems = isPolicyAdmin(policy, login);
const accountingConnectionNames = CONST.POLICY.CONNECTIONS.ACCOUNTING_CONNECTION_NAMES;
const hasSyncError = shouldShowSyncError(policy, isConnectionInProgress(connectionSyncProgress, policy), accountingConnectionNames);
const hasMembersError = shouldShowEmployeeListError(policy);
@@ -184,9 +184,8 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
const shouldShowPolicy = checkIfShouldShowPolicy(policy, true, currentUserLogin);
const isPendingDelete = isPendingDeletePolicy(policy);
const prevIsPendingDelete = isPendingDeletePolicy(prevPolicy);
- // While the policy is being fetched (e.g., right after joinAccessiblePolicy), the role is not yet populated,
- // so checkIfShouldShowPolicy returns false. Suppress NotFound during this loading window.
- const shouldShowNotFoundPage = !shouldShowPolicy && !policy?.isLoading && (!isPendingDelete || prevIsPendingDelete);
+
+ const shouldShowNotFoundPage = !shouldShowPolicy && (!isPendingDelete || prevIsPendingDelete);
const fetchPolicyData = () => {
if (policyDraft?.id || !isFocused) {
return;
@@ -228,7 +227,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
},
];
- if (isGroupPolicy(policy) && shouldShowProtectedItems) {
+ if (isPaidGroupPolicy(policy) && shouldShowProtectedItems) {
workspaceMenuItems.push({
translationKey: 'common.reports',
icon: expensifyIcons.Document,
diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx
index 2d13f4524d63..8ef72782aad5 100644
--- a/src/pages/workspace/WorkspaceMembersPage.tsx
+++ b/src/pages/workspace/WorkspaceMembersPage.tsx
@@ -62,13 +62,13 @@ import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import {isPersonalDetailsReady, sortAlphabetically} from '@libs/OptionsListUtils';
import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils';
import {
- canEditWorkspaceSettings,
getConnectionExporters,
getMemberAccountIDsForWorkspace,
isControlPolicy,
isDeletedPolicyEmployee,
isExpensifyTeam,
isPaidGroupPolicy,
+ isPolicyAdmin as isPolicyAdminUtils,
isPolicyApprover,
} from '@libs/PolicyUtils';
import {getDisplayNameForParticipant} from '@libs/ReportUtils';
@@ -155,7 +155,7 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply the correct modal type for the decision modal
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
- const isPolicyAdmin = canEditWorkspaceSettings(policy);
+ const isPolicyAdmin = isPolicyAdminUtils(policy);
const isLoading = useMemo(
() => !isOfflineAndNoMemberDataAvailable && (!isPersonalDetailsReady(personalDetails) || isEmptyObject(policy?.employeeList)),
[isOfflineAndNoMemberDataAvailable, personalDetails, policy?.employeeList],
diff --git a/src/pages/workspace/WorkspaceOverviewPage.tsx b/src/pages/workspace/WorkspaceOverviewPage.tsx
index 4f8d0cec2cc7..445882b87b8b 100644
--- a/src/pages/workspace/WorkspaceOverviewPage.tsx
+++ b/src/pages/workspace/WorkspaceOverviewPage.tsx
@@ -53,7 +53,6 @@ import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import {
- canEditWorkspaceSettings,
getConnectionExporters,
getUserFriendlyWorkspaceType,
goBackFromInvalidPolicy,
@@ -110,7 +109,7 @@ function WorkspaceOverviewPage({policyDraft, policy: policyProp, route}: Workspa
const isBankAccountVerified = !!settings?.paymentBankAccountID;
const shouldBlockCurrencyChange = useShouldBlockCurrencyChange(policyID);
- const isPolicyAdmin = canEditWorkspaceSettings(policy);
+ const isPolicyAdmin = isPolicyAdminPolicyUtils(policy);
const outputCurrency = policy?.outputCurrency ?? '';
const currencySymbol = getCurrencySymbol(outputCurrency) ?? '';
const formattedCurrency = !isEmptyObject(policy) ? `${outputCurrency} - ${currencySymbol}` : '';
@@ -178,7 +177,7 @@ function WorkspaceOverviewPage({policyDraft, policy: policyProp, route}: Workspa
const policyName = policy?.name ?? '';
const policyDescription = policy?.description ?? translate('workspace.common.defaultDescription');
const policyCurrency = policy?.outputCurrency ?? '';
- const readOnly = !canEditWorkspaceSettings(policy);
+ const readOnly = !isPolicyAdminPolicyUtils(policy);
const currencyReadOnly = readOnly || isBankAccountVerified;
const isOwner = isPolicyOwner(policy, currentUserPersonalDetails.accountID);
const shouldShowAddress = !readOnly || !!formattedAddress;
diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx
index 1d90f6414c89..a2e1c8846792 100644
--- a/src/pages/workspace/WorkspacePageWithSections.tsx
+++ b/src/pages/workspace/WorkspacePageWithSections.tsx
@@ -19,7 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {openWorkspaceView} from '@libs/actions/BankAccounts';
import goBackFromWorkspaceSettingPages from '@libs/Navigation/helpers/goBackFromWorkspaceSettingPages';
import Navigation from '@libs/Navigation/Navigation';
-import {canEditWorkspaceSettings, isPendingDeletePolicy, shouldShowPolicy as shouldShowPolicyUtil} from '@libs/PolicyUtils';
+import {isPendingDeletePolicy, isPolicyAdmin, shouldShowPolicy as shouldShowPolicyUtil} from '@libs/PolicyUtils';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -172,7 +172,7 @@ function WorkspacePageWithSections({
}
// We check isPendingDelete and prevIsPendingDelete to prevent the NotFound view from showing right after we delete the workspace
- return (!isEmptyObject(policy) && !canEditWorkspaceSettings(policy) && !shouldShowNonAdmin) || (!shouldShowPolicy && !(isPendingDelete && !prevIsPendingDelete));
+ return (!isEmptyObject(policy) && !isPolicyAdmin(policy) && !shouldShowNonAdmin) || (!shouldShowPolicy && !(isPendingDelete && !prevIsPendingDelete));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [policy, shouldShowNonAdmin, shouldShowPolicy]);
diff --git a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx
index 576f4af16821..8661e2e0a8b4 100644
--- a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx
+++ b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx
@@ -15,7 +15,7 @@ import {updateXeroMappings} from '@libs/actions/connections/Xero';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
-import {canEditWorkspaceSettings, canModifyPlan, getDefaultApprover, getPerDiemCustomUnit, isControlPolicy} from '@libs/PolicyUtils';
+import {canModifyPlan, getDefaultApprover, getPerDiemCustomUnit, isControlPolicy} from '@libs/PolicyUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
import {enablePerDiem} from '@userActions/Policy/PerDiem';
import CONST from '@src/CONST';
@@ -231,10 +231,7 @@ function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) {
}, [isUpgraded, canPerformUpgrade, confirmUpgrade]),
);
- // Gate the page to users who can edit workspace settings (admins on any policy,
- // or editors on Submit policies). `canPerformUpgrade` (strict admin) still controls
- // whether the upgrade button is active, so editors see the intro but can't upgrade.
- if (!canEditWorkspaceSettings(policy)) {
+ if (!canPerformUpgrade) {
return ;
}
@@ -267,7 +264,7 @@ function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) {
policyID={policyID}
feature={feature}
onUpgrade={onUpgradeToCorporate}
- buttonDisabled={isOffline || !canPerformUpgrade}
+ buttonDisabled={isOffline}
loading={policy?.isPendingUpgrade}
backTo={route.params.backTo}
/>
diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx
index 72e265ee83dd..17cbe5df9fc7 100644
--- a/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx
+++ b/src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx
@@ -14,7 +14,7 @@ import {getLatestErrorField} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
-import {canEditWorkspaceSettings, getCorrectedAutoReportingFrequency, goBackFromInvalidPolicy, isGroupPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
+import {getCorrectedAutoReportingFrequency, goBackFromInvalidPolicy, isPaidGroupPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy';
@@ -118,7 +118,7 @@ function WorkspaceAutoReportingFrequencyPage({policy, route}: WorkspaceAutoRepor
diff --git a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx
index 32e450265fe0..c45a8debd9f3 100644
--- a/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx
+++ b/src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx
@@ -9,7 +9,7 @@ import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
-import {canEditWorkspaceSettings, goBackFromInvalidPolicy, isGroupPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
+import {goBackFromInvalidPolicy, isPaidGroupPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicy from '@pages/workspace/withPolicy';
import type {WithPolicyOnyxProps} from '@pages/workspace/withPolicy';
@@ -94,7 +94,7 @@ function WorkspaceAutoReportingMonthlyOffsetPage({policy, route}: WorkspaceAutoR
diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
index 40fb435a3dc8..56618ac811b5 100644
--- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
+++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
@@ -49,11 +49,10 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig
import {getPaymentMethodDescription} from '@libs/PaymentUtils';
import {getDisplayNameOrDefault, getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
import {
- canEditWorkspaceSettings,
getCorrectedAutoReportingFrequency,
hasDynamicExternalWorkflow,
isControlPolicy,
- isGroupPolicy as isGroupPolicyUtil,
+ isPaidGroupPolicy as isPaidGroupPolicyUtil,
isPolicyAdmin as isPolicyAdminUtil,
} from '@libs/PolicyUtils';
import {hasInProgressVBBA} from '@libs/ReimbursementAccountUtils';
@@ -631,7 +630,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
);
- const isGroupPolicy = isGroupPolicyUtil(policy);
+ const isPaidGroupPolicy = isPaidGroupPolicyUtil(policy);
const isLoading = !!(policy?.isLoading && policy?.reimbursementChoice === undefined);
return (
@@ -644,7 +643,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
icon={illustrations.Workflows}
route={route}
shouldShowOfflineIndicatorInWideScreen
- shouldShowNotFoundPage={!isGroupPolicy || !canEditWorkspaceSettings(policy)}
+ shouldShowNotFoundPage={!isPaidGroupPolicy || !isPolicyAdmin}
isLoading={isLoading}
shouldShowLoading={isLoading}
shouldUseScrollView
diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApprovalLimitPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApprovalLimitPage.tsx
index f7822beb595f..5e14701cac06 100644
--- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApprovalLimitPage.tsx
+++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsApprovalLimitPage.tsx
@@ -23,7 +23,7 @@ import {convertToBackendAmount, convertToFrontendAmountAsString} from '@libs/Cur
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
-import {canEditWorkspaceSettings, goBackFromInvalidPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
+import {goBackFromInvalidPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
@@ -65,7 +65,7 @@ function WorkspaceWorkflowsApprovalsApprovalLimitPage({policy, isLoadingReportDa
const selectedApproverPersonalDetails = selectedApproverEmail ? personalDetailsByEmail?.[selectedApproverEmail] : undefined;
const selectedApproverDisplayName = selectedApproverEmail ? Str.removeSMSDomain(selectedApproverPersonalDetails?.displayName ?? selectedApproverEmail) : '';
- const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !canEditWorkspaceSettings(policy) || isPendingDeletePolicy(policy);
+ const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !isPolicyAdmin(policy) || isPendingDeletePolicy(policy);
const approverDisplayName = currentApprover ? Str.removeSMSDomain(currentApprover.displayName) : '';
const isApproverSelected = isEditFlow ? approverDisplayName.length > 0 : true;
diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx
index 2c81ebc003c4..15da8a38321d 100644
--- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx
+++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsCreatePage.tsx
@@ -13,7 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
-import {canEditWorkspaceSettings, goBackFromInvalidPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
+import {goBackFromInvalidPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
@@ -36,7 +36,7 @@ function WorkspaceWorkflowsApprovalsCreatePage({policy, isLoadingReportData = tr
const [addExpenseApprovalsTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${addExpenseApprovalsTaskReportID}`);
const formRef = useRef(null);
- const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !canEditWorkspaceSettings(policy) || isPendingDeletePolicy(policy);
+ const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !isPolicyAdmin(policy) || isPendingDeletePolicy(policy);
const createApprovalWorkflow = useCallback(() => {
if (!approvalWorkflow) {
diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx
index d43a130d3aa8..6ada3231860a 100644
--- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx
+++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx
@@ -18,7 +18,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
-import {canEditWorkspaceSettings, goBackFromInvalidPolicy, isPendingDeletePolicy} from '@libs/PolicyUtils';
+import {goBackFromInvalidPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import {convertPolicyEmployeesToApprovalWorkflows, mergeWorkflowMembersWithAvailableMembers} from '@libs/WorkflowUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
@@ -102,7 +102,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true
const {currentApprovalWorkflow, defaultWorkflowMembers, usedApproverEmails} = getApprovalWorkflowData();
- const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !canEditWorkspaceSettings(policy) || isPendingDeletePolicy(policy) || !currentApprovalWorkflow;
+ const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !isPolicyAdmin(policy) || isPendingDeletePolicy(policy) || !currentApprovalWorkflow;
// Set the initial approval workflow when the page is loaded
useEffect(() => {
diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx
index dcb9a4639f43..cc9f680177a7 100644
--- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx
+++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsExpensesFromPage.tsx
@@ -15,7 +15,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils';
-import {canEditWorkspaceSettings, getDefaultApprover, getMemberAccountIDsForWorkspace, isPendingDeletePolicy} from '@libs/PolicyUtils';
+import {getDefaultApprover, getMemberAccountIDsForWorkspace, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import MemberRightIcon from '@pages/workspace/MemberRightIcon';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
@@ -41,7 +41,7 @@ function WorkspaceWorkflowsApprovalsExpensesFromPage({policy, isLoadingReportDat
const isLoadingApprovalWorkflow = isLoadingOnyxValue(approvalWorkflowResults);
const [selectedMembers, setSelectedMembers] = useState([]);
- const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !canEditWorkspaceSettings(policy) || isPendingDeletePolicy(policy);
+ const shouldShowNotFoundView = (isEmptyObject(policy) && !isLoadingReportData) || !isPolicyAdmin(policy) || isPendingDeletePolicy(policy);
const isInitialCreationFlow = approvalWorkflow?.action === CONST.APPROVAL_WORKFLOW.ACTION.CREATE && approvalWorkflow?.isInitialFlow;
const shouldShowListEmptyContent = !isLoadingApprovalWorkflow && approvalWorkflow?.availableMembers.length === 0;
const firstApprover = approvalWorkflow?.originalApprovers?.[0]?.email ?? '';
diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts
index eb859463f640..fdb480dcea5b 100644
--- a/tests/actions/PolicyTest.ts
+++ b/tests/actions/PolicyTest.ts
@@ -1115,43 +1115,6 @@ describe('actions/Policy', () => {
});
});
- it('creates a Submit workspace with ADVANCED approval mode and correct feature flags', async () => {
- const policyID = Policy.generatePolicyID();
- Policy.createWorkspace({
- policyOwnerEmail: ESH_EMAIL,
- makeMeAdmin: true,
- policyName: WORKSPACE_NAME,
- policyID,
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- introSelected: {choice: CONST.ONBOARDING_CHOICES.EMPLOYER},
- currentUserAccountIDParam: ESH_ACCOUNT_ID,
- currentUserEmailParam: ESH_EMAIL,
- isSelfTourViewed: false,
- betas: undefined,
- hasActiveAdminPolicies: false,
- activePolicy: undefined,
- type: CONST.POLICY.TYPE.SUBMIT,
- });
- await waitForBatchedUpdates();
-
- await TestHelper.getOnyxData({
- key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
- waitForCollectionCallback: false,
- callback: (policy) => {
- expect(policy?.type).toBe(CONST.POLICY.TYPE.SUBMIT);
- expect(policy?.role).toBe(CONST.POLICY.ROLE.EDITOR);
- expect(policy?.approvalMode).toBe(CONST.POLICY.APPROVAL_MODE.ADVANCED);
- expect(policy?.areWorkflowsEnabled).toBe(true);
- expect(policy?.areTagsEnabled).toBe(true);
- expect(policy?.areDistanceRatesEnabled).toBe(true);
- expect(policy?.areCompanyCardsEnabled).toBe(false);
- expect(policy?.harvesting?.enabled).toBe(false);
- expect(policy?.autoReportingFrequency).toBe(CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE);
- expect(policy?.employeeList?.[ESH_EMAIL]?.role).toBe(CONST.POLICY.ROLE.EDITOR);
- },
- });
- });
-
it('should pass areDistanceRatesEnabled as true when creating workspace with distance rates feature enabled', async () => {
await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID});
await waitForBatchedUpdates();
diff --git a/tests/ui/OnboardingPurpose.tsx b/tests/ui/OnboardingPurpose.tsx
index 5a487f15f185..1b48f6e54cce 100644
--- a/tests/ui/OnboardingPurpose.tsx
+++ b/tests/ui/OnboardingPurpose.tsx
@@ -13,7 +13,6 @@ import Navigation from '@libs/Navigation/Navigation';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types';
import OnboardingPurpose from '@pages/OnboardingPurpose';
-import {createWorkspace} from '@userActions/Policy/Policy';
import {completeOnboarding} from '@userActions/Report';
import CONST from '@src/CONST';
import IntlStore from '@src/languages/IntlStore';
@@ -25,7 +24,6 @@ import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
const mockCompleteOnboarding = jest.mocked(completeOnboarding);
-const mockCreateWorkspace = jest.mocked(createWorkspace);
jest.mock('@userActions/Report', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -37,19 +35,6 @@ jest.mock('@userActions/Report', () => {
};
});
-jest.mock('@userActions/Policy/Policy', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const actual = jest.requireActual('@userActions/Policy/Policy');
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return {
- ...actual,
- createWorkspace: jest.fn().mockReturnValue({
- policyID: 'test-policy-id',
- adminsChatReportID: 'test-admins-report-id',
- }),
- };
-});
-
TestHelper.setupGlobalFetchMock();
// Helper to translate onboarding purpose keys that use dynamic CONST values
@@ -153,132 +138,6 @@ describe('OnboardingPurpose Page', () => {
await waitForBatchedUpdatesWithAct();
});
- it('should navigate to personal details page when user selects EMPLOYER with Submit2026 beta and is from public domain', async () => {
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.ACCOUNT, {
- isFromPublicDomain: true,
- hasAccessibleDomainPolicies: false,
- });
- await Onyx.merge(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- });
-
- const {unmount} = renderOnboardingPurposePage(SCREENS.ONBOARDING.PURPOSE, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- const user = userEvent.setup();
- const employerLabel = translatePurpose(CONST.ONBOARDING_CHOICES.EMPLOYER);
- const employerOption = screen.getByLabelText(employerLabel);
- await user.press(employerOption);
-
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(ROUTES.ONBOARDING_PERSONAL_DETAILS.getRoute(''));
- });
-
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
-
- it('should navigate to workspaces page when user selects EMPLOYER with Submit2026 beta and is from private domain with name set', async () => {
- const testEmail = 'test@user.com';
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.ACCOUNT, {
- isFromPublicDomain: false,
- hasAccessibleDomainPolicies: true,
- });
- await Onyx.merge(ONYXKEYS.LOGIN_LIST, {
- [testEmail]: {
- partnerName: 'expensify.com',
- partnerUserID: testEmail,
- validatedDate: 'fake-validatedDate',
- },
- });
- await Onyx.merge(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- await Onyx.merge(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM, {
- firstName: 'Test',
- lastName: 'User',
- });
- });
-
- const {unmount} = renderOnboardingPurposePage(SCREENS.ONBOARDING.PURPOSE, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- const user = userEvent.setup();
- const employerLabel = translatePurpose(CONST.ONBOARDING_CHOICES.EMPLOYER);
- const employerOption = screen.getByLabelText(employerLabel);
- await user.press(employerOption);
-
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(ROUTES.ONBOARDING_WORKSPACES.getRoute(''));
- });
-
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
-
- it('should create a Submit workspace from Purpose when EMPLOYER is selected and personal details already exist', async () => {
- jest.spyOn(Navigation, 'dismissModal').mockImplementation(() => {});
- jest.spyOn(Navigation, 'setNavigationActionToMicrotaskQueue').mockImplementation((callback: () => void) => callback());
-
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.ACCOUNT, {
- isFromPublicDomain: true,
- hasAccessibleDomainPolicies: false,
- });
- await Onyx.merge(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- await Onyx.merge(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM, {
- firstName: 'Test',
- lastName: 'User',
- });
- });
-
- const onyxSetSpy = jest.spyOn(Onyx, 'set');
- onyxSetSpy.mockClear();
-
- const {unmount} = renderOnboardingPurposePage(SCREENS.ONBOARDING.PURPOSE, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- const user = userEvent.setup();
- const employerLabel = translatePurpose(CONST.ONBOARDING_CHOICES.EMPLOYER);
- const employerOption = screen.getByLabelText(employerLabel);
- await user.press(employerOption);
-
- await waitFor(() => {
- expect(mockCreateWorkspace).toHaveBeenCalledWith(
- expect.objectContaining({
- type: CONST.POLICY.TYPE.SUBMIT,
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- }),
- );
- });
-
- await waitFor(() => {
- expect(mockCompleteOnboarding).toHaveBeenCalledWith(
- expect.objectContaining({
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- onboardingPolicyID: 'test-policy-id',
- }),
- );
- });
-
- await waitFor(() => {
- expect(onyxSetSpy).toHaveBeenCalledWith(ONYXKEYS.NVP_ONBOARDING_RHP_VARIANT, CONST.ONBOARDING_RHP_VARIANT.RHP_ADMINS_ROOM);
- expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_CATEGORIES.getRoute('test-policy-id'));
- });
-
- onyxSetSpy.mockRestore();
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
-
it('should call completeOnboarding with introSelected when user is from private domain and selects a direct-complete choice', async () => {
await TestHelper.signInWithTestUser();
diff --git a/tests/ui/PersonalDetailsOnboarding.tsx b/tests/ui/PersonalDetailsOnboarding.tsx
index d7a7dde9d632..d165bcc96aaa 100644
--- a/tests/ui/PersonalDetailsOnboarding.tsx
+++ b/tests/ui/PersonalDetailsOnboarding.tsx
@@ -14,8 +14,6 @@ import Navigation from '@libs/Navigation/Navigation';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import type {OnboardingModalNavigatorParamList} from '@navigation/types';
import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails';
-import {createWorkspace} from '@userActions/Policy/Policy';
-import {completeOnboarding} from '@userActions/Report';
import CONST from '@src/CONST';
import IntlStore from '@src/languages/IntlStore';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -24,32 +22,6 @@ import SCREENS from '@src/SCREENS';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
-const mockCreateWorkspace = jest.mocked(createWorkspace);
-const mockCompleteOnboarding = jest.mocked(completeOnboarding);
-
-jest.mock('@userActions/Report', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const actual = jest.requireActual('@userActions/Report');
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return {
- ...actual,
- completeOnboarding: jest.fn(),
- };
-});
-
-jest.mock('@userActions/Policy/Policy', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const actual = jest.requireActual('@userActions/Policy/Policy');
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return {
- ...actual,
- createWorkspace: jest.fn().mockReturnValue({
- policyID: 'test-policy-id',
- adminsChatReportID: 'test-admins-report-id',
- }),
- };
-});
-
TestHelper.setupGlobalFetchMock();
const Stack = createPlatformStackNavigator();
@@ -171,84 +143,4 @@ describe('OnboardingPersonalDetails Page', () => {
unmount();
await waitForBatchedUpdatesWithAct();
});
-
- it('should navigate to Onboarding workspaces page when submitting form with EMPLOYER + Submit2026 beta and validated private domain', async () => {
- const testEmail = 'test@user.com';
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.ACCOUNT, {
- isFromPublicDomain: false,
- hasAccessibleDomainPolicies: true,
- });
- await Onyx.merge(ONYXKEYS.LOGIN_LIST, {
- [testEmail]: {
- partnerName: 'expensify.com',
- partnerUserID: testEmail,
- validatedDate: 'fake-validatedDate',
- },
- });
- await Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.EMPLOYER);
- await Onyx.set(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- });
-
- const {unmount} = renderOnboardingPersonalDetailsPage(SCREENS.ONBOARDING.PERSONAL_DETAILS, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- fireEvent.press(screen.getByText(TestHelper.translateLocal('common.continue')));
-
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(ROUTES.ONBOARDING_WORKSPACES.getRoute());
- });
-
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
-
- it('should create a Submit workspace when submitting form with EMPLOYER + Submit2026 beta and public domain', async () => {
- jest.spyOn(Navigation, 'dismissModal').mockImplementation(() => {});
- jest.spyOn(Navigation, 'setNavigationActionToMicrotaskQueue').mockImplementation((callback: () => void) => callback());
-
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.ACCOUNT, {
- isFromPublicDomain: true,
- hasAccessibleDomainPolicies: false,
- });
- await Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.EMPLOYER);
- await Onyx.set(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- });
-
- const {unmount} = renderOnboardingPersonalDetailsPage(SCREENS.ONBOARDING.PERSONAL_DETAILS, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- fireEvent.press(screen.getByText(TestHelper.translateLocal('common.continue')));
-
- await waitFor(() => {
- expect(mockCreateWorkspace).toHaveBeenCalledWith(
- expect.objectContaining({
- type: CONST.POLICY.TYPE.SUBMIT,
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- }),
- );
- });
-
- await waitFor(() => {
- expect(mockCompleteOnboarding).toHaveBeenCalledWith(
- expect.objectContaining({
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- }),
- );
- });
-
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_CATEGORIES.getRoute('test-policy-id'));
- });
-
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
});
diff --git a/tests/ui/WorkspaceOnboarding.tsx b/tests/ui/WorkspaceOnboarding.tsx
index d2307caf85cd..134697814764 100644
--- a/tests/ui/WorkspaceOnboarding.tsx
+++ b/tests/ui/WorkspaceOnboarding.tsx
@@ -13,54 +13,13 @@ import Navigation from '@libs/Navigation/Navigation';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types';
import OnboardingWorkspaces from '@pages/OnboardingWorkspaces';
-import {joinAccessiblePolicy} from '@userActions/Policy/Member';
-import {createWorkspace} from '@userActions/Policy/Policy';
-import {completeOnboarding} from '@userActions/Report';
import CONST from '@src/CONST';
-import IntlStore from '@src/languages/IntlStore';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import * as TestHelper from '../utils/TestHelper';
import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
-const mockCreateWorkspace = jest.mocked(createWorkspace);
-const mockCompleteOnboarding = jest.mocked(completeOnboarding);
-const mockJoinAccessiblePolicy = jest.mocked(joinAccessiblePolicy);
-
-jest.mock('@userActions/Report', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const actual = jest.requireActual('@userActions/Report');
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return {
- ...actual,
- completeOnboarding: jest.fn(),
- };
-});
-
-jest.mock('@userActions/Policy/Policy', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const actual = jest.requireActual('@userActions/Policy/Policy');
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return {
- ...actual,
- createWorkspace: jest.fn().mockReturnValue({
- policyID: 'test-policy-id',
- adminsChatReportID: 'test-admins-report-id',
- }),
- };
-});
-
-jest.mock('@userActions/Policy/Member', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const actual = jest.requireActual('@userActions/Policy/Member');
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return {
- ...actual,
- joinAccessiblePolicy: jest.fn(),
- };
-});
-
TestHelper.setupGlobalFetchMock();
const Stack = createPlatformStackNavigator();
@@ -90,7 +49,6 @@ describe('OnboardingWorkspaces Page', () => {
Onyx.init({
keys: ONYXKEYS,
});
- return IntlStore.load(CONST.LOCALES.EN);
});
beforeEach(() => {
@@ -215,118 +173,4 @@ describe('OnboardingWorkspaces Page', () => {
unmount();
await waitForBatchedUpdatesWithAct();
});
-
- it('should create a Submit workspace when skip is pressed with EMPLOYER purpose and Submit2026 beta', async () => {
- jest.spyOn(Navigation, 'dismissModal').mockImplementation(() => {});
- jest.spyOn(Navigation, 'setNavigationActionToMicrotaskQueue').mockImplementation((callback: () => void) => callback());
-
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {
- hasCompletedGuidedSetupFlow: false,
- });
- await Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.EMPLOYER);
- await Onyx.set(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- });
-
- const {unmount} = renderOnboardingWorkspacesPage(SCREENS.ONBOARDING.WORKSPACES, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- const skipButton = screen.getByTestId('onboardingWorkSpaceSkipButton');
-
- const mockEvent = {
- nativeEvent: {},
- type: 'press',
- target: skipButton,
- currentTarget: skipButton,
- };
-
- fireEvent.press(skipButton, mockEvent);
-
- await waitFor(() => {
- expect(mockCreateWorkspace).toHaveBeenCalledWith(
- expect.objectContaining({
- type: CONST.POLICY.TYPE.SUBMIT,
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- }),
- );
- });
-
- await waitFor(() => {
- expect(mockCompleteOnboarding).toHaveBeenCalledWith(
- expect.objectContaining({
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- }),
- );
- });
-
- await waitFor(() => {
- expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_CATEGORIES.getRoute('test-policy-id'));
- });
-
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
-
- it('should complete onboarding with the joined Submit workspace policyID and open Categories in the admins room', async () => {
- jest.spyOn(Navigation, 'dismissModal').mockImplementation(() => {});
- jest.spyOn(Navigation, 'setNavigationActionToMicrotaskQueue').mockImplementation((callback: () => void) => callback());
-
- await TestHelper.signInWithTestUser();
-
- await act(async () => {
- await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {
- hasCompletedGuidedSetupFlow: false,
- });
- await Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, CONST.ONBOARDING_CHOICES.EMPLOYER);
- await Onyx.set(ONYXKEYS.BETAS, [CONST.BETAS.SUBMIT_2026]);
- await Onyx.merge(ONYXKEYS.FORMS.ONBOARDING_PERSONAL_DETAILS_FORM, {
- firstName: 'Test',
- lastName: 'User',
- });
- await Onyx.set(ONYXKEYS.JOINABLE_POLICIES, {
- submitPolicyID: {
- policyID: 'submit-policy-id',
- policyName: 'Submit Workspace',
- policyOwner: 'owner@test.com',
- employeeCount: 4,
- hasPendingAccess: false,
- automaticJoiningEnabled: true,
- },
- });
- });
-
- const onyxSetSpy = jest.spyOn(Onyx, 'set');
- onyxSetSpy.mockClear();
-
- const {unmount} = renderOnboardingWorkspacesPage(SCREENS.ONBOARDING.WORKSPACES, {backTo: ''});
-
- await waitForBatchedUpdatesWithAct();
-
- fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.workspaceList.joinNow')));
-
- await waitFor(() => {
- expect(mockJoinAccessiblePolicy).toHaveBeenCalledWith('submit-policy-id');
- });
-
- await waitFor(() => {
- expect(mockCompleteOnboarding).toHaveBeenCalledWith(
- expect.objectContaining({
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- onboardingPolicyID: 'submit-policy-id',
- }),
- );
- });
-
- await waitFor(() => {
- expect(onyxSetSpy).toHaveBeenCalledWith(ONYXKEYS.NVP_ONBOARDING_RHP_VARIANT, CONST.ONBOARDING_RHP_VARIANT.RHP_ADMINS_ROOM);
- expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_CATEGORIES.getRoute('submit-policy-id'));
- });
-
- onyxSetSpy.mockRestore();
- unmount();
- await waitForBatchedUpdatesWithAct();
- });
});
diff --git a/tests/unit/hooks/useAutoCreateSubmitWorkspace.test.ts b/tests/unit/hooks/useAutoCreateSubmitWorkspace.test.ts
deleted file mode 100644
index a01b7002d801..000000000000
--- a/tests/unit/hooks/useAutoCreateSubmitWorkspace.test.ts
+++ /dev/null
@@ -1,330 +0,0 @@
-import {renderHook} from '@testing-library/react-native';
-import useAutoCreateSubmitWorkspace from '@hooks/useAutoCreateSubmitWorkspace';
-import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
-import useHasActiveAdminPolicies from '@hooks/useHasActiveAdminPolicies';
-import useLocalize from '@hooks/useLocalize';
-import useOnboardingMessages from '@hooks/useOnboardingMessages';
-import useOnyx from '@hooks/useOnyx';
-import usePreferredPolicy from '@hooks/usePreferredPolicy';
-import * as navigateAfterOnboarding from '@libs/navigateAfterOnboarding';
-import * as Policy from '@userActions/Policy/Policy';
-import * as Report from '@userActions/Report';
-import * as Welcome from '@userActions/Welcome';
-import CONST from '@src/CONST';
-
-jest.mock('@hooks/useOnyx', () => {
- return {__esModule: true, default: jest.fn(() => [undefined])};
-});
-
-jest.mock('@hooks/useCurrentUserPersonalDetails');
-jest.mock('@hooks/useHasActiveAdminPolicies');
-jest.mock('@hooks/useLocalize');
-jest.mock('@hooks/usePreferredPolicy');
-jest.mock('@hooks/useOnboardingMessages');
-
-const mockTranslate = jest.fn((key: string) => key);
-const mockFormatPhoneNumber = jest.fn((phone: string) => phone);
-
-const MOCK_SESSION = {
- accountID: 12345,
- email: 'test@expensify.com',
-};
-
-const MOCK_POLICY_ID = 'mock-policy-id';
-const MOCK_ADMINS_CHAT_REPORT_ID = 'mock-admins-chat-report-id';
-const MOCK_ONBOARDING_MESSAGE = {message: 'Welcome!', video: undefined, tasks: []};
-
-function setupDefaultMocks() {
- const mockUseOnyx = useOnyx as jest.Mock;
- mockUseOnyx.mockImplementation((key: string) => {
- if (key === 'session') {
- return [MOCK_SESSION];
- }
- if (key === 'betas') {
- return [[]];
- }
- if (key.startsWith('policy_')) {
- return [false];
- }
- return [undefined];
- });
-
- (useCurrentUserPersonalDetails as jest.Mock).mockReturnValue({
- accountID: MOCK_SESSION.accountID,
- login: MOCK_SESSION.email,
- localCurrencyCode: 'USD',
- });
-
- (useLocalize as jest.Mock).mockReturnValue({
- translate: mockTranslate,
- formatPhoneNumber: mockFormatPhoneNumber,
- });
-
- (usePreferredPolicy as jest.Mock).mockReturnValue({
- isRestrictedToPreferredPolicy: false,
- preferredPolicyID: undefined,
- isRestrictedPolicyCreation: false,
- });
-
- (useHasActiveAdminPolicies as jest.Mock).mockReturnValue(false);
-
- (useOnboardingMessages as jest.Mock).mockReturnValue({
- onboardingMessages: {
- [CONST.ONBOARDING_CHOICES.EMPLOYER]: MOCK_ONBOARDING_MESSAGE,
- },
- });
-}
-
-describe('useAutoCreateSubmitWorkspace', () => {
- const createWorkspaceSpy = jest.spyOn(Policy, 'createWorkspace').mockReturnValue({
- policyID: MOCK_POLICY_ID,
- adminsChatReportID: MOCK_ADMINS_CHAT_REPORT_ID,
- } as ReturnType);
- const completeOnboardingSpy = jest.spyOn(Report, 'completeOnboarding').mockImplementation(jest.fn());
- const setOnboardingAdminsChatReportIDSpy = jest.spyOn(Welcome, 'setOnboardingAdminsChatReportID').mockImplementation(jest.fn());
- const setOnboardingPolicyIDSpy = jest.spyOn(Welcome, 'setOnboardingPolicyID').mockImplementation(jest.fn());
- const navigateSpy = jest.spyOn(navigateAfterOnboarding, 'navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue').mockImplementation(jest.fn());
-
- beforeEach(() => {
- jest.clearAllMocks();
- setupDefaultMocks();
- });
-
- it('creates a Submit workspace with the correct parameters for a new EMPLOYER user', () => {
- // Given a new user going through onboarding with no existing workspace (onboardingPolicyID is undefined,
- // hasEditableGroupPolicy is false, and policy creation is not restricted)
-
- // When the autoCreateSubmitWorkspace function is invoked during onboarding
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('John', 'Doe');
-
- // Then a Submit workspace should be created because EMPLOYER users without an existing
- // workspace need one auto-created to land on after onboarding
- expect(createWorkspaceSpy).toHaveBeenCalledTimes(1);
- expect(createWorkspaceSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- makeMeAdmin: true,
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- currency: 'USD',
- shouldAddOnboardingTasks: false,
- shouldAddGuideWelcomeMessage: false,
- type: CONST.POLICY.TYPE.SUBMIT,
- currentUserAccountIDParam: MOCK_SESSION.accountID,
- currentUserEmailParam: MOCK_SESSION.email,
- }),
- );
- });
-
- it('completes onboarding with the newly created workspace and admins chat IDs', () => {
- // Given a new user with no pre-existing onboarding workspace
-
- // When the hook creates a workspace and finishes the onboarding flow
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('John', 'Doe');
-
- // Then completeOnboarding should receive the IDs returned by createWorkspace so the backend
- // can associate the guided setup data with the correct workspace and admins chat
- expect(completeOnboardingSpy).toHaveBeenCalledTimes(1);
- expect(completeOnboardingSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
- onboardingMessage: MOCK_ONBOARDING_MESSAGE,
- firstName: 'John',
- lastName: 'Doe',
- adminsChatReportID: MOCK_ADMINS_CHAT_REPORT_ID,
- onboardingPolicyID: MOCK_POLICY_ID,
- }),
- );
- });
-
- it('clears onboarding state after the flow completes', () => {
- // Given a user completing the onboarding flow
-
- // When autoCreateSubmitWorkspace finishes
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('John', 'Doe');
-
- // Then the transient onboarding Onyx keys should be cleared so the onboarding
- // flow is not re-triggered on subsequent app launches
- expect(setOnboardingAdminsChatReportIDSpy).toHaveBeenCalledTimes(1);
- expect(setOnboardingPolicyIDSpy).toHaveBeenCalledTimes(1);
- });
-
- it('navigates to the submit workspace page after completing onboarding', () => {
- // Given a user completing the EMPLOYER onboarding flow
-
- // When autoCreateSubmitWorkspace finishes setting up the workspace
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('John', 'Doe');
-
- // Then the user should be navigated to the newly created Submit workspace
- // so they land on their workspace immediately after onboarding
- expect(navigateSpy).toHaveBeenCalledTimes(1);
- expect(navigateSpy).toHaveBeenCalledWith(MOCK_POLICY_ID, expect.any(Boolean));
- });
-
- it('reuses the existing onboarding workspace instead of creating a new one', () => {
- // Given a user who already has an onboardingPolicyID set (e.g. assigned by an admin
- // or from a previous partial onboarding attempt)
- const existingPolicyID = 'existing-policy-id';
- const existingAdminsReportID = 'existing-admins-report-id';
-
- (useOnyx as jest.Mock).mockImplementation((key: string) => {
- if (key === 'onboardingPolicyID') {
- return [existingPolicyID];
- }
- if (key === 'onboardingAdminsChatReportID') {
- return [existingAdminsReportID];
- }
- if (key === 'session') {
- return [MOCK_SESSION];
- }
- if (key === 'betas') {
- return [[]];
- }
- if (key.startsWith('policy_')) {
- return [false];
- }
- return [undefined];
- });
-
- // When the onboarding flow runs
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('Jane', 'Smith');
-
- // Then no new workspace should be created, and completeOnboarding should use
- // the pre-existing IDs to avoid creating duplicate workspaces
- expect(createWorkspaceSpy).not.toHaveBeenCalled();
- expect(completeOnboardingSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- adminsChatReportID: existingAdminsReportID,
- onboardingPolicyID: existingPolicyID,
- }),
- );
- });
-
- it('skips workspace creation when the user is already a paid group policy admin', () => {
- // Given a user who is already an admin of a paid group policy
- (useOnyx as jest.Mock).mockImplementation((key: string) => {
- if (key === 'session') {
- return [MOCK_SESSION];
- }
- if (key === 'betas') {
- return [[]];
- }
- if (key === 'onboardingPolicyID') {
- return [undefined];
- }
- if (key === 'onboardingAdminsChatReportID') {
- return [undefined];
- }
- if (key.startsWith('policy_')) {
- return [true];
- }
- return [undefined];
- });
-
- // When onboarding completes
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('Jane', 'Smith');
-
- // Then no Submit workspace should be created because the user already has
- // a paid group workspace and creating another would be redundant
- expect(createWorkspaceSpy).not.toHaveBeenCalled();
- });
-
- it('skips workspace creation when the user domain restricts policy creation', () => {
- // Given a user whose domain security group has enableRestrictedPolicyCreation set to true
- (usePreferredPolicy as jest.Mock).mockReturnValue({
- isRestrictedToPreferredPolicy: false,
- preferredPolicyID: undefined,
- isRestrictedPolicyCreation: true,
- });
-
- // When the onboarding flow runs
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('Jane', 'Smith');
-
- // Then workspace creation should be skipped because the domain admin has
- // restricted users from creating their own policies
- expect(createWorkspaceSpy).not.toHaveBeenCalled();
- expect(completeOnboardingSpy).toHaveBeenCalledTimes(1);
- });
-
- it('still completes onboarding and navigates even when workspace creation is skipped', () => {
- // Given a user who cannot create a workspace due to domain restrictions
- (usePreferredPolicy as jest.Mock).mockReturnValue({
- isRestrictedToPreferredPolicy: false,
- preferredPolicyID: undefined,
- isRestrictedPolicyCreation: true,
- });
-
- // When the onboarding flow runs
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('Jane', 'Smith');
-
- // Then onboarding should still be completed and navigation should still occur
- // because the user needs to finish onboarding regardless of workspace creation
- expect(completeOnboardingSpy).toHaveBeenCalledTimes(1);
- expect(setOnboardingAdminsChatReportIDSpy).toHaveBeenCalledTimes(1);
- expect(setOnboardingPolicyIDSpy).toHaveBeenCalledTimes(1);
- expect(navigateSpy).toHaveBeenCalledTimes(1);
- });
-
- it('uses the localCurrencyCode from personal details for workspace currency', () => {
- // Given a user whose personal details have localCurrencyCode set to GBP
- (useCurrentUserPersonalDetails as jest.Mock).mockReturnValue({
- accountID: MOCK_SESSION.accountID,
- login: MOCK_SESSION.email,
- localCurrencyCode: 'GBP',
- });
-
- // When a workspace is created during onboarding
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('John', 'Doe');
-
- // Then the workspace should use GBP as its currency so it matches the user's locale
- expect(createWorkspaceSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- currency: 'GBP',
- }),
- );
- });
-
- it('falls back to USD when localCurrencyCode is not available', () => {
- // Given a user whose personal details do not have a localCurrencyCode set
- (useCurrentUserPersonalDetails as jest.Mock).mockReturnValue({
- accountID: MOCK_SESSION.accountID,
- login: MOCK_SESSION.email,
- localCurrencyCode: undefined,
- });
-
- // When a workspace is created during onboarding
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('John', 'Doe');
-
- // Then the workspace should default to USD as a safe fallback currency
- expect(createWorkspaceSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- currency: CONST.CURRENCY.USD,
- }),
- );
- });
-
- it('forwards firstName and lastName to completeOnboarding for display name setup', () => {
- // Given a user providing their name during the onboarding personal details step
-
- // When the onboarding flow completes
- const {result} = renderHook(() => useAutoCreateSubmitWorkspace());
- result.current('Alice', 'Wonderland');
-
- // Then the provided name should be passed through to completeOnboarding so the
- // backend can set up the user's display name as part of the guided setup
- expect(completeOnboardingSpy).toHaveBeenCalledWith(
- expect.objectContaining({
- firstName: 'Alice',
- lastName: 'Wonderland',
- }),
- );
- });
-});