diff --git a/src/ROUTES.ts b/src/ROUTES.ts index dbb3e460b6c2..6d6e690e1b51 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2031,6 +2031,10 @@ const ROUTES = { route: 'workspaces/:policyID/members/:accountID', getRoute: (policyID: string, accountID: number) => `workspaces/${policyID}/members/${accountID}` as const, }, + WORKSPACE_MEMBER_DETAILS_ROLE: { + route: 'workspaces/:policyID/members/:accountID/role', + getRoute: (policyID: string, accountID: number) => `workspaces/${policyID}/members/${accountID}/role` as const, + }, WORKSPACE_CUSTOM_FIELDS: { route: 'workspaces/:policyID/members/:accountID/:customFieldType', getRoute: (policyID: string, accountID: number, customFieldType: CustomFieldType) => `/workspaces/${policyID}/members/${accountID}/${customFieldType}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 64a4207cebfb..006662eaff7c 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -680,6 +680,7 @@ const SCREENS = { CATEGORIES_IMPORTED: 'Categories_Imported', MORE_FEATURES: 'Workspace_More_Features', MEMBER_DETAILS: 'Workspace_Member_Details', + MEMBER_DETAILS_ROLE: 'Workspace_Member_Details_Role', MEMBER_CUSTOM_FIELD: 'Workspace_Member_Custom_Field', MEMBER_NEW_CARD: 'Workspace_Member_NewCard', OWNER_CHANGE_CHECK: 'Workspace_Owner_Change_Check', diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx new file mode 100644 index 000000000000..44983c756bd6 --- /dev/null +++ b/src/components/WorkspaceMemberRoleList.tsx @@ -0,0 +1,87 @@ +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 useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import {isControlPolicy} from '@libs/PolicyUtils'; +import CONST from '@src/CONST'; +import type {Route} from '@src/ROUTES'; +import type {Policy} from '@src/types/onyx'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import SelectionList from './SelectionList'; +import RadioListItem from './SelectionList/ListItem/RadioListItem'; +import type {ListItem} from './SelectionList/types'; + +type ListItemType = ListItem> & { + value: ValueOf; + text: string; + alternateText: string; + isSelected: boolean; +}; + +type WorkspaceMemberRoleListProps = { + role: string | undefined; + policy: OnyxEntry; + navigateBackTo?: Route; + isLoading?: boolean; + onSelectRole?: (value: ListItemType) => void; +}; + +function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLoading = false, onSelectRole = () => {}}: WorkspaceMemberRoleListProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const workspaceRoles: ListItemType[] = [ + { + value: CONST.POLICY.ROLE.ADMIN, + text: translate('common.admin'), + alternateText: translate('workspace.common.adminAlternateText'), + isSelected: role === CONST.POLICY.ROLE.ADMIN, + keyForList: CONST.POLICY.ROLE.ADMIN, + }, + { + value: CONST.POLICY.ROLE.AUDITOR, + text: translate('common.auditor'), + alternateText: translate('workspace.common.auditorAlternateText'), + isSelected: role === CONST.POLICY.ROLE.AUDITOR, + keyForList: CONST.POLICY.ROLE.AUDITOR, + }, + { + value: CONST.POLICY.ROLE.USER, + text: translate('common.member'), + alternateText: translate('workspace.common.memberAlternateText'), + isSelected: role === CONST.POLICY.ROLE.USER, + keyForList: CONST.POLICY.ROLE.USER, + }, + ]; + + const isPolicyControl = isControlPolicy(policy); + const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => isPolicyControl || item.value !== CONST.POLICY.ROLE.AUDITOR); + + return ( + <> + Navigation.goBack(navigateBackTo)} + /> + {!isLoading && ( + + item.isSelected)?.keyForList} + addBottomSafeAreaPadding + /> + + )} + + ); +} + +WorkspaceMemberRoleList.displayName = 'WorkspaceMemberRoleList'; +export default WorkspaceMemberRoleList; +export type {ListItemType}; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index e8057f6fc609..13f0af01bcb2 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -493,6 +493,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/downgrade/WorkspaceDowngradePage').default, [SCREENS.WORKSPACE.PAY_AND_DOWNGRADE]: () => require('../../../../pages/workspace/downgrade/PayAndDowngradePage').default, [SCREENS.WORKSPACE.MEMBER_DETAILS]: () => require('../../../../pages/workspace/members/WorkspaceMemberDetailsPage').default, + [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE]: () => require('../../../../pages/workspace/members/WorkspaceMemberDetailsRolePage').default, [SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD]: () => require('../../../../pages/workspace/members/WorkspaceMemberCustomFieldPage').default, [SCREENS.WORKSPACE.MEMBER_NEW_CARD]: () => require('../../../../pages/workspace/members/WorkspaceMemberNewCardPage').default, [SCREENS.WORKSPACE.OWNER_CHANGE_CHECK]: () => require('@pages/workspace/members/WorkspaceOwnerChangeWrapperPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts index 1d8d9e92db9b..048dc497afe7 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts @@ -15,6 +15,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = { [SCREENS.WORKSPACE.MEMBER_DETAILS]: { path: ROUTES.WORKSPACE_MEMBER_DETAILS.route, }, + [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE]: { + path: ROUTES.WORKSPACE_MEMBER_DETAILS_ROLE.route, + }, [SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD]: { path: ROUTES.WORKSPACE_CUSTOM_FIELDS.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 486cb38b8085..fe9138269e00 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -583,6 +583,10 @@ type SettingsNavigatorParamList = { policyID: string; accountID: string; }; + [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE]: { + policyID: string; + accountID: string; + }; [SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD]: { policyID: string; accountID: string; diff --git a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx index f8054aa3cb11..12234262599f 100644 --- a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx @@ -1,20 +1,13 @@ -import React, {useMemo} from 'react'; -import {View} from 'react-native'; -import type {ValueOf} from 'type-fest'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import React from 'react'; import ScreenWrapper from '@components/ScreenWrapper'; -import SelectionList from '@components/SelectionList'; -import RadioListItem from '@components/SelectionList/ListItem/RadioListItem'; -import type {ListItem} from '@components/SelectionList/types'; -import useLocalize from '@hooks/useLocalize'; +import WorkspaceMemberRoleList from '@components/WorkspaceMemberRoleList'; import useOnyx from '@hooks/useOnyx'; -import useThemeStyles from '@hooks/useThemeStyles'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; import {setWorkspaceInviteRoleDraft} from '@libs/actions/Policy/Member'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import {goBackFromInvalidPolicy, isControlPolicy} from '@libs/PolicyUtils'; +import {goBackFromInvalidPolicy} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -24,55 +17,15 @@ import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading'; -type ListItemType = ListItem> & { - value: ValueOf; - text: string; - alternateText: string; - isSelected: boolean; -}; - type WorkspaceInviteMessageRolePageProps = WithPolicyAndFullscreenLoadingProps & PlatformStackScreenProps; function WorkspaceInviteMessageRolePage({policy, route}: WorkspaceInviteMessageRolePageProps) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); const [role = CONST.POLICY.ROLE.USER, roleResult] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_ROLE_DRAFT}${route.params.policyID}`, { canBeMissing: true, }); const viewportOffsetTop = useViewportOffsetTop(); const isOnyxLoading = isLoadingOnyxValue(roleResult); - const roleItems: ListItemType[] = useMemo(() => { - const items: ListItemType[] = [ - { - value: CONST.POLICY.ROLE.ADMIN, - text: translate('common.admin'), - alternateText: translate('workspace.common.adminAlternateText'), - isSelected: role === CONST.POLICY.ROLE.ADMIN, - keyForList: CONST.POLICY.ROLE.ADMIN, - }, - { - value: CONST.POLICY.ROLE.AUDITOR, - text: translate('common.auditor'), - alternateText: translate('workspace.common.auditorAlternateText'), - isSelected: role === CONST.POLICY.ROLE.AUDITOR, - keyForList: CONST.POLICY.ROLE.AUDITOR, - }, - { - value: CONST.POLICY.ROLE.USER, - text: translate('common.member'), - alternateText: translate('workspace.common.memberAlternateText'), - isSelected: role === CONST.POLICY.ROLE.USER, - keyForList: CONST.POLICY.ROLE.USER, - }, - ]; - - if (!isControlPolicy(policy)) { - return items.filter((item) => item.value !== CONST.POLICY.ROLE.AUDITOR); - } - return items; - }, [role, translate, policy]); - return ( - Navigation.goBack(route.params.backTo)} + { + setWorkspaceInviteRoleDraft(route.params.policyID, value); + Navigation.setNavigationActionToMicrotaskQueue(() => { + Navigation.goBack(route.params.backTo); + }); + }} + navigateBackTo={route.params.backTo} /> - {!isOnyxLoading && ( - - { - setWorkspaceInviteRoleDraft(route.params.policyID, value); - Navigation.setNavigationActionToMicrotaskQueue(() => { - Navigation.goBack(route.params.backTo); - }); - }} - shouldSingleExecuteRowSelect - initiallyFocusedItemKey={roleItems.find((item) => item.isSelected)?.keyForList} - addBottomSafeAreaPadding - /> - - )} ); diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 950684c5ff3c..cfc7cf1c924d 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -53,18 +53,9 @@ import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; -import type {ListItemType} from '@pages/workspace/WorkspaceMemberRoleSelectionModal'; -import WorkspaceMemberDetailsRoleSelectionModal from '@pages/workspace/WorkspaceMemberRoleSelectionModal'; import variables from '@styles/variables'; import {setIssueNewCardStepAndData} from '@userActions/Card'; -import { - clearWorkspaceOwnerChangeFlow, - isApprover as isApproverUserAction, - openPolicyMemberProfilePage, - removeMembers, - requestWorkspaceOwnerChange, - updateWorkspaceMembersRole, -} from '@userActions/Policy/Member'; +import {clearWorkspaceOwnerChangeFlow, isApprover as isApproverUserAction, openPolicyMemberProfilePage, removeMembers, requestWorkspaceOwnerChange} from '@userActions/Policy/Member'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -98,7 +89,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM const expensifyCardSettings = useExpensifyCardFeeds(policyID); const [isRemoveMemberConfirmModalVisible, setIsRemoveMemberConfirmModalVisible] = useState(false); - const [isRoleSelectionModalVisible, setIsRoleSelectionModalVisible] = useState(false); const accountID = Number(route.params.accountID); const memberLogin = personalDetails?.[accountID]?.login ?? ''; @@ -191,37 +181,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM }); }, [policy, memberLogin, details.login, isReimburser, translate, displayName, policyOwnerDisplayName]); - const roleItems: ListItemType[] = useMemo(() => { - const items: ListItemType[] = [ - { - value: CONST.POLICY.ROLE.ADMIN, - text: translate('common.admin'), - alternateText: translate('workspace.common.adminAlternateText'), - isSelected: member?.role === CONST.POLICY.ROLE.ADMIN, - keyForList: CONST.POLICY.ROLE.ADMIN, - }, - { - value: CONST.POLICY.ROLE.AUDITOR, - text: translate('common.auditor'), - alternateText: translate('workspace.common.auditorAlternateText'), - isSelected: member?.role === CONST.POLICY.ROLE.AUDITOR, - keyForList: CONST.POLICY.ROLE.AUDITOR, - }, - { - value: CONST.POLICY.ROLE.USER, - text: translate('common.member'), - alternateText: translate('workspace.common.memberAlternateText'), - isSelected: member?.role === CONST.POLICY.ROLE.USER, - keyForList: CONST.POLICY.ROLE.USER, - }, - ]; - - if (isControlPolicy(policy)) { - return items; - } - return member?.role === CONST.POLICY.ROLE.AUDITOR ? items : items.filter((item) => item.value !== CONST.POLICY.ROLE.AUDITOR); - }, [member?.role, translate, policy]); - useEffect(() => { if (!prevMember || prevMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || member?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return; @@ -329,20 +288,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW.getRoute(policyID, activeRoute)); }, [accountID, hasMultipleFeeds, memberLogin, policyID, isAccountLocked, showLockedAccountModal]); - const openRoleSelectionModal = useCallback(() => { - setIsRoleSelectionModalVisible(true); - }, []); - - const changeRole = useCallback( - ({value}: ListItemType) => { - setIsRoleSelectionModalVisible(false); - if (value !== member?.role) { - updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value); - } - }, - [accountID, member?.role, memberLogin, policyID], - ); - const startChangeOwnershipFlow = useCallback(() => { clearWorkspaceOwnerChangeFlow(policyID); requestWorkspaceOwnerChange(policyID, currentUserPersonalDetails.accountID, currentUserPersonalDetails.login ?? ''); @@ -447,7 +392,7 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM title={translate(`workspace.common.roleName`, {role: member?.role})} description={translate('common.role')} shouldShowRightIcon - onPress={openRoleSelectionModal} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS_ROLE.getRoute(policyID, accountID))} /> {isControlPolicy(policy) && ( <> @@ -476,12 +421,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM onPress={navigateToProfile} shouldShowRightIcon /> - setIsRoleSelectionModalVisible(false)} - /> {shouldShowCardsSection && ( <> diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx new file mode 100644 index 000000000000..30e2f088279d --- /dev/null +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import ScreenWrapper from '@components/ScreenWrapper'; +import WorkspaceMemberRoleList from '@components/WorkspaceMemberRoleList'; +import type {ListItemType} from '@components/WorkspaceMemberRoleList'; +import {updateWorkspaceMembersRole} from '@libs/actions/Policy/Member'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetailsList} from '@src/types/onyx'; + +type WorkspaceMemberDetailsRolePageProps = Omit & + PlatformStackScreenProps & { + /** Personal details of all users */ + personalDetails: OnyxEntry; + }; + +function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: WorkspaceMemberDetailsRolePageProps) { + const accountID = Number(route.params.accountID); + const policyID = route.params.policyID; + const memberLogin = personalDetails?.[accountID]?.login ?? ''; + const member = policy?.employeeList?.[memberLogin]; + + const changeRole = ({value}: ListItemType) => { + if (value === member?.role) { + return; + } + updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value); + Navigation.goBack(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policyID, accountID)); + }; + + return ( + + + + + + ); +} + +WorkspaceMemberDetailsRolePage.displayName = 'WorkspaceMemberDetailsRolePage'; + +export default withPolicyAndFullscreenLoading(WorkspaceMemberDetailsRolePage); diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index af306528aec6..2f5b48d990df 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -32,6 +32,7 @@ type PolicyRouteName = | typeof SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET | typeof SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY | typeof SCREENS.WORKSPACE.MEMBER_DETAILS + | typeof SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE | typeof SCREENS.WORKSPACE.MEMBER_CUSTOM_FIELD | typeof SCREENS.WORKSPACE.MEMBER_NEW_CARD | typeof SCREENS.WORKSPACE.INVOICES