Skip to content

Commit 045ff9b

Browse files
authored
Merge pull request Expensify#89994 from abzokhattab/revert-89808
feat: Submit workspace creation + onboarding flow ||
2 parents 81390e7 + ce61065 commit 045ff9b

47 files changed

Lines changed: 1380 additions & 103 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/CONST/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,7 @@ const CONST = {
891891
GUSTO: 'gustoNewDot',
892892
BULK_EDIT: 'bulkEdit',
893893
NEW_MANUAL_EXPENSE_FLOW: 'newManualExpenseFlow',
894+
SUBMIT_2026: 'submit2026',
894895
BULK_SUBMIT_APPROVE_PAY: 'bulkSubmitApprovePay',
895896
},
896897
BUTTON_STATES: {
@@ -3535,6 +3536,8 @@ const CONST = {
35353536

35363537
// Often referred to as "collect" workspaces
35373538
TEAM: 'team',
3539+
3540+
SUBMIT: 'submit2026',
35383541
},
35393542
RULE_CONDITIONS: {
35403543
MATCHES: 'matches',
@@ -3553,6 +3556,7 @@ const CONST = {
35533556
ADMIN: 'admin',
35543557
AUDITOR: 'auditor',
35553558
USER: 'user',
3559+
EDITOR: 'editor',
35563560
},
35573561
AUTO_REIMBURSEMENT_MAX_LIMIT_CENTS: 2000000,
35583562

src/components/SidePanel/SidePanelContextProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
1010
import SidePanelActions from '@libs/actions/SidePanel';
1111
import DateUtils from '@libs/DateUtils';
1212
import focusComposerWithDelay from '@libs/focusComposerWithDelay';
13-
import {isPolicyAdmin, shouldShowPolicy} from '@libs/PolicyUtils';
13+
import {canEditWorkspaceSettings, shouldShowPolicy} from '@libs/PolicyUtils';
1414
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
1515
import variables from '@styles/variables';
1616
import CONST from '@src/CONST';
@@ -82,7 +82,7 @@ function SidePanelContextProvider({children}: PropsWithChildren) {
8282

8383
const isRHPAdminsRoom = onboardingRHPVariant === CONST.ONBOARDING_RHP_VARIANT.RHP_ADMINS_ROOM;
8484
const isRHPHomePage = onboardingRHPVariant === CONST.ONBOARDING_RHP_VARIANT.RHP_HOME_PAGE;
85-
const isUserAdmin = isPolicyAdmin(activePolicy, sessionEmail);
85+
const isUserAdmin = canEditWorkspaceSettings(activePolicy);
8686
const isPolicyActive = shouldShowPolicy(activePolicy, false, sessionEmail ?? '');
8787
const adminsChatReportID = activePolicy?.chatReportIDAdmins?.toString();
8888

src/components/WorkspaceMemberRoleList.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import {emailSelector} from '@selectors/Session';
12
import React from 'react';
23
import {View} from 'react-native';
34
import type {OnyxEntry} from 'react-native-onyx';
45
import type {ValueOf} from 'type-fest';
56
import useLocalize from '@hooks/useLocalize';
7+
import useOnyx from '@hooks/useOnyx';
68
import useThemeStyles from '@hooks/useThemeStyles';
79
import Navigation from '@libs/Navigation/Navigation';
8-
import {isControlPolicy} from '@libs/PolicyUtils';
10+
import {isControlPolicy, isPolicyAdmin} from '@libs/PolicyUtils';
911
import CONST from '@src/CONST';
12+
import ONYXKEYS from '@src/ONYXKEYS';
1013
import type {Route} from '@src/ROUTES';
1114
import type {Policy} from '@src/types/onyx';
1215
import HeaderWithBackButton from './HeaderWithBackButton';
@@ -32,6 +35,7 @@ type WorkspaceMemberRoleListProps = {
3235
function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLoading = false, onSelectRole = () => {}}: WorkspaceMemberRoleListProps) {
3336
const {translate} = useLocalize();
3437
const styles = useThemeStyles();
38+
const [currentUserEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector});
3539

3640
const workspaceRoles: ListItemType[] = [
3741
{
@@ -58,7 +62,18 @@ function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLo
5862
];
5963

6064
const isPolicyControl = isControlPolicy(policy);
61-
const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => isPolicyControl || item.value !== CONST.POLICY.ROLE.AUDITOR);
65+
// Only strict admins can assign the ADMIN role. Editors (e.g. Submit workspace owners) can
66+
// invite/manage members but must not be able to escalate anyone to admin.
67+
const canAssignAdminRole = isPolicyAdmin(policy, currentUserEmail);
68+
const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => {
69+
if (item.value === CONST.POLICY.ROLE.AUDITOR && !isPolicyControl) {
70+
return false;
71+
}
72+
if (item.value === CONST.POLICY.ROLE.ADMIN && !canAssignAdminRole) {
73+
return false;
74+
}
75+
return true;
76+
});
6277

6378
return (
6479
<>
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {useCallback, useMemo} from 'react';
2+
import type {OnyxCollection} from 'react-native-onyx';
3+
import {navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue} from '@libs/navigateAfterOnboarding';
4+
import {createDisplayName} from '@libs/PersonalDetailsUtils';
5+
import {canEditWorkspaceSettings, isGroupPolicy} from '@libs/PolicyUtils';
6+
import {createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from '@userActions/Policy/Policy';
7+
import {completeOnboarding} from '@userActions/Report';
8+
import {setOnboardingAdminsChatReportID, setOnboardingPolicyID} from '@userActions/Welcome';
9+
import CONST from '@src/CONST';
10+
import ONYXKEYS from '@src/ONYXKEYS';
11+
import type {Policy} from '@src/types/onyx';
12+
import useOnboardingWorkspaceCreationState from './useOnboardingWorkspaceCreationState';
13+
import useOnyx from './useOnyx';
14+
15+
/**
16+
* Hook that provides a function to auto-create a Submit workspace for EMPLOYER
17+
* users during onboarding and complete the onboarding flow.
18+
*
19+
* Shared by BaseOnboardingPersonalDetails, BaseOnboardingPurpose, and BaseOnboardingWorkspaces.
20+
*/
21+
function useAutoCreateSubmitWorkspace() {
22+
const {
23+
onboardingPolicyID,
24+
onboardingAdminsChatReportID,
25+
introSelected,
26+
isSelfTourViewed,
27+
betas,
28+
currentUserEmail,
29+
currentUserAccountID,
30+
localCurrencyCode,
31+
activePolicy,
32+
translate,
33+
formatPhoneNumber,
34+
isRestrictedPolicyCreation,
35+
hasActiveAdminPolicies,
36+
onboardingMessages,
37+
lastWorkspaceNumber,
38+
shouldUseNarrowLayout,
39+
} = useOnboardingWorkspaceCreationState();
40+
41+
const groupPolicySelector = useMemo(
42+
() => (policies: OnyxCollection<Policy>) => Object.values(policies ?? {}).some((policy) => isGroupPolicy(policy) && canEditWorkspaceSettings(policy)),
43+
[],
44+
);
45+
const [hasEditableGroupPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: groupPolicySelector});
46+
47+
const autoCreateSubmitWorkspace = useCallback(
48+
(firstName: string, lastName: string) => {
49+
const shouldCreateWorkspace = !isRestrictedPolicyCreation && !onboardingPolicyID && !hasEditableGroupPolicy;
50+
const displayName = createDisplayName(currentUserEmail, {firstName, lastName}, formatPhoneNumber);
51+
52+
const {adminsChatReportID: newAdminsChatReportID, policyID: newPolicyID} = shouldCreateWorkspace
53+
? createWorkspace({
54+
policyOwnerEmail: undefined,
55+
makeMeAdmin: true,
56+
policyName: generateDefaultWorkspaceName(currentUserEmail, lastWorkspaceNumber, translate, displayName),
57+
policyID: generatePolicyID(),
58+
engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
59+
currency: localCurrencyCode,
60+
file: undefined,
61+
shouldAddOnboardingTasks: false,
62+
introSelected,
63+
activePolicy,
64+
currentUserAccountIDParam: currentUserAccountID,
65+
currentUserEmailParam: currentUserEmail,
66+
shouldAddGuideWelcomeMessage: false,
67+
type: CONST.POLICY.TYPE.SUBMIT,
68+
betas,
69+
isSelfTourViewed,
70+
hasActiveAdminPolicies,
71+
})
72+
: {adminsChatReportID: onboardingAdminsChatReportID, policyID: onboardingPolicyID};
73+
74+
completeOnboarding({
75+
engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER,
76+
onboardingMessage: onboardingMessages[CONST.ONBOARDING_CHOICES.EMPLOYER],
77+
firstName,
78+
lastName,
79+
adminsChatReportID: newAdminsChatReportID,
80+
onboardingPolicyID: newPolicyID,
81+
introSelected,
82+
isSelfTourViewed,
83+
betas,
84+
});
85+
86+
setOnboardingAdminsChatReportID();
87+
setOnboardingPolicyID();
88+
89+
navigateToSubmitWorkspaceAfterOnboardingWithMicrotaskQueue(newPolicyID, shouldUseNarrowLayout);
90+
},
91+
[
92+
currentUserEmail,
93+
currentUserAccountID,
94+
lastWorkspaceNumber,
95+
translate,
96+
formatPhoneNumber,
97+
isRestrictedPolicyCreation,
98+
onboardingPolicyID,
99+
hasEditableGroupPolicy,
100+
onboardingAdminsChatReportID,
101+
localCurrencyCode,
102+
introSelected,
103+
activePolicy,
104+
isSelfTourViewed,
105+
onboardingMessages,
106+
betas,
107+
hasActiveAdminPolicies,
108+
shouldUseNarrowLayout,
109+
],
110+
);
111+
112+
return autoCreateSubmitWorkspace;
113+
}
114+
115+
export default useAutoCreateSubmitWorkspace;

src/hooks/useAutoCreateTrackWorkspace.ts

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {hasSeenTourSelector} from '@selectors/Onboarding';
21
import {useCallback, useMemo} from 'react';
32
import type {OnyxCollection} from 'react-native-onyx';
43
import isSidePanelReportSupported from '@components/SidePanel/isSidePanelReportSupported';
@@ -12,17 +11,10 @@ import {setOnboardingAdminsChatReportID, setOnboardingPolicyID} from '@userActio
1211
import CONST from '@src/CONST';
1312
import ONYXKEYS from '@src/ONYXKEYS';
1413
import type {OnboardingPurpose, OnboardingRHPVariant, Policy} from '@src/types/onyx';
15-
import useActivePolicy from './useActivePolicy';
1614
import useArchivedReportsIdSet from './useArchivedReportsIdSet';
17-
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
18-
import useHasActiveAdminPolicies from './useHasActiveAdminPolicies';
19-
import useLastWorkspaceNumber from './useLastWorkspaceNumber';
20-
import useLocalize from './useLocalize';
21-
import useOnboardingMessages from './useOnboardingMessages';
15+
import useOnboardingWorkspaceCreationState from './useOnboardingWorkspaceCreationState';
2216
import useOnyx from './useOnyx';
2317
import usePermissions from './usePermissions';
24-
import usePreferredPolicy from './usePreferredPolicy';
25-
import useResponsiveLayout from './useResponsiveLayout';
2618

2719
/**
2820
* Hook that provides a function to auto-create a workspace for Track (PERSONAL_SPEND)
@@ -31,42 +23,42 @@ import useResponsiveLayout from './useResponsiveLayout';
3123
* Shared by BaseOnboardingPersonalDetails and BaseOnboardingPurpose.
3224
*/
3325
function useAutoCreateTrackWorkspace() {
34-
const [onboardingPolicyID] = useOnyx(ONYXKEYS.ONBOARDING_POLICY_ID);
35-
const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID);
36-
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
37-
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
38-
const [betas] = useOnyx(ONYXKEYS.BETAS);
39-
const [session] = useOnyx(ONYXKEYS.SESSION);
26+
const {
27+
onboardingPolicyID,
28+
onboardingAdminsChatReportID,
29+
introSelected,
30+
isSelfTourViewed,
31+
betas,
32+
currentUserEmail,
33+
currentUserAccountID,
34+
localCurrencyCode,
35+
activePolicy,
36+
translate,
37+
formatPhoneNumber,
38+
isRestrictedPolicyCreation,
39+
hasActiveAdminPolicies,
40+
onboardingMessages,
41+
lastWorkspaceNumber,
42+
shouldUseNarrowLayout,
43+
} = useOnboardingWorkspaceCreationState();
44+
4045
const paidGroupPolicySelector = useMemo(
41-
() => (policies: OnyxCollection<Policy>) => Object.values(policies ?? {}).some((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, session?.email)),
42-
[session?.email],
46+
() => (policies: OnyxCollection<Policy>) => Object.values(policies ?? {}).some((policy) => isPaidGroupPolicy(policy) && isPolicyAdmin(policy, currentUserEmail)),
47+
[currentUserEmail],
4348
);
4449
const [hasPaidGroupAdminPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: paidGroupPolicySelector});
50+
4551
const [conciergeChatReportID = ''] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
4652
const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
47-
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
4853
const archivedReportsIdSet = useArchivedReportsIdSet();
4954
const {isBetaEnabled} = usePermissions();
50-
const {translate, formatPhoneNumber} = useLocalize();
51-
const activePolicy = useActivePolicy();
52-
const {isRestrictedPolicyCreation} = usePreferredPolicy();
53-
const hasActiveAdminPolicies = useHasActiveAdminPolicies();
54-
const lastWorkspaceNumber = useLastWorkspaceNumber();
55-
const {onboardingMessages} = useOnboardingMessages();
56-
57-
// We use isSmallScreenWidth instead of shouldUseNarrowLayout because navigateAfterOnboarding
58-
// relies on actual device screen width to handle navigation stack differences: on small screens,
59-
// removing OnboardingModalNavigator redirects to HOME, requiring explicit navigation to the last
60-
// accessed report. This behavior is tied to screen size, not responsive layout mode.
61-
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
62-
const {isSmallScreenWidth} = useResponsiveLayout();
6355

6456
const mergedAccountConciergeReportID = !onboardingValues?.shouldRedirectToClassicAfterMerge && onboardingValues?.shouldValidate ? conciergeChatReportID : undefined;
6557

6658
const autoCreateTrackWorkspace = useCallback(
6759
async (firstName: string, lastName: string, onboardingPurposeSelected: OnboardingPurpose) => {
6860
const shouldCreateWorkspace = !isRestrictedPolicyCreation && !onboardingPolicyID && !hasPaidGroupAdminPolicy;
69-
const displayName = createDisplayName(session?.email ?? '', {firstName, lastName}, formatPhoneNumber);
61+
const displayName = createDisplayName(currentUserEmail, {firstName, lastName}, formatPhoneNumber);
7062

7163
const engagementChoice =
7264
onboardingPurposeSelected === CONST.ONBOARDING_CHOICES.TRACK_PERSONAL ? CONST.ONBOARDING_CHOICES.TRACK_PERSONAL : CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE;
@@ -75,16 +67,16 @@ function useAutoCreateTrackWorkspace() {
7567
? createWorkspace({
7668
policyOwnerEmail: undefined,
7769
makeMeAdmin: true,
78-
policyName: generateDefaultWorkspaceName(session?.email ?? '', lastWorkspaceNumber, translate, displayName),
70+
policyName: generateDefaultWorkspaceName(currentUserEmail, lastWorkspaceNumber, translate, displayName),
7971
policyID: generatePolicyID(),
8072
engagementChoice,
81-
currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
73+
currency: localCurrencyCode,
8274
file: undefined,
8375
shouldAddOnboardingTasks: false,
8476
introSelected,
8577
activePolicy,
86-
currentUserAccountIDParam: session?.accountID ?? CONST.DEFAULT_NUMBER_ID,
87-
currentUserEmailParam: session?.email ?? '',
78+
currentUserAccountIDParam: currentUserAccountID,
79+
currentUserEmailParam: currentUserEmail,
8880
shouldAddGuideWelcomeMessage: false,
8981
onboardingPurposeSelected,
9082
betas,
@@ -126,7 +118,7 @@ function useAutoCreateTrackWorkspace() {
126118
setOnboardingPolicyID();
127119

128120
navigateAfterOnboardingWithMicrotaskQueue(
129-
isSmallScreenWidth,
121+
shouldUseNarrowLayout,
130122
isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS),
131123
conciergeChatReportID,
132124
archivedReportsIdSet,
@@ -138,23 +130,23 @@ function useAutoCreateTrackWorkspace() {
138130
}
139131
},
140132
[
141-
session?.email,
142-
session?.accountID,
133+
currentUserEmail,
134+
currentUserAccountID,
143135
lastWorkspaceNumber,
144136
translate,
145137
formatPhoneNumber,
146138
isRestrictedPolicyCreation,
147139
onboardingPolicyID,
148140
hasPaidGroupAdminPolicy,
149141
onboardingAdminsChatReportID,
150-
currentUserPersonalDetails.localCurrencyCode,
142+
localCurrencyCode,
151143
introSelected,
152144
activePolicy,
153145
isSelfTourViewed,
154146
onboardingMessages,
155147
betas,
156148
hasActiveAdminPolicies,
157-
isSmallScreenWidth,
149+
shouldUseNarrowLayout,
158150
isBetaEnabled,
159151
conciergeChatReportID,
160152
archivedReportsIdSet,

0 commit comments

Comments
 (0)