From 89ab3a4f1bc8bd346c54430071d0b06aa3ac5b11 Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Fri, 20 Dec 2024 08:53:08 -0300 Subject: [PATCH 1/2] feat: Room roles visibility in members panel --- apps/meteor/app/api/server/v1/rooms.ts | 5 +++ .../client/views/hooks/useMembersList.ts | 7 +-- .../RoomMembers/MembersListDivider.tsx | 18 ++++++++ .../contextualBar/RoomMembers/RoomMembers.tsx | 43 +++++++++++++++---- .../actions/useChangeModeratorAction.tsx | 12 ++++-- .../actions/useChangeOwnerAction.tsx | 17 +++++--- .../lib/findUsersOfRoomOrderedByRole.ts | 2 +- packages/i18n/src/locales/en.i18n.json | 2 + 8 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 apps/meteor/client/views/room/contextualBar/RoomMembers/MembersListDivider.tsx diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 696d8201cf58..32a5c584033b 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -12,6 +12,7 @@ import { isRoomsOpenProps, isRoomsMembersOrderedByRoleProps, } from '@rocket.chat/rest-typings'; +import { MembershipCache } from 'matrix-appservice-bridge'; import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../../lib/isTruthy'; @@ -908,6 +909,10 @@ API.v1.addRoute( rolesInOrder: rolesOrder || ['owner', 'moderator'], }); + members.forEach((member) => { + console.log(member.roles); + }); + return API.v1.success({ members, count: members.length, diff --git a/apps/meteor/client/views/hooks/useMembersList.ts b/apps/meteor/client/views/hooks/useMembersList.ts index 1dc79775908a..f70c48d2b53b 100644 --- a/apps/meteor/client/views/hooks/useMembersList.ts +++ b/apps/meteor/client/views/hooks/useMembersList.ts @@ -11,15 +11,15 @@ type MembersListOptions = { const endpointsByRoomType = { d: '/v1/im.members', - p: '/v1/groups.members', - c: '/v1/channels.members', + p: '/v1/rooms.membersOrderedByRole', + c: '/v1/rooms.membersOrderedByRole', } as const; export const useMembersList = (options: MembersListOptions) => { const getMembers = useEndpoint('GET', endpointsByRoomType[options.roomType]); return useInfiniteQuery( - [options.roomType, 'members', options.rid, options.type, options.debouncedText], + ['members', options.roomType, options.rid, options.type, options.debouncedText], async ({ pageParam }) => { const start = pageParam ?? 0; @@ -27,6 +27,7 @@ export const useMembersList = (options: MembersListOptions) => { roomId: options.rid, offset: start, count: 20, + rolesOrder: ['owner', 'moderator'], ...(options.debouncedText && { filter: options.debouncedText }), ...(options.type !== 'all' && { status: [options.type] }), }); diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/MembersListDivider.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/MembersListDivider.tsx new file mode 100644 index 000000000000..4d2208fdca05 --- /dev/null +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/MembersListDivider.tsx @@ -0,0 +1,18 @@ +import { Box } from '@rocket.chat/fuselage'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +type MembersListDividerProps = { + title: TranslationKey; +}; + +export const MembersListDivider = ({ title }: MembersListDividerProps) => { + const { t } = useTranslation(); + + return ( + + {t(title)} + + ); +}; diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx index 0f182215190c..949ed6a52f95 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx @@ -1,12 +1,13 @@ -import type { IRoom, IUser } from '@rocket.chat/core-typings'; +import type { IRoom, IUser, IRole } from '@rocket.chat/core-typings'; import type { SelectOption } from '@rocket.chat/fuselage'; import { Box, Icon, TextInput, Select, Throbber, ButtonGroup, Button, Callout } from '@rocket.chat/fuselage'; import { useAutoFocus, useDebouncedCallback } from '@rocket.chat/fuselage-hooks'; import { useTranslation, useSetting } from '@rocket.chat/ui-contexts'; import type { ReactElement, FormEventHandler, ComponentProps, MouseEvent } from 'react'; import React, { useMemo } from 'react'; -import { Virtuoso } from 'react-virtuoso'; +import { GroupedVirtuoso } from 'react-virtuoso'; +import { MembersListDivider } from './MembersListDivider'; import RoomMembersRow from './RoomMembersRow'; import { ContextualbarHeader, @@ -21,7 +22,7 @@ import { import { VirtuosoScrollbars } from '../../../../components/CustomScrollbars'; import InfiniteListAnchor from '../../../../components/InfiniteListAnchor'; -type RoomMemberUser = Pick; +type RoomMemberUser = Pick & { roles: IRole['_id'] }; type RoomMembersProps = { rid: IRoom['_id']; @@ -86,6 +87,32 @@ const RoomMembers = ({ const useRealName = useSetting('UI_Use_Real_Name', false); + const { counts, titles } = useMemo(() => { + const owners = members.filter((member) => member.roles?.includes('owner')); + const moderators = members.filter((member) => !member.roles?.includes('owner') && member.roles?.includes('moderator')); + const normalMembers = members.filter((member) => !member.roles?.includes('owner') && !member.roles?.includes('moderator')); + + const counts = []; + const titles = []; + + if (owners.length > 0) { + counts.push(owners.length); + titles.push(); + } + + if (moderators.length > 0) { + counts.push(moderators.length); + titles.push(); + } + + if (members.length > 0) { + counts.push(normalMembers.length); + titles.push(); + } + + return { counts, titles }; + }, [members]); + return ( <> @@ -129,18 +156,18 @@ const RoomMembers = ({ - titles[index]} // eslint-disable-next-line react/no-multi-comp components={{ Scroller: VirtuosoScrollbars, Footer: () => }} - itemContent={(index, data): ReactElement => ( - + itemContent={(index): ReactElement => ( + )} /> diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx index 095695b160c1..e0239c51c112 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useMemo } from 'react'; import GenericModal from '../../../../../components/GenericModal'; import { useEndpointAction } from '../../../../../hooks/useEndpointAction'; +import { queryClient } from '../../../../../lib/queryClient'; import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; import { getRoomDirectives } from '../../../lib/getRoomDirectives'; import { useUserHasRoomRole } from '../../useUserHasRoomRole'; @@ -63,15 +64,18 @@ export const useChangeModeratorAction = (user: Pick, successMessage: t(changeModeratorMessage, { username: user.username, room_name: roomName }), }); - const handleConfirm = useCallback(() => { - changeModerator({ roomId: rid, userId: uid }); + const handleConfirm = useCallback(async () => { + await changeModerator({ roomId: rid, userId: uid }); + queryClient.invalidateQueries({ queryKey: ['members'] }); closeModal(); }, [changeModerator, rid, uid, closeModal]); const handleChangeModerator = useCallback( - ({ userId }) => { + async ({ userId }) => { if (!isRoomFederated(room)) { - return changeModerator({ roomId: rid, userId: uid }); + await changeModerator({ roomId: rid, userId: uid }); + queryClient.invalidateQueries({ queryKey: ['members'] }); + return; } const changingOwnRole = userId === loggedUserId; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx index 5894138d87f4..498d00b6908d 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx @@ -3,6 +3,7 @@ import { isRoomFederated } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { escapeHTML } from '@rocket.chat/string-helpers'; import { useTranslation, usePermission, useUserRoom, useUserSubscription, useSetModal, useUser } from '@rocket.chat/ui-contexts'; +import { useQueryClient } from '@tanstack/react-query'; import type { ReactElement } from 'react'; import React, { useCallback, useMemo } from 'react'; @@ -43,6 +44,7 @@ export const useChangeOwnerAction = (user: Pick, rid: const { _id: loggedUserId = '' } = useUser() || {}; const loggedUserIsOwner = useUserHasRoomRole(loggedUserId, rid, 'owner'); const closeModal = useCallback(() => setModal(null), [setModal]); + const queryClient = useQueryClient(); if (!room) { throw Error('Room not provided'); @@ -58,15 +60,18 @@ export const useChangeOwnerAction = (user: Pick, rid: successMessage: t(changeOwnerMessage, { username: user.username, room_name: roomName }), }); - const handleConfirm = useCallback(() => { - changeOwner({ roomId: rid, userId: uid }); + const handleConfirm = useCallback(async () => { + await changeOwner({ roomId: rid, userId: uid }); + queryClient.invalidateQueries({ queryKey: ['members'] }); closeModal(); - }, [changeOwner, rid, uid, closeModal]); + }, [changeOwner, rid, uid, queryClient, closeModal]); const handleChangeOwner = useCallback( - ({ userId }) => { + async ({ userId }) => { if (!isRoomFederated(room)) { - return changeOwner({ roomId: rid, userId: uid }); + await changeOwner({ roomId: rid, userId: uid }); + queryClient.invalidateQueries({ queryKey: ['members'] }); + return; } const changingOwnRole = userId === loggedUserId; @@ -96,7 +101,7 @@ export const useChangeOwnerAction = (user: Pick, rid: changeOwner({ roomId: rid, userId: uid }); }, - [setModal, loggedUserId, loggedUserIsOwner, t, rid, uid, changeOwner, closeModal, handleConfirm, room], + [room, loggedUserId, loggedUserIsOwner, changeOwner, rid, uid, queryClient, setModal, closeModal, handleConfirm, t], ); const changeOwnerAction = useMutableCallback(async () => handleChangeOwner({ roomId: rid, userId: uid })); diff --git a/apps/meteor/server/lib/findUsersOfRoomOrderedByRole.ts b/apps/meteor/server/lib/findUsersOfRoomOrderedByRole.ts index 29ec32c4b945..9e5dcc684aa4 100644 --- a/apps/meteor/server/lib/findUsersOfRoomOrderedByRole.ts +++ b/apps/meteor/server/lib/findUsersOfRoomOrderedByRole.ts @@ -152,7 +152,7 @@ export async function findUsersOfRoomOrderedByRole({ const totalResult = Subscriptions.col.aggregate([{ $match: { rid } }, ...filteredPipeline, { $count: 'total' }], { allowDiskUse: true }); - const [members, [{ totalCount }]] = await Promise.all([membersResult.toArray(), totalResult.toArray()]); + const [members, [{ total: totalCount }]] = await Promise.all([membersResult.toArray(), totalResult.toArray()]); return { members: members.map((member: any) => { diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index e9c452eaa45c..42f900b14934 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3834,6 +3834,7 @@ "mobile-upload-file_description": "Permission to allow file upload on mobile devices", "Mobile_Push_Notifications_Default_Alert": "Push Notifications Default Alert", "Moderation": "Moderation", + "Moderators": "Moderators", "Moderation_Show_reports": "Show reports", "Moderation_See_reports": "See reports", "Moderation_Go_to_message": "Go to message", @@ -4269,6 +4270,7 @@ "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Override URL to which files are uploaded. This url also used for downloads unless a CDN is given", "Override_Destination_Channel": "Allow to overwrite destination channel in the body parameters", "Owner": "Owner", + "Owners": "Owners", "Play": "Play", "Page_not_exist_or_not_permission": "The page does not exist or you may not have access permission", "Page_not_found": "Page not found", From 3e7527bb5356e1510924d414026a8191a954250a Mon Sep 17 00:00:00 2001 From: MartinSchoeler Date: Tue, 31 Dec 2024 10:11:16 -0300 Subject: [PATCH 2/2] chore: TS --- apps/meteor/app/api/server/v1/rooms.ts | 5 ----- .../room/contextualBar/RoomMembers/RoomMembers.stories.tsx | 1 + .../views/room/contextualBar/RoomMembers/RoomMembers.tsx | 2 +- packages/rest-typings/src/v1/rooms.ts | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index 32a5c584033b..696d8201cf58 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -12,7 +12,6 @@ import { isRoomsOpenProps, isRoomsMembersOrderedByRoleProps, } from '@rocket.chat/rest-typings'; -import { MembershipCache } from 'matrix-appservice-bridge'; import { Meteor } from 'meteor/meteor'; import { isTruthy } from '../../../../lib/isTruthy'; @@ -909,10 +908,6 @@ API.v1.addRoute( rolesInOrder: rolesOrder || ['owner', 'moderator'], }); - members.forEach((member) => { - console.log(member.roles); - }); - return API.v1.success({ members, count: members.length, diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.stories.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.stories.tsx index 379a966ba9f1..c2c53a16c7fe 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.stories.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.stories.tsx @@ -27,6 +27,7 @@ Default.args = { username: 'rocket.cat', status: UserStatus.ONLINE, name: 'Rocket.Cat', + roles: ['owner'], }, ], text: 'filter', diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx index 949ed6a52f95..5cf0e9462e7a 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/RoomMembers.tsx @@ -22,7 +22,7 @@ import { import { VirtuosoScrollbars } from '../../../../components/CustomScrollbars'; import InfiniteListAnchor from '../../../../components/InfiniteListAnchor'; -type RoomMemberUser = Pick & { roles: IRole['_id'] }; +type RoomMemberUser = Pick & { roles?: IRole['_id'][] }; type RoomMembersProps = { rid: IRoom['_id']; diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index 964e5515ca45..181445ffdc7a 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -843,7 +843,7 @@ export type RoomsEndpoints = { '/v1/rooms.membersOrderedByRole': { GET: (params: RoomsMembersOrderedByRoleProps) => PaginatedResult<{ - members: IUser & { roles: IRole['_id'] }[]; + members: IUser & { roles: IRole['_id'][] }; }>; }; };