diff --git a/apps/meteor/client/views/hooks/useActionSpread.ts b/apps/meteor/client/views/hooks/useActionSpread.ts index 21a089ba1443..b1c2fddc9e9b 100644 --- a/apps/meteor/client/views/hooks/useActionSpread.ts +++ b/apps/meteor/client/views/hooks/useActionSpread.ts @@ -1,14 +1,14 @@ -import { useMemo } from 'react'; +import { useMemo, ReactNode } from 'react'; -type Action = { - label: string; - icon: string; - action: () => any; +export type Action = { + label: ReactNode; + icon?: string; + action: () => void; }; type MenuOption = { - label: { label: string; icon: string }; - action: Function; + label: { label: ReactNode; icon?: string }; + action: () => void; }; const mapOptions = ([key, { action, label, icon }]: [string, Action]): [string, MenuOption] => [ diff --git a/apps/meteor/client/views/room/hooks/useUserHasRoomRole.ts b/apps/meteor/client/views/room/hooks/useUserHasRoomRole.ts new file mode 100644 index 000000000000..cc9e93bdf318 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserHasRoomRole.ts @@ -0,0 +1,8 @@ +import { IRole, IRoom, IUser } from '@rocket.chat/core-typings'; +import { useCallback } from 'react'; + +import { RoomRoles } from '../../../../app/models/client'; +import { useReactiveValue } from '../../../hooks/useReactiveValue'; + +export const useUserHasRoomRole = (uid: IUser['_id'], rid: IRoom['_id'], role: IRole['name']): boolean => + useReactiveValue(useCallback(() => !!RoomRoles.findOne({ rid, 'u._id': uid, 'roles': role }), [uid, rid, role])); diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions.js b/apps/meteor/client/views/room/hooks/useUserInfoActions.js deleted file mode 100644 index fc6fdabd4a1f..000000000000 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions.js +++ /dev/null @@ -1,415 +0,0 @@ -import { Button, ButtonGroup, Icon, Modal, Box } from '@rocket.chat/fuselage'; -import { useAutoFocus, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { escapeHTML } from '@rocket.chat/string-helpers'; -import { - useSetModal, - useToastMessageDispatch, - useRoute, - useUserId, - useUserSubscription, - useUserRoom, - useUserSubscriptionByName, - usePermission, - useAllPermissions, - useMethod, - useTranslation, -} from '@rocket.chat/ui-contexts'; -import React, { useCallback, useMemo } from 'react'; - -import { RoomRoles } from '../../../../app/models/client'; -import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; -import { useEndpointActionExperimental } from '../../../hooks/useEndpointActionExperimental'; -import { useReactiveValue } from '../../../hooks/useReactiveValue'; -import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; -import RemoveUsersModal from '../../teams/contextualBar/members/RemoveUsersModal'; -import { useWebRTC } from './useWebRTC'; - -const useUserHasRoomRole = (uid, rid, role) => - useReactiveValue(useCallback(() => !!RoomRoles.findOne({ rid, 'u._id': uid, 'roles': role }), [uid, rid, role])); - -const getShouldOpenDirectMessage = (currentSubscription, usernameSubscription, canOpenDirectMessage, username) => { - const canOpenDm = canOpenDirectMessage || usernameSubscription; - const directMessageIsNotAlreadyOpen = currentSubscription && currentSubscription.name !== username; - return canOpenDm && directMessageIsNotAlreadyOpen; -}; - -const getUserIsMuted = (room, user, userCanPostReadonly) => { - if (room && room.ro) { - if (Array.isArray(room.unmuted) && room.unmuted.indexOf(user && user.username) !== -1) { - return false; - } - - if (userCanPostReadonly) { - return Array.isArray(room.muted) && room.muted.indexOf(user && user.username) !== -1; - } - - return true; - } - - return room && Array.isArray(room.muted) && room.muted.indexOf(user && user.username) > -1; -}; - -const WarningModal = ({ text, confirmText, close, confirm, ...props }) => { - const refAutoFocus = useAutoFocus(true); - const t = useTranslation(); - return ( - - - - {t('Are_you_sure')} - - - {text} - - - - - - - - ); -}; -// TODO: Remove endpoint concatenation -export const useUserInfoActions = (user = {}, rid, reload) => { - const t = useTranslation(); - const dispatchToastMessage = useToastMessageDispatch(); - const directRoute = useRoute('direct'); - - const setModal = useSetModal(); - - const { _id: uid } = user; - const ownUserId = useUserId(); - - const closeModal = useMutableCallback(() => setModal(null)); - - const room = useUserRoom(rid); - const currentSubscription = useUserSubscription(rid); - const usernameSubscription = useUserSubscriptionByName(user.username); - - const isLeader = useUserHasRoomRole(uid, rid, 'leader'); - const isModerator = useUserHasRoomRole(uid, rid, 'moderator'); - const isOwner = useUserHasRoomRole(uid, rid, 'owner'); - - const otherUserCanPostReadonly = useAllPermissions('post-readonly', rid); - - const isIgnored = currentSubscription && currentSubscription.ignored && currentSubscription.ignored.indexOf(uid) > -1; - const isMuted = getUserIsMuted(room, user, otherUserCanPostReadonly); - - const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels'; - - const roomDirectives = room && room.t && roomCoordinator.getRoomDirectives(room.t); - - const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove] = [ - ...(roomDirectives && [ - roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_OWNER), - roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_LEADER), - roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_MODERATOR), - roomDirectives.allowMemberAction(room, RoomMemberActions.IGNORE), - roomDirectives.allowMemberAction(room, RoomMemberActions.BLOCK), - roomDirectives.allowMemberAction(room, RoomMemberActions.MUTE), - roomDirectives.allowMemberAction(room, RoomMemberActions.REMOVE_USER), - ]), - ]; - - const roomName = room && room.t && escapeHTML(roomCoordinator.getRoomName(room.t, room)); - - const userCanSetOwner = usePermission('set-owner', rid); - const userCanSetLeader = usePermission('set-leader', rid); - const userCanSetModerator = usePermission('set-moderator', rid); - const userCanMute = usePermission('mute-user', rid); - const userCanRemove = usePermission('remove-user', rid); - const userCanDirectMessage = usePermission('create-d'); - const { shouldAllowCalls, callInProgress, joinCall, startCall } = useWebRTC(rid); - - const shouldOpenDirectMessage = getShouldOpenDirectMessage( - currentSubscription, - usernameSubscription, - userCanDirectMessage, - user.username, - ); - - const openDirectDm = useMutableCallback(() => - directRoute.push({ - rid: user.username, - }), - ); - - const openDirectMessageOption = useMemo( - () => - shouldOpenDirectMessage && { - label: t('Direct_Message'), - icon: 'balloon', - action: openDirectDm, - }, - [openDirectDm, shouldOpenDirectMessage, t], - ); - - const videoCallOption = useMemo(() => { - const handleJoinCall = () => { - joinCall({ audio: true, video: true }); - }; - const handleStartCall = () => { - startCall({ audio: true, video: true }); - }; - const action = callInProgress ? handleJoinCall : handleStartCall; - - return ( - shouldAllowCalls && { - label: t(callInProgress ? 'Join_video_call' : 'Start_video_call'), - icon: 'video', - action, - } - ); - }, [callInProgress, shouldAllowCalls, t, joinCall, startCall]); - - const audioCallOption = useMemo(() => { - const handleJoinCall = () => { - joinCall({ audio: true, video: false }); - }; - const handleStartCall = () => { - startCall({ audio: true, video: false }); - }; - const action = callInProgress ? handleJoinCall : handleStartCall; - - return ( - shouldAllowCalls && { - label: t(callInProgress ? 'Join_audio_call' : 'Start_audio_call'), - icon: 'mic', - action, - } - ); - }, [callInProgress, shouldAllowCalls, t, joinCall, startCall]); - - const changeOwnerEndpoint = isOwner ? 'removeOwner' : 'addOwner'; - const changeOwnerMessage = isOwner ? 'User__username__removed_from__room_name__owners' : 'User__username__is_now_a_owner_of__room_name_'; - const changeOwner = useEndpointActionExperimental( - 'POST', - `${endpointPrefix}.${changeOwnerEndpoint}`, - t(changeOwnerMessage, { username: user.username, room_name: roomName }), - ); - const changeOwnerAction = useMutableCallback(async () => changeOwner({ roomId: rid, userId: uid })); - const changeOwnerOption = useMemo( - () => - roomCanSetOwner && - userCanSetOwner && { - label: t(isOwner ? 'Remove_as_owner' : 'Set_as_owner'), - icon: 'shield-check', - action: changeOwnerAction, - }, - [changeOwnerAction, isOwner, t, roomCanSetOwner, userCanSetOwner], - ); - - const changeLeaderEndpoint = isLeader ? 'removeLeader' : 'addLeader'; - const changeLeaderMessage = isLeader - ? 'User__username__removed_from__room_name__leaders' - : 'User__username__is_now_a_leader_of__room_name_'; - const changeLeader = useEndpointActionExperimental( - 'POST', - `${endpointPrefix}.${changeLeaderEndpoint}`, - t(changeLeaderMessage, { username: user.username, room_name: roomName }), - ); - const changeLeaderAction = useMutableCallback(() => changeLeader({ roomId: rid, userId: uid })); - const changeLeaderOption = useMemo( - () => - roomCanSetLeader && - userCanSetLeader && { - label: t(isLeader ? 'Remove_as_leader' : 'Set_as_leader'), - icon: 'shield-alt', - action: changeLeaderAction, - }, - [isLeader, roomCanSetLeader, t, userCanSetLeader, changeLeaderAction], - ); - - 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 = useEndpointActionExperimental( - 'POST', - `${endpointPrefix}.${changeModeratorEndpoint}`, - t(changeModeratorMessage, { username: user.username, room_name: roomName }), - ); - const changeModeratorAction = useMutableCallback(() => changeModerator({ roomId: rid, userId: uid })); - const changeModeratorOption = useMemo( - () => - roomCanSetModerator && - userCanSetModerator && { - label: t(isModerator ? 'Remove_as_moderator' : 'Set_as_moderator'), - icon: 'shield', - action: changeModeratorAction, - }, - [changeModeratorAction, isModerator, roomCanSetModerator, t, userCanSetModerator], - ); - - const ignoreUser = useMethod('ignoreUser'); - const ignoreUserAction = useMutableCallback(async () => { - try { - await ignoreUser({ rid, userId: uid, ignore: !isIgnored }); - if (isIgnored) { - dispatchToastMessage({ type: 'success', message: t('User_has_been_unignored') }); - } else { - dispatchToastMessage({ type: 'success', message: t('User_has_been_ignored') }); - } - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - } - }); - const ignoreUserOption = useMemo( - () => - roomCanIgnore && - uid !== ownUserId && { - label: t(isIgnored ? 'Unignore' : 'Ignore'), - icon: 'ban', - action: ignoreUserAction, - }, - [ignoreUserAction, isIgnored, ownUserId, roomCanIgnore, t, uid], - ); - - const isUserBlocked = currentSubscription && currentSubscription.blocker; - const toggleBlock = useMethod(isUserBlocked ? 'unblockUser' : 'blockUser'); - const toggleBlockUserAction = useMutableCallback(async () => { - try { - await toggleBlock({ rid, blocked: uid }); - dispatchToastMessage({ - type: 'success', - message: t(isUserBlocked ? 'User_is_unblocked' : 'User_is_blocked'), - }); - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - } - }); - const toggleBlockUserOption = useMemo( - () => - roomCanBlock && - uid !== ownUserId && { - label: t(isUserBlocked ? 'Unblock' : 'Block'), - icon: 'ban', - action: toggleBlockUserAction, - }, - [isUserBlocked, ownUserId, roomCanBlock, t, toggleBlockUserAction, uid], - ); - - const muteFn = useMethod(isMuted ? 'unmuteUserInRoom' : 'muteUserInRoom'); - const muteUserOption = useMemo(() => { - const action = () => { - const onConfirm = async () => { - try { - await muteFn({ rid, username: user.username }); - closeModal(); - dispatchToastMessage({ - type: 'success', - message: t(isMuted ? 'User__username__unmuted_in_room__roomName__' : 'User__username__muted_in_room__roomName__', { - username: user.username, - roomName, - }), - }); - } catch (error) { - dispatchToastMessage({ type: 'error', message: error }); - } - }; - - if (isMuted) { - return onConfirm(); - } - - setModal( - , - ); - }; - - return ( - roomCanMute && - userCanMute && { - label: t(isMuted ? 'Unmute_user' : 'Mute_user'), - icon: isMuted ? 'mic' : 'mic-off', - action, - } - ); - }, [closeModal, dispatchToastMessage, isMuted, muteFn, rid, roomCanMute, roomName, setModal, t, user.username, userCanMute]); - - const removeFromTeam = useEndpointActionExperimental('POST', 'teams.removeMember', t('User_has_been_removed_from_team')); - - const removeUserAction = useEndpointActionExperimental('POST', `${endpointPrefix}.kick`, t('User_has_been_removed_from_s', roomName)); - const removeUserOptionAction = useMutableCallback(() => { - if (room.teamMain && room.teamId) { - return setModal( - { - const roomKeys = Object.keys(rooms); - await removeFromTeam({ - teamId: room.teamId, - userId: uid, - ...(roomKeys.length && { rooms: roomKeys }), - }); - closeModal(); - reload && reload(); - }} - />, - ); - } - - setModal( - { - await removeUserAction({ roomId: rid, userId: uid }); - closeModal(); - reload && reload(); - }} - />, - ); - }); - - const removeUserOption = useMemo( - () => - roomCanRemove && - userCanRemove && { - label: {room.teamMain ? t('Remove_from_team') : t('Remove_from_room')}, - icon: 'sign-out', - action: removeUserOptionAction, - }, - [room, roomCanRemove, userCanRemove, removeUserOptionAction, t], - ); - - return useMemo( - () => ({ - ...(openDirectMessageOption && { openDirectMessage: openDirectMessageOption }), - ...(videoCallOption && { video: videoCallOption }), - ...(audioCallOption && { audio: audioCallOption }), - ...(changeOwnerOption && { changeOwner: changeOwnerOption }), - ...(changeLeaderOption && { changeLeader: changeLeaderOption }), - ...(changeModeratorOption && { changeModerator: changeModeratorOption }), - ...(ignoreUserOption && { ignoreUser: ignoreUserOption }), - ...(muteUserOption && { muteUser: muteUserOption }), - ...(removeUserOption && { removeUser: removeUserOption }), - ...(toggleBlockUserOption && { toggleBlock: toggleBlockUserOption }), - }), - [ - audioCallOption, - changeLeaderOption, - changeModeratorOption, - changeOwnerOption, - ignoreUserOption, - muteUserOption, - openDirectMessageOption, - removeUserOption, - videoCallOption, - toggleBlockUserOption, - ], - ); -}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAudioCallAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAudioCallAction.ts new file mode 100644 index 000000000000..db92ce2f6895 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useAudioCallAction.ts @@ -0,0 +1,33 @@ +import { IRoom } from '@rocket.chat/core-typings'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { Action } from '../../../../hooks/useActionSpread'; +import { useWebRTC } from '../../useWebRTC'; + +export const useAudioCallAction = (rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const { shouldAllowCalls, callInProgress, joinCall, startCall } = useWebRTC(rid); + + const audioCallOption = useMemo(() => { + const handleJoinCall = (): void => { + joinCall({ audio: true, video: false }); + }; + + const handleStartCall = (): void => { + startCall({ audio: true, video: false }); + }; + + const action = callInProgress ? handleJoinCall : handleStartCall; + + return shouldAllowCalls + ? { + label: t(callInProgress ? 'Join_audio_call' : 'Start_audio_call'), + icon: 'mic', + action, + } + : undefined; + }, [callInProgress, shouldAllowCalls, t, joinCall, startCall]); + + return audioCallOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts new file mode 100644 index 000000000000..0a0574d6b762 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useBlockUserAction.ts @@ -0,0 +1,51 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useTranslation, useMethod, useToastMessageDispatch, useUserId, useUserSubscription, useUserRoom } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { Action } from '../../../../hooks/useActionSpread'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; + +export const useBlockUserAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const currentSubscription = useUserSubscription(rid); + const ownUserId = useUserId(); + const { _id: uid } = user; + const room = useUserRoom(rid); + + if (!room) { + throw Error('Room not provided'); + } + + const { roomCanBlock } = getRoomDirectives(room); + + const isUserBlocked = currentSubscription?.blocker; + const toggleBlock = useMethod(isUserBlocked ? 'unblockUser' : 'blockUser'); + + const toggleBlockUserAction = useMutableCallback(async () => { + try { + await toggleBlock({ rid, blocked: uid }); + dispatchToastMessage({ + type: 'success', + message: t(isUserBlocked ? 'User_is_unblocked' : 'User_is_blocked'), + }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }); + + const toggleBlockUserOption = useMemo( + () => + roomCanBlock && uid !== ownUserId + ? { + label: t(isUserBlocked ? 'Unblock' : 'Block'), + icon: 'ban', + action: toggleBlockUserAction, + } + : undefined, + [isUserBlocked, ownUserId, roomCanBlock, t, toggleBlockUserAction, uid], + ); + + return toggleBlockUserOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts new file mode 100644 index 000000000000..dddfc28397e3 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeLeaderAction.ts @@ -0,0 +1,53 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { escapeHTML } from '@rocket.chat/string-helpers'; +import { useTranslation, usePermission, useUserRoom } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { useEndpointActionExperimental } from '../../../../../hooks/useEndpointActionExperimental'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; +import { Action } from '../../../../hooks/useActionSpread'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; +import { useUserHasRoomRole } from '../../useUserHasRoomRole'; + +// TODO: Remove endpoint concatenation +export const useChangeLeaderAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const { _id: uid } = user; + const userCanSetLeader = usePermission('set-leader', rid); + + if (!room) { + throw Error('Room not provided'); + } + + const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels'; + const { roomCanSetLeader } = getRoomDirectives(room); + const isLeader = useUserHasRoomRole(uid, rid, 'leader'); + const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room)); + + const changeLeaderEndpoint = isLeader ? 'removeLeader' : 'addLeader'; + const changeLeaderMessage = isLeader + ? 'User__username__removed_from__room_name__leaders' + : 'User__username__is_now_a_leader_of__room_name_'; + const changeLeader = useEndpointActionExperimental( + 'POST', + `${endpointPrefix}.${changeLeaderEndpoint}`, + // eslint-disable-next-line @typescript-eslint/camelcase + t(changeLeaderMessage, { username: user.username, room_name: roomName }), + ); + const changeLeaderAction = useMutableCallback(() => changeLeader({ roomId: rid, userId: uid })); + const changeLeaderOption = useMemo( + () => + roomCanSetLeader && userCanSetLeader + ? { + label: t(isLeader ? 'Remove_as_leader' : 'Set_as_leader'), + icon: 'shield-alt', + action: changeLeaderAction, + } + : undefined, + [isLeader, roomCanSetLeader, t, userCanSetLeader, changeLeaderAction], + ); + + return changeLeaderOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.ts new file mode 100644 index 000000000000..0b32928789aa --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeModeratorAction.ts @@ -0,0 +1,54 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { escapeHTML } from '@rocket.chat/string-helpers'; +import { useTranslation, usePermission, useUserRoom } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { useEndpointActionExperimental } from '../../../../../hooks/useEndpointActionExperimental'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; +import { Action } from '../../../../hooks/useActionSpread'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; +import { useUserHasRoomRole } from '../../useUserHasRoomRole'; + +// TODO: Remove endpoint concatenation +export const useChangeModeratorAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const { _id: uid } = user; + + const userCanSetModerator = usePermission('set-moderator', rid); + const isModerator = useUserHasRoomRole(uid, rid, 'moderator'); + + if (!room) { + throw Error('Room not provided'); + } + + const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels'; + const { roomCanSetModerator } = getRoomDirectives(room); + const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room)); + + 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 = useEndpointActionExperimental( + 'POST', + `${endpointPrefix}.${changeModeratorEndpoint}`, + // eslint-disable-next-line @typescript-eslint/camelcase + t(changeModeratorMessage, { username: user.username, room_name: roomName }), + ); + const changeModeratorAction = useMutableCallback(() => changeModerator({ roomId: rid, userId: uid })); + const changeModeratorOption = useMemo( + () => + roomCanSetModerator && userCanSetModerator + ? { + label: t(isModerator ? 'Remove_as_moderator' : 'Set_as_moderator'), + icon: 'shield-blank', + action: changeModeratorAction, + } + : undefined, + [changeModeratorAction, isModerator, roomCanSetModerator, t, userCanSetModerator], + ); + + return changeModeratorOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx new file mode 100644 index 000000000000..4e027c0c150d --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useChangeOwnerAction.tsx @@ -0,0 +1,53 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { escapeHTML } from '@rocket.chat/string-helpers'; +import { useTranslation, usePermission, useUserRoom } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { useEndpointActionExperimental } from '../../../../../hooks/useEndpointActionExperimental'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; +import { Action } from '../../../../hooks/useActionSpread'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; +import { useUserHasRoomRole } from '../../useUserHasRoomRole'; + +// TODO: Remove endpoint concatenation +export const useChangeOwnerAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const { _id: uid } = user; + const userCanSetOwner = usePermission('set-owner', rid); + const isOwner = useUserHasRoomRole(uid, rid, 'owner'); + + if (!room) { + throw Error('Room not provided'); + } + + const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels'; + const { roomCanSetOwner } = getRoomDirectives(room); + const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room)); + + const changeOwnerEndpoint = isOwner ? 'removeOwner' : 'addOwner'; + const changeOwnerMessage = isOwner ? 'User__username__removed_from__room_name__owners' : 'User__username__is_now_a_owner_of__room_name_'; + + const changeOwner = useEndpointActionExperimental( + 'POST', + `${endpointPrefix}.${changeOwnerEndpoint}`, + // eslint-disable-next-line @typescript-eslint/camelcase + t(changeOwnerMessage, { username: user.username, room_name: roomName }), + ); + + const changeOwnerAction = useMutableCallback(async () => changeOwner({ roomId: rid, userId: uid })); + const changeOwnerOption = useMemo( + () => + roomCanSetOwner && userCanSetOwner + ? { + label: t(isOwner ? 'Remove_as_owner' : 'Set_as_owner'), + icon: 'shield-check', + action: changeOwnerAction, + } + : undefined, + [changeOwnerAction, roomCanSetOwner, userCanSetOwner, isOwner, t], + ); + + return changeOwnerOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useDirectMessageAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useDirectMessageAction.ts new file mode 100644 index 000000000000..07f07a78d328 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useDirectMessageAction.ts @@ -0,0 +1,54 @@ +import { IRoom, IUser, ISubscription } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useTranslation, usePermission, useRoute, useUserSubscription, useUserSubscriptionByName } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { Action } from '../../../../hooks/useActionSpread'; + +const getShouldOpenDirectMessage = ( + currentSubscription?: ISubscription, + usernameSubscription?: ISubscription, + canOpenDirectMessage?: boolean, + username?: IUser['username'], +): boolean => { + const canOpenDm = canOpenDirectMessage || usernameSubscription; + const directMessageIsNotAlreadyOpen = currentSubscription && currentSubscription.name !== username; + return (canOpenDm && directMessageIsNotAlreadyOpen) ?? false; +}; + +export const useDirectMessageAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const usernameSubscription = useUserSubscriptionByName(user.username ?? ''); + const currentSubscription = useUserSubscription(rid); + const canOpenDirectMessage = usePermission('create-d'); + const directRoute = useRoute('direct'); + + const shouldOpenDirectMessage = getShouldOpenDirectMessage( + currentSubscription, + usernameSubscription, + canOpenDirectMessage, + user.username, + ); + + const openDirectMessage = useMutableCallback( + () => + user.username && + directRoute.push({ + rid: user.username, + }), + ); + + const openDirectMessageOption = useMemo( + () => + shouldOpenDirectMessage + ? { + label: t('Direct_Message'), + icon: 'balloon', + action: openDirectMessage, + } + : undefined, + [openDirectMessage, shouldOpenDirectMessage, t], + ); + + return openDirectMessageOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts new file mode 100644 index 000000000000..8147c461ef8d --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useIgnoreUserAction.ts @@ -0,0 +1,52 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useTranslation, useMethod, useUserSubscription, useUserRoom, useUserId, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { Action } from '../../../../hooks/useActionSpread'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; + +export const useIgnoreUserAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const { _id: uid } = user; + const ownUserId = useUserId(); + const dispatchToastMessage = useToastMessageDispatch(); + const currentSubscription = useUserSubscription(rid); + const ignoreUser = useMethod('ignoreUser'); + + const isIgnored = currentSubscription?.ignored && currentSubscription.ignored.indexOf(uid) > -1; + + if (!room) { + throw Error('Room not provided'); + } + + const { roomCanIgnore } = getRoomDirectives(room); + + const ignoreUserAction = useMutableCallback(async () => { + try { + await ignoreUser({ rid, userId: uid, ignore: !isIgnored }); + if (isIgnored) { + dispatchToastMessage({ type: 'success', message: t('User_has_been_unignored') }); + } else { + dispatchToastMessage({ type: 'success', message: t('User_has_been_ignored') }); + } + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }); + + const ignoreUserOption = useMemo( + () => + roomCanIgnore && uid !== ownUserId + ? { + label: t(isIgnored ? 'Unignore' : 'Ignore'), + icon: 'ban', + action: ignoreUserAction, + } + : undefined, + [ignoreUserAction, isIgnored, ownUserId, roomCanIgnore, t, uid], + ); + + return ignoreUserOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx new file mode 100644 index 000000000000..b3fc6fe926b4 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useMuteUserAction.tsx @@ -0,0 +1,119 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { escapeHTML } from '@rocket.chat/string-helpers'; +import { + useAllPermissions, + usePermission, + useSetModal, + useMethod, + useToastMessageDispatch, + useTranslation, + useUserRoom, +} from '@rocket.chat/ui-contexts'; +import React, { useMemo } from 'react'; + +import GenericModal from '../../../../../components/GenericModal'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; +import { Action } from '../../../../hooks/useActionSpread'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; + +const getUserIsMuted = ( + user: Pick, + room: IRoom | undefined, + userCanPostReadonly: boolean, +): boolean | undefined => { + if (room?.ro) { + if (Array.isArray(room.unmuted) && room.unmuted.indexOf(user.username ?? '') !== -1) { + return false; + } + + if (userCanPostReadonly) { + return Array.isArray(room.muted) && room.muted.indexOf(user.username ?? '') !== -1; + } + + return true; + } + + return room && Array.isArray(room.muted) && room.muted.indexOf(user.username ?? '') > -1; +}; + +export const useMuteUserAction = (user: Pick, rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const userCanMute = usePermission('mute-user', rid); + const dispatchToastMessage = useToastMessageDispatch(); + const setModal = useSetModal(); + const closeModal = useMutableCallback(() => setModal(null)); + const otherUserCanPostReadonly = useAllPermissions( + useMemo(() => ['post-readonly'], []), + rid, + ); + + const isMuted = getUserIsMuted(user, room, otherUserCanPostReadonly); + const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room)); + + if (!room) { + throw Error('Room not provided'); + } + + const { roomCanMute } = getRoomDirectives(room); + + const mutedMessage = isMuted ? 'User__username__unmuted_in_room__roomName__' : 'User__username__muted_in_room__roomName__'; + + const muteUser = useMethod(isMuted ? 'unmuteUserInRoom' : 'muteUserInRoom'); + + const muteUserOption = useMemo(() => { + const action = (): Promise | void => { + const onConfirm = async (): Promise => { + try { + await muteUser({ rid, username: user.username }); + + return dispatchToastMessage({ + type: 'success', + message: t(mutedMessage, { + username: user.username, + roomName, + }), + }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + closeModal(); + } + }; + + if (isMuted) { + return onConfirm(); + } + + return setModal( + + {t('The_user_wont_be_able_to_type_in_s', roomName)} + , + ); + }; + + return roomCanMute && userCanMute + ? { + label: t(isMuted ? 'Unmute_user' : 'Mute_user'), + icon: isMuted ? 'mic' : 'mic-off', + action, + } + : undefined; + }, [ + closeModal, + mutedMessage, + dispatchToastMessage, + isMuted, + muteUser, + rid, + roomCanMute, + roomName, + setModal, + t, + user.username, + userCanMute, + ]); + + return muteUserOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx new file mode 100644 index 000000000000..6bc41e267db0 --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRemoveUserAction.tsx @@ -0,0 +1,92 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { Box, Icon } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { escapeHTML } from '@rocket.chat/string-helpers'; +import { usePermission, useSetModal, useTranslation, useUserRoom } from '@rocket.chat/ui-contexts'; +import React, { useMemo } from 'react'; + +import GenericModal from '../../../../../components/GenericModal'; +import { useEndpointActionExperimental } from '../../../../../hooks/useEndpointActionExperimental'; +import { roomCoordinator } from '../../../../../lib/rooms/roomCoordinator'; +import { Action } from '../../../../hooks/useActionSpread'; +import RemoveUsersModal from '../../../../teams/contextualBar/members/RemoveUsersModal'; +import { getRoomDirectives } from '../../../lib/getRoomDirectives'; + +// TODO: Remove endpoint concatenation +export const useRemoveUserAction = (user: Pick, rid: IRoom['_id'], reload?: () => void): Action | undefined => { + const t = useTranslation(); + const room = useUserRoom(rid); + const { _id: uid } = user; + + const userCanRemove = usePermission('remove-user', rid); + const setModal = useSetModal(); + const closeModal = useMutableCallback(() => setModal(null)); + const roomName = room?.t && escapeHTML(roomCoordinator.getRoomName(room.t, room)); + + if (!room) { + throw Error('Room not provided'); + } + + const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels'; + const { roomCanRemove } = getRoomDirectives(room); + + const removeFromTeam = useEndpointActionExperimental('POST', '/v1/teams.removeMember', t('User_has_been_removed_from_team')); + const removeFromRoom = useEndpointActionExperimental('POST', `${endpointPrefix}.kick`, t('User_has_been_removed_from_s', roomName)); + + const removeUserOptionAction = useMutableCallback(() => { + const handleRemoveFromTeam = async (rooms: IRoom[]): Promise => { + if (room.teamId) { + const roomKeys = Object.keys(rooms); + await removeFromTeam({ + teamId: room.teamId, + userId: uid, + ...(roomKeys.length && { rooms: roomKeys }), + }); + closeModal(); + reload?.(); + } + }; + + const handleRemoveFromRoom = async (rid: IRoom['_id'], uid: IUser['_id']): Promise => { + await removeFromRoom({ roomId: rid, userId: uid }); + closeModal(); + reload?.(); + }; + + if (room.teamMain && room.teamId) { + return setModal( + , + ); + } + + setModal( + => handleRemoveFromRoom(rid, uid)} + > + {t('The_user_will_be_removed_from_s', roomName)} + , + ); + }); + + const removeUserOption = useMemo( + () => + roomCanRemove && userCanRemove + ? { + label: ( + + + {room?.teamMain ? t('Remove_from_team') : t('Remove_from_room')} + + ), + action: removeUserOptionAction, + } + : undefined, + [room, roomCanRemove, userCanRemove, removeUserOptionAction, t], + ); + + return removeUserOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useVideoCallAction.tsx b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useVideoCallAction.tsx new file mode 100644 index 000000000000..6a3fafcd8dbe --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useVideoCallAction.tsx @@ -0,0 +1,33 @@ +import { IRoom } from '@rocket.chat/core-typings'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { Action } from '../../../../hooks/useActionSpread'; +import { useWebRTC } from '../../useWebRTC'; + +export const useVideoCallAction = (rid: IRoom['_id']): Action | undefined => { + const t = useTranslation(); + const { shouldAllowCalls, callInProgress, joinCall, startCall } = useWebRTC(rid); + + const videoCallOption = useMemo(() => { + const handleJoinCall = (): void => { + joinCall({ audio: true, video: true }); + }; + + const handleStartCall = (): void => { + startCall({ audio: true, video: true }); + }; + + const action = callInProgress ? handleJoinCall : handleStartCall; + + return shouldAllowCalls + ? { + label: t(callInProgress ? 'Join_video_call' : 'Start_video_call'), + icon: 'video', + action, + } + : undefined; + }, [callInProgress, shouldAllowCalls, t, joinCall, startCall]); + + return videoCallOption; +}; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/index.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/index.ts new file mode 100644 index 000000000000..44ce5ef6c2da --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/index.ts @@ -0,0 +1 @@ +export { useUserInfoActions } from './useUserInfoActions'; diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts new file mode 100644 index 000000000000..f54586c0a18e --- /dev/null +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/useUserInfoActions.ts @@ -0,0 +1,60 @@ +import { IRoom, IUser } from '@rocket.chat/core-typings'; +import { useMemo } from 'react'; + +import { Action } from '../../../hooks/useActionSpread'; +import { useAudioCallAction } from './actions/useAudioCallAction'; +import { useBlockUserAction } from './actions/useBlockUserAction'; +import { useChangeLeaderAction } from './actions/useChangeLeaderAction'; +import { useChangeModeratorAction } from './actions/useChangeModeratorAction'; +import { useChangeOwnerAction } from './actions/useChangeOwnerAction'; +import { useDirectMessageAction } from './actions/useDirectMessageAction'; +import { useIgnoreUserAction } from './actions/useIgnoreUserAction'; +import { useMuteUserAction } from './actions/useMuteUserAction'; +import { useRemoveUserAction } from './actions/useRemoveUserAction'; +import { useVideoCallAction } from './actions/useVideoCallAction'; + +export const useUserInfoActions = ( + user: Pick, + rid: IRoom['_id'], + reload?: () => void, +): { + [key: string]: Action; +} => { + const audioCallOption = useAudioCallAction(rid); + const blockUserOption = useBlockUserAction(user, rid); + const changeLeaderOption = useChangeLeaderAction(user, rid); + const changeModeratorOption = useChangeModeratorAction(user, rid); + const changeOwnerOption = useChangeOwnerAction(user, rid); + const openDirectMessageOption = useDirectMessageAction(user, rid); + const ignoreUserOption = useIgnoreUserAction(user, rid); + const muteUserOption = useMuteUserAction(user, rid); + const removeUserOption = useRemoveUserAction(user, rid, reload); + const videoCallOption = useVideoCallAction(rid); + + return useMemo( + () => ({ + ...(openDirectMessageOption && { openDirectMessage: openDirectMessageOption }), + ...(videoCallOption && { video: videoCallOption }), + ...(audioCallOption && { audio: audioCallOption }), + ...(changeOwnerOption && { changeOwner: changeOwnerOption }), + ...(changeLeaderOption && { changeLeader: changeLeaderOption }), + ...(changeModeratorOption && { changeModerator: changeModeratorOption }), + ...(ignoreUserOption && { ignoreUser: ignoreUserOption }), + ...(muteUserOption && { muteUser: muteUserOption }), + ...(blockUserOption && { toggleBlock: blockUserOption }), + ...(removeUserOption && { removeUser: removeUserOption }), + }), + [ + audioCallOption, + changeLeaderOption, + changeModeratorOption, + changeOwnerOption, + ignoreUserOption, + muteUserOption, + openDirectMessageOption, + removeUserOption, + videoCallOption, + blockUserOption, + ], + ); +}; diff --git a/apps/meteor/client/views/room/lib/getRoomDirectives.ts b/apps/meteor/client/views/room/lib/getRoomDirectives.ts new file mode 100644 index 000000000000..2f5602f02d2b --- /dev/null +++ b/apps/meteor/client/views/room/lib/getRoomDirectives.ts @@ -0,0 +1,33 @@ +import { IRoom } from '@rocket.chat/core-typings'; + +import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; +import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; + +type getRoomDirectiesType = { + roomCanSetOwner: boolean; + roomCanSetLeader: boolean; + roomCanSetModerator: boolean; + roomCanIgnore: boolean; + roomCanBlock: boolean; + roomCanMute: boolean; + roomCanRemove: boolean; +}; + +export const getRoomDirectives = (room: IRoom): getRoomDirectiesType => { + const roomDirectives = room?.t && roomCoordinator.getRoomDirectives(room.t); + + const [roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove] = [ + ...((roomDirectives && [ + roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_OWNER), + roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_LEADER), + roomDirectives.allowMemberAction(room, RoomMemberActions.SET_AS_MODERATOR), + roomDirectives.allowMemberAction(room, RoomMemberActions.IGNORE), + roomDirectives.allowMemberAction(room, RoomMemberActions.BLOCK), + roomDirectives.allowMemberAction(room, RoomMemberActions.MUTE), + roomDirectives.allowMemberAction(room, RoomMemberActions.REMOVE_USER), + ]) ?? + []), + ]; + + return { roomCanSetOwner, roomCanSetLeader, roomCanSetModerator, roomCanIgnore, roomCanBlock, roomCanMute, roomCanRemove }; +}; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 98cb59cc79fa..7396656497f0 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -670,6 +670,7 @@ "Better": "Better", "Bio": "Bio", "Bio_Placeholder": "Bio Placeholder", + "Block": "Block", "Block_Multiple_Failed_Logins_Attempts_Until_Block_By_Ip": "How many failed attempts until block by IP", "Block_Multiple_Failed_Logins_Attempts_Until_Block_by_User": "How many failed attempts until block by User", "Block_Multiple_Failed_Logins_By_Ip": "Block failed login attempts by IP", @@ -4547,6 +4548,7 @@ "unarchive-room_description": "Permission to unarchive channels", "Unassigned": "Unassigned", "Unavailable": "Unavailable", + "Unblock": "Unblock", "Unblock_User": "Unblock User", "Uncheck_All": "Uncheck All", "Uncollapse": "Uncollapse", diff --git a/packages/core-typings/src/ISubscription.ts b/packages/core-typings/src/ISubscription.ts index 3dcdccd2fa95..7b140a775e3c 100644 --- a/packages/core-typings/src/ISubscription.ts +++ b/packages/core-typings/src/ISubscription.ts @@ -56,7 +56,7 @@ export interface ISubscription extends IRocketChatRecord { autoTranslateLanguage?: string; disableNotifications?: boolean; muteGroupMentions?: boolean; - ignored?: unknown; + ignored?: IUser['_id'][]; department?: unknown; diff --git a/packages/rest-typings/src/v1/groups.ts b/packages/rest-typings/src/v1/groups.ts index 7fc73cbe7440..9ab08be043d4 100644 --- a/packages/rest-typings/src/v1/groups.ts +++ b/packages/rest-typings/src/v1/groups.ts @@ -381,4 +381,22 @@ export type GroupsEndpoints = { messages: IMessage[]; }>; }; + '/v1/groups.addModerator': { + POST: (params: { roomId: string; userId: string }) => {}; + }; + '/v1/groups.removeModerator': { + POST: (params: { roomId: string; userId: string }) => {}; + }; + '/v1/groups.addOwner': { + POST: (params: { roomId: string; userId: string }) => {}; + }; + '/v1/groups.removeOwner': { + POST: (params: { roomId: string; userId: string }) => {}; + }; + '/v1/groups.addLeader': { + POST: (params: { roomId: string; userId: string }) => {}; + }; + '/v1/groups.removeLeader': { + POST: (params: { roomId: string; userId: string }) => {}; + }; }; diff --git a/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts b/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts index 8ec78f325c56..716ae5fb1108 100644 --- a/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts +++ b/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts @@ -4,7 +4,7 @@ import { useSubscription } from 'use-subscription'; import { Fields, Sort, UserContext } from '../UserContext'; -export const useUserSubscriptionByName = (name: string, fields: Fields, sort?: Sort): ISubscription | undefined => { +export const useUserSubscriptionByName = (name: string, fields?: Fields, sort?: Sort): ISubscription | undefined => { const { querySubscription } = useContext(UserContext); const subscription = useMemo(() => querySubscription({ name }, fields, sort), [querySubscription, name, fields, sort]); return useSubscription(subscription);