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,13 +2,19 @@ import * as React from 'react'; +import { useSelector } from './redux-utils.js'; +import { threadInfoSelector } from '../selectors/thread-selectors.js'; import { type UserSurfacedPermissionOption, userSurfacedPermissions, userSurfacedPermissionOptions, } 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 ThreadType, threadTypes } from '../types/thread-types-enum.js'; +import type { + ThreadInfo, + RelativeMemberInfo, + RoleInfo, +} from '../types/thread-types.js'; function useFilterPermissionOptionsByThreadType( threadType: ThreadType, @@ -74,8 +80,47 @@ }, [roleName, defaultRoleID, existingRoleID]); } +function useRolesFromCommunityThreadInfo( + 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] : null, + ), + ); + return roleMap; + } + + const { members: memberInfosFromTopMostThreadInfo, roles } = + topMostThreadInfo; + memberInfosFromTopMostThreadInfo.forEach(memberInfo => { + roleMap.set(memberInfo.id, memberInfo.role ? roles[memberInfo.role] : null); + }); + return roleMap; +} + export { useFilterPermissionOptionsByThreadType, constructRoleDeletionMessagePrompt, useRoleDeletableAndEditableStatus, + useRolesFromCommunityThreadInfo, }; 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 { useRolesFromCommunityThreadInfo } 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 roles = useRolesFromCommunityThreadInfo(props.threadInfo, [ + props.memberInfo, + ]); + const roleName = roles.get(props.memberInfo.id)?.name; + return ( { @@ -109,8 +111,6 @@ [], ); - const roleName = role && roles[role].name; - const label = React.useMemo( () => , [roleName],