Skip to content

Commit 4233b3f

Browse files
authored
Merge pull request Expensify#76419 from mkzie2/mkzie2-issue/76370
fix: migrate role selection modal to new page
2 parents 5235031 + fb6756d commit 4233b3f

11 files changed

Lines changed: 178 additions & 133 deletions

File tree

src/ROUTES.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,6 +2044,10 @@ const ROUTES = {
20442044
route: 'workspaces/:policyID/members/:accountID',
20452045
getRoute: (policyID: string, accountID: number) => `workspaces/${policyID}/members/${accountID}` as const,
20462046
},
2047+
WORKSPACE_MEMBER_DETAILS_ROLE: {
2048+
route: 'workspaces/:policyID/members/:accountID/role',
2049+
getRoute: (policyID: string, accountID: number) => `workspaces/${policyID}/members/${accountID}/role` as const,
2050+
},
20472051
WORKSPACE_CUSTOM_FIELDS: {
20482052
route: 'workspaces/:policyID/members/:accountID/:customFieldType',
20492053
getRoute: (policyID: string, accountID: number, customFieldType: CustomFieldType) => `/workspaces/${policyID}/members/${accountID}/${customFieldType}` as const,

src/SCREENS.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ const SCREENS = {
674674
CATEGORIES_IMPORTED: 'Categories_Imported',
675675
MORE_FEATURES: 'Workspace_More_Features',
676676
MEMBER_DETAILS: 'Workspace_Member_Details',
677+
MEMBER_DETAILS_ROLE: 'Workspace_Member_Details_Role',
677678
MEMBER_CUSTOM_FIELD: 'Workspace_Member_Custom_Field',
678679
MEMBER_NEW_CARD: 'Workspace_Member_NewCard',
679680
OWNER_CHANGE_CHECK: 'Workspace_Owner_Change_Check',
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import type {OnyxEntry} from 'react-native-onyx';
4+
import type {ValueOf} from 'type-fest';
5+
import useLocalize from '@hooks/useLocalize';
6+
import useThemeStyles from '@hooks/useThemeStyles';
7+
import Navigation from '@libs/Navigation/Navigation';
8+
import {isControlPolicy} from '@libs/PolicyUtils';
9+
import CONST from '@src/CONST';
10+
import type {Route} from '@src/ROUTES';
11+
import type {Policy} from '@src/types/onyx';
12+
import HeaderWithBackButton from './HeaderWithBackButton';
13+
import SelectionList from './SelectionList';
14+
import RadioListItem from './SelectionList/ListItem/RadioListItem';
15+
import type {ListItem} from './SelectionList/types';
16+
17+
type ListItemType = ListItem<ValueOf<typeof CONST.POLICY.ROLE>> & {
18+
value: ValueOf<typeof CONST.POLICY.ROLE>;
19+
text: string;
20+
alternateText: string;
21+
isSelected: boolean;
22+
};
23+
24+
type WorkspaceMemberRoleListProps = {
25+
role: string | undefined;
26+
policy: OnyxEntry<Policy>;
27+
navigateBackTo?: Route;
28+
isLoading?: boolean;
29+
onSelectRole?: (value: ListItemType) => void;
30+
};
31+
32+
function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLoading = false, onSelectRole = () => {}}: WorkspaceMemberRoleListProps) {
33+
const {translate} = useLocalize();
34+
const styles = useThemeStyles();
35+
36+
const workspaceRoles: ListItemType[] = [
37+
{
38+
value: CONST.POLICY.ROLE.ADMIN,
39+
text: translate('common.admin'),
40+
alternateText: translate('workspace.common.adminAlternateText'),
41+
isSelected: role === CONST.POLICY.ROLE.ADMIN,
42+
keyForList: CONST.POLICY.ROLE.ADMIN,
43+
},
44+
{
45+
value: CONST.POLICY.ROLE.AUDITOR,
46+
text: translate('common.auditor'),
47+
alternateText: translate('workspace.common.auditorAlternateText'),
48+
isSelected: role === CONST.POLICY.ROLE.AUDITOR,
49+
keyForList: CONST.POLICY.ROLE.AUDITOR,
50+
},
51+
{
52+
value: CONST.POLICY.ROLE.USER,
53+
text: translate('common.member'),
54+
alternateText: translate('workspace.common.memberAlternateText'),
55+
isSelected: role === CONST.POLICY.ROLE.USER,
56+
keyForList: CONST.POLICY.ROLE.USER,
57+
},
58+
];
59+
60+
const isPolicyControl = isControlPolicy(policy);
61+
const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => isPolicyControl || item.value !== CONST.POLICY.ROLE.AUDITOR);
62+
63+
return (
64+
<>
65+
<HeaderWithBackButton
66+
title={translate('common.role')}
67+
onBackButtonPress={() => Navigation.goBack(navigateBackTo)}
68+
/>
69+
{!isLoading && (
70+
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone]}>
71+
<SelectionList
72+
data={availableRoleItems}
73+
ListItem={RadioListItem}
74+
onSelectRow={onSelectRole}
75+
shouldSingleExecuteRowSelect
76+
initiallyFocusedItemKey={availableRoleItems.find((item) => item.isSelected)?.keyForList}
77+
addBottomSafeAreaPadding
78+
/>
79+
</View>
80+
)}
81+
</>
82+
);
83+
}
84+
85+
WorkspaceMemberRoleList.displayName = 'WorkspaceMemberRoleList';
86+
export default WorkspaceMemberRoleList;
87+
export type {ListItemType};

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ const SettingsModalStackNavigator = createModalStackNavigator<SettingsNavigatorP
565565
[SCREENS.WORKSPACE.DOWNGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/downgrade/WorkspaceDowngradePage').default,
566566
[SCREENS.WORKSPACE.PAY_AND_DOWNGRADE]: () => require<ReactComponentModule>('../../../../pages/workspace/downgrade/PayAndDowngradePage').default,
567567
[SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberDetailsPage').default,
568+
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberDetailsRolePage').default,
568569
[SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberCustomFieldPage').default,
569570
[SCREENS.WORKSPACE.MEMBER_NEW_CARD]: () => require<ReactComponentModule>('../../../../pages/workspace/members/WorkspaceMemberNewCardPage').default,
570571
[SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: () => require<ReactComponentModule>('@pages/workspace/members/WorkspaceOwnerChangeWrapperPage').default,

src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const WORKSPACE_TO_RHP: Partial<Record<keyof WorkspaceSplitNavigatorParamList, s
1515
],
1616
[SCREENS.WORKSPACE.MEMBERS]: [
1717
SCREENS.WORKSPACE.MEMBER_DETAILS,
18+
SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE,
1819
SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD,
1920
SCREENS.WORKSPACE.MEMBER_NEW_CARD,
2021
SCREENS.WORKSPACE.OWNER_CHANGE_CHECK,

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,9 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
845845
[SCREENS.WORKSPACE.MEMBER_DETAILS]: {
846846
path: ROUTES.WORKSPACE_MEMBER_DETAILS.route,
847847
},
848+
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE]: {
849+
path: ROUTES.WORKSPACE_MEMBER_DETAILS_ROLE.route,
850+
},
848851
[SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD]: {
849852
path: ROUTES.WORKSPACE_CUSTOM_FIELDS.route,
850853
},

src/libs/Navigation/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,10 @@ type SettingsNavigatorParamList = {
583583
policyID: string;
584584
accountID: string;
585585
};
586+
[SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE]: {
587+
policyID: string;
588+
accountID: string;
589+
};
586590
[SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD]: {
587591
policyID: string;
588592
accountID: string;

src/pages/workspace/WorkspaceInviteMessageRolePage.tsx

Lines changed: 14 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
1-
import React, {useMemo} from 'react';
2-
import {View} from 'react-native';
3-
import type {ValueOf} from 'type-fest';
4-
import HeaderWithBackButton from '@components/HeaderWithBackButton';
1+
import React from 'react';
52
import ScreenWrapper from '@components/ScreenWrapper';
6-
import SelectionList from '@components/SelectionList';
7-
import RadioListItem from '@components/SelectionList/ListItem/RadioListItem';
8-
import type {ListItem} from '@components/SelectionList/types';
9-
import useLocalize from '@hooks/useLocalize';
3+
import WorkspaceMemberRoleList from '@components/WorkspaceMemberRoleList';
104
import useOnyx from '@hooks/useOnyx';
11-
import useThemeStyles from '@hooks/useThemeStyles';
125
import useViewportOffsetTop from '@hooks/useViewportOffsetTop';
136
import {setWorkspaceInviteRoleDraft} from '@libs/actions/Policy/Member';
147
import Navigation from '@libs/Navigation/Navigation';
158
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
169
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
17-
import {goBackFromInvalidPolicy, isControlPolicy} from '@libs/PolicyUtils';
10+
import {goBackFromInvalidPolicy} from '@libs/PolicyUtils';
1811
import CONST from '@src/CONST';
1912
import ONYXKEYS from '@src/ONYXKEYS';
2013
import type SCREENS from '@src/SCREENS';
@@ -24,55 +17,15 @@ import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper';
2417
import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
2518
import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
2619

27-
type ListItemType = ListItem<ValueOf<typeof CONST.POLICY.ROLE>> & {
28-
value: ValueOf<typeof CONST.POLICY.ROLE>;
29-
text: string;
30-
alternateText: string;
31-
isSelected: boolean;
32-
};
33-
3420
type WorkspaceInviteMessageRolePageProps = WithPolicyAndFullscreenLoadingProps & PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.WORKSPACE.INVITE_MESSAGE_ROLE>;
3521

3622
function WorkspaceInviteMessageRolePage({policy, route}: WorkspaceInviteMessageRolePageProps) {
37-
const styles = useThemeStyles();
38-
const {translate} = useLocalize();
3923
const [role = CONST.POLICY.ROLE.USER, roleResult] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_ROLE_DRAFT}${route.params.policyID}`, {
4024
canBeMissing: true,
4125
});
4226
const viewportOffsetTop = useViewportOffsetTop();
4327
const isOnyxLoading = isLoadingOnyxValue(roleResult);
4428

45-
const roleItems: ListItemType[] = useMemo(() => {
46-
const items: ListItemType[] = [
47-
{
48-
value: CONST.POLICY.ROLE.ADMIN,
49-
text: translate('common.admin'),
50-
alternateText: translate('workspace.common.adminAlternateText'),
51-
isSelected: role === CONST.POLICY.ROLE.ADMIN,
52-
keyForList: CONST.POLICY.ROLE.ADMIN,
53-
},
54-
{
55-
value: CONST.POLICY.ROLE.AUDITOR,
56-
text: translate('common.auditor'),
57-
alternateText: translate('workspace.common.auditorAlternateText'),
58-
isSelected: role === CONST.POLICY.ROLE.AUDITOR,
59-
keyForList: CONST.POLICY.ROLE.AUDITOR,
60-
},
61-
{
62-
value: CONST.POLICY.ROLE.USER,
63-
text: translate('common.member'),
64-
alternateText: translate('workspace.common.memberAlternateText'),
65-
isSelected: role === CONST.POLICY.ROLE.USER,
66-
keyForList: CONST.POLICY.ROLE.USER,
67-
},
68-
];
69-
70-
if (!isControlPolicy(policy)) {
71-
return items.filter((item) => item.value !== CONST.POLICY.ROLE.AUDITOR);
72-
}
73-
return items;
74-
}, [role, translate, policy]);
75-
7629
return (
7730
<AccessOrNotFoundWrapper
7831
policyID={route.params.policyID}
@@ -85,27 +38,18 @@ function WorkspaceInviteMessageRolePage({policy, route}: WorkspaceInviteMessageR
8538
shouldEnableMaxHeight
8639
style={{marginTop: viewportOffsetTop}}
8740
>
88-
<HeaderWithBackButton
89-
title={translate('common.role')}
90-
onBackButtonPress={() => Navigation.goBack(route.params.backTo)}
41+
<WorkspaceMemberRoleList
42+
role={role}
43+
policy={policy}
44+
isLoading={isOnyxLoading}
45+
onSelectRole={({value}) => {
46+
setWorkspaceInviteRoleDraft(route.params.policyID, value);
47+
Navigation.setNavigationActionToMicrotaskQueue(() => {
48+
Navigation.goBack(route.params.backTo);
49+
});
50+
}}
51+
navigateBackTo={route.params.backTo}
9152
/>
92-
{!isOnyxLoading && (
93-
<View style={[styles.containerWithSpaceBetween, styles.pointerEventsBoxNone]}>
94-
<SelectionList
95-
data={roleItems}
96-
ListItem={RadioListItem}
97-
onSelectRow={({value}: ListItemType) => {
98-
setWorkspaceInviteRoleDraft(route.params.policyID, value);
99-
Navigation.setNavigationActionToMicrotaskQueue(() => {
100-
Navigation.goBack(route.params.backTo);
101-
});
102-
}}
103-
shouldSingleExecuteRowSelect
104-
initiallyFocusedItemKey={roleItems.find((item) => item.isSelected)?.keyForList}
105-
addBottomSafeAreaPadding
106-
/>
107-
</View>
108-
)}
10953
</ScreenWrapper>
11054
</AccessOrNotFoundWrapper>
11155
);

src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx

Lines changed: 2 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,9 @@ import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
5353
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
5454
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
5555
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
56-
import type {ListItemType} from '@pages/workspace/WorkspaceMemberRoleSelectionModal';
57-
import WorkspaceMemberDetailsRoleSelectionModal from '@pages/workspace/WorkspaceMemberRoleSelectionModal';
5856
import variables from '@styles/variables';
5957
import {setIssueNewCardStepAndData} from '@userActions/Card';
60-
import {
61-
clearWorkspaceOwnerChangeFlow,
62-
isApprover as isApproverUserAction,
63-
openPolicyMemberProfilePage,
64-
removeMembers,
65-
requestWorkspaceOwnerChange,
66-
updateWorkspaceMembersRole,
67-
} from '@userActions/Policy/Member';
58+
import {clearWorkspaceOwnerChangeFlow, isApprover as isApproverUserAction, openPolicyMemberProfilePage, removeMembers, requestWorkspaceOwnerChange} from '@userActions/Policy/Member';
6859
import CONST from '@src/CONST';
6960
import ONYXKEYS from '@src/ONYXKEYS';
7061
import ROUTES from '@src/ROUTES';
@@ -98,7 +89,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
9889
const expensifyCardSettings = useExpensifyCardFeeds(policyID);
9990

10091
const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = useState(false);
101-
const [isRoleSelectionModalVisible, setIsRoleSelectionModalVisible] = useState(false);
10292

10393
const accountID = Number(route.params.accountID);
10494
const memberLogin = personalDetails?.[accountID]?.login ?? '';
@@ -191,37 +181,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
191181
});
192182
}, [policy, memberLogin, details.login, isReimburser, translate, displayName, policyOwnerDisplayName]);
193183

194-
const roleItems: ListItemType[] = useMemo(() => {
195-
const items: ListItemType[] = [
196-
{
197-
value: CONST.POLICY.ROLE.ADMIN,
198-
text: translate('common.admin'),
199-
alternateText: translate('workspace.common.adminAlternateText'),
200-
isSelected: member?.role === CONST.POLICY.ROLE.ADMIN,
201-
keyForList: CONST.POLICY.ROLE.ADMIN,
202-
},
203-
{
204-
value: CONST.POLICY.ROLE.AUDITOR,
205-
text: translate('common.auditor'),
206-
alternateText: translate('workspace.common.auditorAlternateText'),
207-
isSelected: member?.role === CONST.POLICY.ROLE.AUDITOR,
208-
keyForList: CONST.POLICY.ROLE.AUDITOR,
209-
},
210-
{
211-
value: CONST.POLICY.ROLE.USER,
212-
text: translate('common.member'),
213-
alternateText: translate('workspace.common.memberAlternateText'),
214-
isSelected: member?.role === CONST.POLICY.ROLE.USER,
215-
keyForList: CONST.POLICY.ROLE.USER,
216-
},
217-
];
218-
219-
if (isControlPolicy(policy)) {
220-
return items;
221-
}
222-
return member?.role === CONST.POLICY.ROLE.AUDITOR ? items : items.filter((item) => item.value !== CONST.POLICY.ROLE.AUDITOR);
223-
}, [member?.role, translate, policy]);
224-
225184
useEffect(() => {
226185
if (!prevMember || prevMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || member?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) {
227186
return;
@@ -329,20 +288,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
329288
Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.getRoute(policyID, activeRoute));
330289
}, [accountID, hasMultipleFeeds, memberLogin, policyID, isAccountLocked, showLockedAccountModal]);
331290

332-
const openRoleSelectionModal = useCallback(() => {
333-
setIsRoleSelectionModalVisible(true);
334-
}, []);
335-
336-
const changeRole = useCallback(
337-
({value}: ListItemType) => {
338-
setIsRoleSelectionModalVisible(false);
339-
if (value !== member?.role) {
340-
updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value);
341-
}
342-
},
343-
[accountID, member?.role, memberLogin, policyID],
344-
);
345-
346291
const startChangeOwnershipFlow = useCallback(() => {
347292
clearWorkspaceOwnerChangeFlow(policyID);
348293
requestWorkspaceOwnerChange(policyID, currentUserPersonalDetails.accountID, currentUserPersonalDetails.login ?? '');
@@ -447,7 +392,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
447392
title={translate(`workspace.common.roleName`, {role: member?.role})}
448393
description={translate('common.role')}
449394
shouldShowRightIcon
450-
onPress={openRoleSelectionModal}
395+
onPress={() => Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS_ROLE.getRoute(policyID, accountID))}
451396
/>
452397
{isControlPolicy(policy) && (
453398
<>
@@ -476,12 +421,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM
476421
onPress={navigateToProfile}
477422
shouldShowRightIcon
478423
/>
479-
<WorkspaceMemberDetailsRoleSelectionModal
480-
isVisible={isRoleSelectionModalVisible}
481-
items={roleItems}
482-
onRoleChange={changeRole}
483-
onClose={() => setIsRoleSelectionModalVisible(false)}
484-
/>
485424
{shouldShowCardsSection && (
486425
<>
487426
<View style={[styles.ph5, styles.pv3]}>

0 commit comments

Comments
 (0)