From 6d2138d200558ad436991d56798616553ee231b2 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 1 Dec 2025 23:41:54 +0700 Subject: [PATCH 1/9] fix: migrate role selection modal to new page --- src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + .../RELATIONS/WORKSPACE_TO_RHP.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 4 + .../members/WorkspaceMemberDetailsPage.tsx | 65 +--------- .../WorkspaceMemberDetailsRolePage.tsx | 115 ++++++++++++++++++ src/pages/workspace/withPolicy.tsx | 1 + 9 files changed, 132 insertions(+), 63 deletions(-) create mode 100644 src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 3893661d7645..170677f60bc1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2003,6 +2003,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 9584b2a5f359..7660954e0dec 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -671,6 +671,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/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 14698f195184..76d6018517ae 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -486,6 +486,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 3bb9ceba4f8b..382a694c514b 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 b2ef587a0955..7373ebbba9db 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -574,6 +574,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/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index ac6798f24ee4..2f231567e1c2 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -52,18 +52,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'; @@ -96,7 +87,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 ?? ''; @@ -189,37 +179,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; @@ -327,20 +286,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); @@ -445,7 +390,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) && ( <> @@ -474,12 +419,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..7850c855dcf7 --- /dev/null +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -0,0 +1,115 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +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 useThemeStyles from '@hooks/useThemeStyles'; +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 {isControlPolicy} from '@libs/PolicyUtils'; +import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; +import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; +import type {PersonalDetailsList} from '@src/types/onyx'; +import AccessOrNotFoundWrapper from '../AccessOrNotFoundWrapper'; + +type ListItemType = ListItem> & { + value: ValueOf; + text: string; + alternateText: string; + isSelected: boolean; +}; + +type WorkspaceMemberDetailsRolePageProps = Omit & + PlatformStackScreenProps & { + /** Personal details of all users */ + personalDetails: OnyxEntry; + }; + +function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: WorkspaceMemberDetailsRolePageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const accountID = Number(route.params.accountID); + const policyID = route.params.policyID; + const memberLogin = personalDetails?.[accountID]?.login ?? ''; + const member = policy?.employeeList?.[memberLogin]; + + 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]); + + const changeRole = useCallback( + ({value}: ListItemType) => { + if (value === member?.role) { + return; + } + updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value); + Navigation.goBack(); + }, + [accountID, member?.role, memberLogin, policyID], + ); + + return ( + + + + + item.isSelected)?.keyForList} + addBottomSafeAreaPadding + /> + + + + ); +} + +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 From b184de4cafbefb14b0ec7e5e7e626f7749c4013e Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Fri, 5 Dec 2025 17:11:16 +0700 Subject: [PATCH 2/9] refactor: dry workspace role list --- src/components/WorkspaceMemberRoleList.tsx | 90 +++++++++++++++++++ .../WorkspaceInviteMessageRolePage.tsx | 74 +++------------ .../WorkspaceMemberDetailsRolePage.tsx | 61 ++----------- 3 files changed, 111 insertions(+), 114 deletions(-) create mode 100644 src/components/WorkspaceMemberRoleList.tsx diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx new file mode 100644 index 000000000000..0fa0c824b232 --- /dev/null +++ b/src/components/WorkspaceMemberRoleList.tsx @@ -0,0 +1,90 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {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 {Route} from '@src/ROUTES'; +import {Policy} from '@src/types/onyx'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import SelectionList from './SelectionList'; +import RadioListItem from './SelectionList/ListItem/RadioListItem'; +import {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 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(navigateBackTo)} + /> + {!isLoading && ( + + item.isSelected)?.keyForList} + addBottomSafeAreaPadding + /> + + )} + + ); +} + +WorkspaceMemberRoleList.displayName = 'WorkspaceMemberRoleList'; +export default WorkspaceMemberRoleList; diff --git a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx index f8054aa3cb11..05bf97a0e5ec 100644 --- a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx @@ -1,20 +1,15 @@ -import React, {useMemo} from 'react'; -import {View} from 'react-native'; +import React from 'react'; import type {ValueOf} from 'type-fest'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; 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'; @@ -34,45 +29,12 @@ type ListItemType = ListItem> & { 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); + }); + }} /> - {!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/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx index 7850c855dcf7..b59fa1b38aaf 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -1,25 +1,19 @@ -import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; +import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; 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 useThemeStyles from '@hooks/useThemeStyles'; +import WorkspaceMemberRoleList 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 {isControlPolicy} from '@libs/PolicyUtils'; +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 type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList} from '@src/types/onyx'; -import AccessOrNotFoundWrapper from '../AccessOrNotFoundWrapper'; type ListItemType = ListItem> & { value: ValueOf; @@ -35,44 +29,11 @@ type WorkspaceMemberDetailsRolePageProps = Omit { - 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]); - const changeRole = useCallback( ({value}: ListItemType) => { if (value === member?.role) { @@ -94,17 +55,11 @@ function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: Worksp includePaddingTop={false} enableEdgeToEdgeBottomSafeAreaPadding > - - - item.isSelected)?.keyForList} - addBottomSafeAreaPadding - /> - + ); From b04fdee9d23d8776ed2b0c63858e6d832d0bed4a Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Fri, 5 Dec 2025 17:14:04 +0700 Subject: [PATCH 3/9] refactor types --- src/components/WorkspaceMemberRoleList.tsx | 1 + src/pages/workspace/WorkspaceInviteMessageRolePage.tsx | 9 +-------- .../members/WorkspaceMemberDetailsRolePage.tsx | 10 +--------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx index 0fa0c824b232..f249acb3f41b 100644 --- a/src/components/WorkspaceMemberRoleList.tsx +++ b/src/components/WorkspaceMemberRoleList.tsx @@ -88,3 +88,4 @@ function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLo WorkspaceMemberRoleList.displayName = 'WorkspaceMemberRoleList'; export default WorkspaceMemberRoleList; +export type {ListItemType}; diff --git a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx index 05bf97a0e5ec..95a65fd3d9e4 100644 --- a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx @@ -19,13 +19,6 @@ 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) { @@ -51,7 +44,7 @@ function WorkspaceInviteMessageRolePage({policy, route}: WorkspaceInviteMessageR role={role} policy={policy} isLoading={isOnyxLoading} - onSelectRole={({value}: ListItemType) => { + onSelectRole={({value}) => { setWorkspaceInviteRoleDraft(route.params.policyID, value); Navigation.setNavigationActionToMicrotaskQueue(() => { Navigation.goBack(route.params.backTo); diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx index b59fa1b38aaf..1e1de79a108e 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -1,9 +1,8 @@ import React, {useCallback} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; import ScreenWrapper from '@components/ScreenWrapper'; -import type {ListItem} from '@components/SelectionList/types'; 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'; @@ -15,13 +14,6 @@ import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import type {PersonalDetailsList} from '@src/types/onyx'; -type ListItemType = ListItem> & { - value: ValueOf; - text: string; - alternateText: string; - isSelected: boolean; -}; - type WorkspaceMemberDetailsRolePageProps = Omit & PlatformStackScreenProps & { /** Personal details of all users */ From 1038904ed3234c6515676d58152195213ff51300 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Fri, 5 Dec 2025 17:17:16 +0700 Subject: [PATCH 4/9] fix: lint --- src/components/WorkspaceMemberRoleList.tsx | 10 +++++----- src/pages/workspace/WorkspaceInviteMessageRolePage.tsx | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx index f249acb3f41b..dfa9001ad8ae 100644 --- a/src/components/WorkspaceMemberRoleList.tsx +++ b/src/components/WorkspaceMemberRoleList.tsx @@ -1,18 +1,18 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; +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 {Route} from '@src/ROUTES'; -import {Policy} from '@src/types/onyx'; +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 {ListItem} from './SelectionList/types'; +import type {ListItem} from './SelectionList/types'; type ListItemType = ListItem> & { value: ValueOf; diff --git a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx index 95a65fd3d9e4..32e5cff9dbcc 100644 --- a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import type {ValueOf} from 'type-fest'; import ScreenWrapper from '@components/ScreenWrapper'; -import type {ListItem} from '@components/SelectionList/types'; import WorkspaceMemberRoleList from '@components/WorkspaceMemberRoleList'; import useOnyx from '@hooks/useOnyx'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; From cc1878cd24d50af363675bbc3b0922b3fa19731d Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 9 Dec 2025 00:45:17 +0700 Subject: [PATCH 5/9] fix: add navigate back route --- src/pages/workspace/WorkspaceInviteMessageRolePage.tsx | 1 + src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx index 32e5cff9dbcc..12234262599f 100644 --- a/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessageRolePage.tsx @@ -48,6 +48,7 @@ function WorkspaceInviteMessageRolePage({policy, route}: WorkspaceInviteMessageR Navigation.goBack(route.params.backTo); }); }} + navigateBackTo={route.params.backTo} /> diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx index 1e1de79a108e..0ac3606df11f 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -11,6 +11,7 @@ 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'; @@ -51,6 +52,7 @@ function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: Worksp role={member?.role} policy={policy} onSelectRole={changeRole} + navigateBackTo={ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policyID, accountID)} /> From 7c27a8ec0765061861443b7c365f039085c0113f Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 9 Dec 2025 00:46:53 +0700 Subject: [PATCH 6/9] fix: backto when select role --- src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx index 0ac3606df11f..6bc0d0145e6f 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -33,7 +33,7 @@ function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: Worksp return; } updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value); - Navigation.goBack(); + Navigation.goBack(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policyID, accountID)); }, [accountID, member?.role, memberLogin, policyID], ); From 78c48de62fbb032fd5dbbf695df3fccd5edd5d4a Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Wed, 10 Dec 2025 17:15:38 +0700 Subject: [PATCH 7/9] fix: add padding top --- src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx index 6bc0d0145e6f..c5b144251703 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -45,7 +45,6 @@ function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: Worksp > Date: Wed, 10 Dec 2025 18:00:40 +0700 Subject: [PATCH 8/9] fix: react compiler --- src/components/WorkspaceMemberRoleList.tsx | 58 +++++++++---------- .../WorkspaceMemberDetailsRolePage.tsx | 19 +++--- 2 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx index dfa9001ad8ae..054114595c85 100644 --- a/src/components/WorkspaceMemberRoleList.tsx +++ b/src/components/WorkspaceMemberRoleList.tsx @@ -33,36 +33,32 @@ function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLo const {translate} = useLocalize(); const styles = useThemeStyles(); - 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, - }, - ]; + 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, + }, + ]; - if (!isControlPolicy(policy)) { - return items.filter((item) => item.value !== CONST.POLICY.ROLE.AUDITOR); - } - return items; - }, [role, translate, policy]); + const isPolicyControl = isControlPolicy(policy); + const availableRoleItems: ListItemType[] = workspaceRoles.filter((item) => isPolicyControl || item.value !== CONST.POLICY.ROLE.AUDITOR); return ( <> @@ -73,11 +69,11 @@ function WorkspaceMemberRoleList({role, policy, navigateBackTo = undefined, isLo {!isLoading && ( item.isSelected)?.keyForList} + initiallyFocusedItemKey={availableRoleItems.find((item) => item.isSelected)?.keyForList} addBottomSafeAreaPadding /> diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx index c5b144251703..30e2f088279d 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsRolePage.tsx @@ -1,4 +1,4 @@ -import React, {useCallback} from 'react'; +import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import ScreenWrapper from '@components/ScreenWrapper'; import WorkspaceMemberRoleList from '@components/WorkspaceMemberRoleList'; @@ -27,16 +27,13 @@ function WorkspaceMemberDetailsRolePage({policy, personalDetails, route}: Worksp const memberLogin = personalDetails?.[accountID]?.login ?? ''; const member = policy?.employeeList?.[memberLogin]; - const changeRole = useCallback( - ({value}: ListItemType) => { - if (value === member?.role) { - return; - } - updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value); - Navigation.goBack(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policyID, accountID)); - }, - [accountID, member?.role, memberLogin, policyID], - ); + const changeRole = ({value}: ListItemType) => { + if (value === member?.role) { + return; + } + updateWorkspaceMembersRole(policyID, [memberLogin], [accountID], value); + Navigation.goBack(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policyID, accountID)); + }; return ( Date: Wed, 10 Dec 2025 18:01:02 +0700 Subject: [PATCH 9/9] fix: remove unused hook --- src/components/WorkspaceMemberRoleList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/WorkspaceMemberRoleList.tsx b/src/components/WorkspaceMemberRoleList.tsx index 054114595c85..44983c756bd6 100644 --- a/src/components/WorkspaceMemberRoleList.tsx +++ b/src/components/WorkspaceMemberRoleList.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest';