diff --git a/lib/utils/role-utils.js b/lib/utils/role-utils.js --- a/lib/utils/role-utils.js +++ b/lib/utils/role-utils.js @@ -2,6 +2,8 @@ import * as React from 'react'; +import { useSelector } from './redux-utils.js'; +import { threadInfoSelector } from '../selectors/thread-selectors.js'; import { type UserSurfacedPermissionOption, userSurfacedPermissions, @@ -9,6 +11,7 @@ } from '../types/thread-permission-types.js'; import type { ThreadType } from '../types/thread-types-enum.js'; import { threadTypes } from '../types/thread-types-enum.js'; +import type { ThreadInfo, RelativeMemberInfo } from '../types/thread-types.js'; function useFilterPermissionOptionsByThreadType( threadType: ThreadType, @@ -74,8 +77,47 @@ }, [roleName, defaultRoleID, existingRoleID]); } +function useRoleNamesFromCommunityThreadInfo( + threadInfo: ThreadInfo, + memberInfos: $ReadOnlyArray, +): $ReadOnlyMap { + // Our in-code system has chat-specific roles, while the + // user-surfaced system has roles only for communities. We retrieve roles + // from the top-level community thread for accuracy, with a rare fallback + // for potential issues reading memberInfos, primarily in GENESIS threads. + // The special case is GENESIS threads, since per prior discussion + // (see context: https://linear.app/comm/issue/ENG-4077/), we don't really + // support roles for it. Also with GENESIS, the list of members are not + // populated in the community root. So in this case to prevent crashing, we + // should just return the role name from the current thread info. + const { community } = threadInfo; + const communityThreadInfo = useSelector(state => + community ? threadInfoSelector(state)[community] : null, + ); + const topMostThreadInfo = communityThreadInfo || threadInfo; + const roleMap = new Map(); + + if (topMostThreadInfo.type === threadTypes.GENESIS) { + memberInfos.forEach(memberInfo => + roleMap.set( + memberInfo.id, + memberInfo.role ? threadInfo.roles[memberInfo.role].name : null, + ), + ); + return roleMap; + } + + const { members: memberInfosFromTopMostThreadInfo, roles } = + topMostThreadInfo; + memberInfosFromTopMostThreadInfo.forEach(memberInfo => { + roleMap.set(memberInfo.id, memberInfo.role && roles[memberInfo.role].name); + }); + return roleMap; +} + export { useFilterPermissionOptionsByThreadType, constructRoleDeletionMessagePrompt, useRoleDeletableAndEditableStatus, + useRoleNamesFromCommunityThreadInfo, }; diff --git a/native/chat/settings/thread-settings-member.react.js b/native/chat/settings/thread-settings-member.react.js --- a/native/chat/settings/thread-settings-member.react.js +++ b/native/chat/settings/thread-settings-member.react.js @@ -23,6 +23,7 @@ type ThreadInfo, type RelativeMemberInfo, } from 'lib/types/thread-types.js'; +import { useRoleNamesFromCommunityThreadInfo } from 'lib/utils/role-utils.js'; import type { ThreadSettingsNavigate } from './thread-settings.react.js'; import UserAvatar from '../../avatars/user-avatar.react.js'; @@ -55,6 +56,7 @@ type Props = { ...BaseProps, // Redux state + +roleName: ?string, +removeUserLoadingStatus: LoadingStatus, +changeRoleLoadingStatus: LoadingStatus, +colors: Colors, @@ -116,14 +118,10 @@ ); } - const roleName = - this.props.memberInfo.role && - this.props.threadInfo.roles[this.props.memberInfo.role].name; - const roleInfo = ( - {roleName} + {this.props.roleName} ); @@ -280,10 +278,16 @@ const navigateToUserProfileBottomSheet = useNavigateToUserProfileBottomSheet(); + const roleNames = useRoleNamesFromCommunityThreadInfo(props.threadInfo, [ + props.memberInfo, + ]); + const roleName = roleNames.get(props.memberInfo.id); + return ( { @@ -109,8 +113,6 @@ [], ); - const roleName = role && roles[role].name; - const label = React.useMemo( () => , [roleName],