From 86a21b5784ad676907dd8f90f45a3f7aacc4a97e Mon Sep 17 00:00:00 2001
From: Douglas Fabris <devfabris@gmail.com>
Date: Tue, 24 Dec 2024 18:12:15 -0300
Subject: [PATCH] fix: Change users room role action message not being
 displayed correctly  (#34503)

---
 .changeset/healthy-coins-scream.md            |   5 +
 .../actions/useChangeLeaderAction.ts          |  51 +++++--
 .../actions/useChangeModeratorAction.tsx      |  74 +++++++---
 .../actions/useChangeOwnerAction.tsx          | 136 +++++++++++-------
 4 files changed, 183 insertions(+), 83 deletions(-)
 create mode 100644 .changeset/healthy-coins-scream.md

diff --git a/.changeset/healthy-coins-scream.md b/.changeset/healthy-coins-scream.md
new file mode 100644
index 00000000000..ede00272b49
--- /dev/null
+++ b/.changeset/healthy-coins-scream.md
@@ -0,0 +1,5 @@
+---
+'@rocket.chat/meteor': patch
+---
+
+Fixes an issue where changing user role in a room displays the wrong message
diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts
index f97ae7dab10..feee1e61832 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts
@@ -1,19 +1,34 @@
 import type { IRoom, IUser } from '@rocket.chat/core-typings';
-import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
-import { useTranslation, usePermission, useUserRoom, useUserSubscription } from '@rocket.chat/ui-contexts';
-import { useMemo } from 'react';
+import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
+import {
+	useTranslation,
+	usePermission,
+	useUserRoom,
+	useUserSubscription,
+	useEndpoint,
+	useToastMessageDispatch,
+} from '@rocket.chat/ui-contexts';
+import { useMutation } from '@tanstack/react-query';
+import { useCallback, useMemo } from 'react';
 
-import { useEndpointAction } from '../../../../../hooks/useEndpointAction';
 import { getRoomDirectives } from '../../../lib/getRoomDirectives';
 import { useUserHasRoomRole } from '../../useUserHasRoomRole';
 import type { UserInfoAction, UserInfoActionType } from '../useUserInfoActions';
 
+const getEndpoint = (roomType: string, isLeader: boolean) => {
+	if (roomType === 'p') {
+		return isLeader ? '/v1/groups.removeLeader' : '/v1/groups.addLeader';
+	}
+	return isLeader ? '/v1/channels.removeLeader' : '/v1/channels.addLeader';
+};
+
 export const useChangeLeaderAction = (user: Pick<IUser, '_id' | 'username'>, rid: IRoom['_id']): UserInfoAction | undefined => {
 	const t = useTranslation();
 	const room = useUserRoom(rid);
-	const { _id: uid } = user;
+	const { _id: uid, username } = user;
 	const userCanSetLeader = usePermission('set-leader', rid);
 	const userSubscription = useUserSubscription(rid);
+	const dispatchToastMessage = useToastMessageDispatch();
 
 	if (!room) {
 		throw Error('Room not provided');
@@ -22,13 +37,27 @@ export const useChangeLeaderAction = (user: Pick<IUser, '_id' | 'username'>, rid
 	const { roomCanSetLeader } = getRoomDirectives({ room, showingUserId: uid, userSubscription });
 	const isLeader = useUserHasRoomRole(uid, rid, 'leader');
 
-	const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels';
-	const changeLeaderEndpoint = isLeader ? 'removeLeader' : 'addLeader';
-	const changeLeaderMessage = isLeader ? 'removed__username__as__role_' : 'set__username__as__role_';
-	const changeLeader = useEndpointAction('POST', `${endpointPrefix}.${changeLeaderEndpoint}`, {
-		successMessage: t(changeLeaderMessage, { username: user.username, role: 'leader' }),
+	const toggleLeaderEndpoint = useEndpoint('POST', getEndpoint(room.t, isLeader));
+
+	const toggleOwnerMutation = useMutation({
+		mutationFn: useCallback(
+			async ({ roomId, userId }) => {
+				await toggleLeaderEndpoint({ roomId, userId });
+
+				return t(isLeader ? 'removed__username__as__role_' : 'set__username__as__role_', { username, role: 'leader' });
+			},
+			[t, isLeader, toggleLeaderEndpoint, username],
+		),
+		onSuccess: (message) => {
+			dispatchToastMessage({ type: 'success', message });
+		},
+		onError: (error) => {
+			dispatchToastMessage({ type: 'error', message: error });
+		},
 	});
-	const changeLeaderAction = useMutableCallback(() => changeLeader({ roomId: rid, userId: uid }));
+
+	const changeLeaderAction = useEffectEvent(async () => toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid }));
+
 	const changeLeaderOption = useMemo(
 		() =>
 			roomCanSetLeader && userCanSetLeader
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 095695b160c..9a50a63567f 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.tsx
@@ -1,13 +1,22 @@
 import type { IRoom, IUser } from '@rocket.chat/core-typings';
 import { isRoomFederated } from '@rocket.chat/core-typings';
-import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+import { useEffectEvent } from '@rocket.chat/fuselage-hooks';
 import { escapeHTML } from '@rocket.chat/string-helpers';
-import { useTranslation, usePermission, useUserRoom, useUserSubscription, useUser, useSetModal } from '@rocket.chat/ui-contexts';
+import {
+	useTranslation,
+	usePermission,
+	useUserRoom,
+	useUserSubscription,
+	useUser,
+	useSetModal,
+	useEndpoint,
+	useToastMessageDispatch,
+} from '@rocket.chat/ui-contexts';
+import { useMutation } from '@tanstack/react-query';
 import type { ReactElement } from 'react';
 import React, { useCallback, useMemo } from 'react';
 
 import GenericModal from '../../../../../components/GenericModal';
-import { useEndpointAction } from '../../../../../hooks/useEndpointAction';
 import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';
 import { getRoomDirectives } from '../../../lib/getRoomDirectives';
 import { useUserHasRoomRole } from '../../useUserHasRoomRole';
@@ -32,6 +41,13 @@ const getWarningModalForFederatedRooms = (
 	</GenericModal>
 );
 
+const getEndpoint = (roomType: string, isModerator: boolean) => {
+	if (roomType === 'p') {
+		return isModerator ? '/v1/groups.removeModerator' : '/v1/groups.addModerator';
+	}
+	return isModerator ? '/v1/channels.removeModerator' : '/v1/channels.addModerator';
+};
+
 export const useChangeModeratorAction = (user: Pick<IUser, '_id' | 'username'>, rid: IRoom['_id']): UserInfoAction | undefined => {
 	const t = useTranslation();
 	const room = useUserRoom(rid);
@@ -44,7 +60,7 @@ export const useChangeModeratorAction = (user: Pick<IUser, '_id' | 'username'>,
 	const loggedUserIsModerator = useUserHasRoomRole(loggedUserId, rid, 'moderator');
 	const loggedUserIsOwner = useUserHasRoomRole(loggedUserId, rid, 'owner');
 	const setModal = useSetModal();
-	const closeModal = useCallback(() => setModal(null), [setModal]);
+	const dispatchToastMessage = useToastMessageDispatch();
 
 	if (!room) {
 		throw Error('Room not provided');
@@ -53,27 +69,42 @@ export const useChangeModeratorAction = (user: Pick<IUser, '_id' | 'username'>,
 	const { roomCanSetModerator } = getRoomDirectives({ room, showingUserId: uid, userSubscription });
 	const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room));
 
-	const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels';
-	const changeModeratorEndpoint = isModerator ? 'removeModerator' : 'addModerator';
-	const changeModeratorMessage = isModerator
-		? 'User__username__removed_from__room_name__moderators'
-		: 'User__username__is_now_a_moderator_of__room_name_';
-
-	const changeModerator = useEndpointAction('POST', `${endpointPrefix}.${changeModeratorEndpoint}`, {
-		successMessage: t(changeModeratorMessage, { username: user.username, room_name: roomName }),
+	const toggleModeratorEndpoint = useEndpoint('POST', getEndpoint(room.t, isModerator));
+	const toggleModerator = useMutation({
+		mutationFn: useCallback(
+			async ({ roomId, userId }) => {
+				await toggleModeratorEndpoint({ roomId, userId });
+
+				return t(
+					isModerator ? 'User__username__removed_from__room_name__moderators' : 'User__username__is_now_a_moderator_of__room_name_',
+					{
+						username: user.username,
+						room_name: roomName,
+					},
+				);
+			},
+			[toggleModeratorEndpoint, t, isModerator, user.username, roomName],
+		),
+		onSuccess: (message) => {
+			dispatchToastMessage({ type: 'success', message });
+		},
+		onError: (error) => {
+			dispatchToastMessage({ type: 'error', message: error });
+		},
 	});
 
-	const handleConfirm = useCallback(() => {
-		changeModerator({ roomId: rid, userId: uid });
-		closeModal();
-	}, [changeModerator, rid, uid, closeModal]);
-
 	const handleChangeModerator = useCallback(
 		({ userId }) => {
 			if (!isRoomFederated(room)) {
-				return changeModerator({ roomId: rid, userId: uid });
+				return toggleModerator.mutateAsync({ roomId: rid, userId: uid });
 			}
 
+			const closeModal = () => setModal(null);
+			const handleConfirm = async () => {
+				await toggleModerator.mutateAsync({ roomId: rid, userId: uid });
+				closeModal();
+			};
+
 			const changingOwnRole = userId === loggedUserId;
 			if (changingOwnRole && loggedUserIsModerator) {
 				return setModal(() =>
@@ -111,12 +142,13 @@ export const useChangeModeratorAction = (user: Pick<IUser, '_id' | 'username'>,
 				);
 			}
 
-			changeModerator({ roomId: rid, userId: uid });
+			toggleModerator.mutateAsync({ roomId: rid, userId: uid });
 		},
-		[setModal, loggedUserId, loggedUserIsModerator, loggedUserIsOwner, t, rid, uid, changeModerator, closeModal, handleConfirm, room],
+		[setModal, loggedUserId, loggedUserIsModerator, loggedUserIsOwner, t, rid, uid, toggleModerator, room],
 	);
 
-	const changeModeratorAction = useMutableCallback(() => handleChangeModerator({ roomId: rid, userId: uid }));
+	const changeModeratorAction = useEffectEvent(() => handleChangeModerator({ roomId: rid, userId: uid }));
+
 	const changeModeratorOption = useMemo(
 		() =>
 			(isRoomFederated(room) && roomCanSetModerator) || (!isRoomFederated(room) && roomCanSetModerator && userCanSetModerator)
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 5894138d87f..d550a43efd1 100644
--- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx
+++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx
@@ -1,13 +1,22 @@
 import type { IRoom, IUser } from '@rocket.chat/core-typings';
 import { isRoomFederated } from '@rocket.chat/core-typings';
-import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+import { useEffectEvent } 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 {
+	useTranslation,
+	usePermission,
+	useUserRoom,
+	useUserSubscription,
+	useSetModal,
+	useUser,
+	useToastMessageDispatch,
+	useEndpoint,
+} from '@rocket.chat/ui-contexts';
+import { useMutation } from '@tanstack/react-query';
 import type { ReactElement } from 'react';
 import React, { useCallback, useMemo } from 'react';
 
 import GenericModal from '../../../../../components/GenericModal';
-import { useEndpointAction } from '../../../../../hooks/useEndpointAction';
 import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator';
 import { getRoomDirectives } from '../../../lib/getRoomDirectives';
 import { useUserHasRoomRole } from '../../useUserHasRoomRole';
@@ -32,17 +41,24 @@ const getWarningModalForFederatedRooms = (
 	</GenericModal>
 );
 
+const getEndpoint = (roomType: string, isOwner: boolean) => {
+	if (roomType === 'p') {
+		return isOwner ? '/v1/groups.removeOwner' : '/v1/groups.addOwner';
+	}
+	return isOwner ? '/v1/channels.removeOwner' : '/v1/channels.addOwner';
+};
+
 export const useChangeOwnerAction = (user: Pick<IUser, '_id' | 'username'>, rid: IRoom['_id']): UserInfoAction | undefined => {
 	const t = useTranslation();
 	const room = useUserRoom(rid);
-	const { _id: uid } = user;
+	const { _id: uid, username } = user;
 	const userCanSetOwner = usePermission('set-owner', rid);
 	const isOwner = useUserHasRoomRole(uid, rid, 'owner');
 	const userSubscription = useUserSubscription(rid);
 	const setModal = useSetModal();
 	const { _id: loggedUserId = '' } = useUser() || {};
 	const loggedUserIsOwner = useUserHasRoomRole(loggedUserId, rid, 'owner');
-	const closeModal = useCallback(() => setModal(null), [setModal]);
+	const dispatchToastMessage = useToastMessageDispatch();
 
 	if (!room) {
 		throw Error('Room not provided');
@@ -51,55 +67,73 @@ export const useChangeOwnerAction = (user: Pick<IUser, '_id' | 'username'>, rid:
 	const { roomCanSetOwner } = getRoomDirectives({ room, showingUserId: uid, userSubscription });
 	const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room));
 
-	const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels';
-	const changeOwnerEndpoint = isOwner ? 'removeOwner' : 'addOwner';
-	const changeOwnerMessage = isOwner ? 'User__username__removed_from__room_name__owners' : 'User__username__is_now_an_owner_of__room_name_';
-	const changeOwner = useEndpointAction('POST', `${endpointPrefix}.${changeOwnerEndpoint}`, {
-		successMessage: t(changeOwnerMessage, { username: user.username, room_name: roomName }),
-	});
+	const toggleOwnerEndpoint = useEndpoint('POST', getEndpoint(room.t, isOwner));
 
-	const handleConfirm = useCallback(() => {
-		changeOwner({ roomId: rid, userId: uid });
-		closeModal();
-	}, [changeOwner, rid, uid, closeModal]);
-
-	const handleChangeOwner = useCallback(
-		({ userId }) => {
-			if (!isRoomFederated(room)) {
-				return changeOwner({ roomId: rid, userId: uid });
-			}
-			const changingOwnRole = userId === loggedUserId;
-
-			if (changingOwnRole && loggedUserIsOwner) {
-				return setModal(() =>
-					getWarningModalForFederatedRooms(
-						closeModal,
-						handleConfirm,
-						t('Federation_Matrix_losing_privileges'),
-						t('Yes_continue'),
-						t('Federation_Matrix_losing_privileges_warning'),
-					),
-				);
-			}
-
-			if (!changingOwnRole && loggedUserIsOwner) {
-				return setModal(() =>
-					getWarningModalForFederatedRooms(
-						closeModal,
-						handleConfirm,
-						t('Warning'),
-						t('Yes_continue'),
-						t('Federation_Matrix_giving_same_permission_warning'),
-					),
-				);
-			}
-
-			changeOwner({ roomId: rid, userId: uid });
+	const toggleOwnerMutation = useMutation({
+		mutationFn: useCallback(
+			async ({ roomId, userId }) => {
+				await toggleOwnerEndpoint({ roomId, userId });
+
+				return t(isOwner ? 'User__username__removed_from__room_name__owners' : 'User__username__is_now_an_owner_of__room_name_', {
+					username,
+					room_name: roomName,
+				});
+			},
+			[toggleOwnerEndpoint, t, isOwner, username, roomName],
+		),
+		onSuccess: (message) => {
+			dispatchToastMessage({ type: 'success', message });
 		},
-		[setModal, loggedUserId, loggedUserIsOwner, t, rid, uid, changeOwner, closeModal, handleConfirm, room],
-	);
+		onError: (error) => {
+			dispatchToastMessage({ type: 'error', message: error });
+		},
+	});
+
+	const handleChangeOwner = useCallback(() => {
+		if (!isRoomFederated(room)) {
+			return toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid });
+		}
+
+		const changingOwnRole = uid === loggedUserId;
+
+		const closeModal = () => {
+			setModal(null);
+		};
+
+		const handleConfirm = () => {
+			toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid });
+			closeModal();
+		};
+
+		if (changingOwnRole && loggedUserIsOwner) {
+			return setModal(() =>
+				getWarningModalForFederatedRooms(
+					closeModal,
+					handleConfirm,
+					t('Federation_Matrix_losing_privileges'),
+					t('Yes_continue'),
+					t('Federation_Matrix_losing_privileges_warning'),
+				),
+			);
+		}
+
+		if (!changingOwnRole && loggedUserIsOwner) {
+			return setModal(() =>
+				getWarningModalForFederatedRooms(
+					closeModal,
+					handleConfirm,
+					t('Warning'),
+					t('Yes_continue'),
+					t('Federation_Matrix_giving_same_permission_warning'),
+				),
+			);
+		}
+
+		toggleOwnerMutation.mutateAsync({ roomId: rid, userId: uid });
+	}, [room, loggedUserId, loggedUserIsOwner, toggleOwnerMutation, rid, uid, t, setModal]);
+
+	const changeOwnerAction = useEffectEvent(async () => handleChangeOwner());
 
-	const changeOwnerAction = useMutableCallback(async () => handleChangeOwner({ roomId: rid, userId: uid }));
 	const changeOwnerOption = useMemo(
 		() =>
 			(isRoomFederated(room) && roomCanSetOwner) || (!isRoomFederated(room) && roomCanSetOwner && userCanSetOwner)
-- 
GitLab