diff --git a/.changeset/add-unhide-channel-toggle.md b/.changeset/add-unhide-channel-toggle.md new file mode 100644 index 0000000000000..dd87b14e4f827 --- /dev/null +++ b/.changeset/add-unhide-channel-toggle.md @@ -0,0 +1,13 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/i18n": minor +--- + +Add dynamic Hide/Unhide toggle for channels + +- Add useUnhideRoomAction hook to unhide channels +- Update Room Info panel to show Hide/Unhide based on channel state +- Update sidebar context menu with dynamic Hide/Unhide option +- Update Teams info panel with Hide/Unhide toggle +- Add translation keys for Unhide functionality +- Fix error toast when clicking Hide on already-hidden channel diff --git a/apps/meteor/client/hooks/useRoomMenuActions.ts b/apps/meteor/client/hooks/useRoomMenuActions.ts index ec89eef7d18ba..70c15ca563443 100644 --- a/apps/meteor/client/hooks/useRoomMenuActions.ts +++ b/apps/meteor/client/hooks/useRoomMenuActions.ts @@ -8,6 +8,7 @@ import { useLeaveRoomAction } from './menuActions/useLeaveRoom'; import { useToggleFavoriteAction } from './menuActions/useToggleFavoriteAction'; import { useToggleReadAction } from './menuActions/useToggleReadAction'; import { useHideRoomAction } from './useHideRoomAction'; +import { useUnhideRoomAction } from './useUnhideRoomAction'; import { useOmnichannelPrioritiesMenu } from '../views/omnichannel/hooks/useOmnichannelPrioritiesMenu'; type RoomMenuActionsProps = { @@ -33,6 +34,7 @@ export const useRoomMenuActions = ({ const subscription = useUserSubscription(rid); const isFavorite = Boolean(subscription?.f); + const isRoomHidden = subscription?.open === false; const canLeaveChannel = usePermission('leave-c'); const canLeavePrivate = usePermission('leave-p'); const canFavorite = useSetting('Favorite_Rooms') as boolean; @@ -48,6 +50,7 @@ export const useRoomMenuActions = ({ })(); const handleHide = useHideRoomAction({ rid, type, name }, { redirect: false }); + const handleUnhide = useUnhideRoomAction({ rid, type }); const handleToggleFavorite = useToggleFavoriteAction({ rid, isFavorite }); const handleToggleRead = useToggleReadAction({ rid, isUnread, subscription }); const handleLeave = useLeaveRoomAction({ rid, type, name, roomOpen }); @@ -60,10 +63,10 @@ export const useRoomMenuActions = ({ !hideDefaultOptions ? ([ !isOmnichannelRoom && { - id: 'hideRoom', - icon: 'eye-off', - content: t('Hide'), - onClick: handleHide, + id: isRoomHidden ? 'unhideRoom' : 'hideRoom', + icon: isRoomHidden ? 'eye' : 'eye-off', + content: isRoomHidden ? t('Unhide') : t('Hide'), + onClick: isRoomHidden ? handleUnhide : handleHide, }, { id: 'toggleRead', @@ -89,6 +92,8 @@ export const useRoomMenuActions = ({ hideDefaultOptions, t, handleHide, + handleUnhide, + isRoomHidden, isUnread, handleToggleRead, canFavorite, diff --git a/apps/meteor/client/hooks/useUnhideRoomAction.tsx b/apps/meteor/client/hooks/useUnhideRoomAction.tsx new file mode 100644 index 0000000000000..582d21fbef5e2 --- /dev/null +++ b/apps/meteor/client/hooks/useUnhideRoomAction.tsx @@ -0,0 +1,46 @@ +import type { RoomType } from '@rocket.chat/core-typings'; +import { useEffectEvent } from '@rocket.chat/fuselage-hooks'; +import { useEndpoint, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts'; +import { useMutation } from '@tanstack/react-query'; +import { useTranslation } from 'react-i18next'; + +import { updateSubscription } from '../lib/mutationEffects/updateSubscription'; + +type UnhideRoomProps = { + rid: string; + type: RoomType; +}; + +export const useUnhideRoomAction = ({ rid: roomId, type }: UnhideRoomProps) => { + const { t } = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const userId = useUserId(); + + const openRoomEndpoint = useEndpoint('POST', '/v1/rooms.open'); + + const unhideRoom = useMutation({ + mutationFn: () => openRoomEndpoint({ roomId }), + onMutate: async () => { + if (userId) { + return updateSubscription(roomId, userId, { open: true }); + } + }, + onSuccess: () => { + dispatchToastMessage({ type: 'success', message: t('Room_unhidden_successfully') }); + }, + onError: async (error, _, rollbackDocument) => { + dispatchToastMessage({ type: 'error', message: error }); + + if (userId && rollbackDocument) { + const { open } = rollbackDocument; + updateSubscription(roomId, userId, { open }); + } + }, + }); + + const handleUnhide = useEffectEvent(() => { + unhideRoom.mutate(); + }); + + return handleUnhide; +}; diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/useRoomActions.ts b/apps/meteor/client/views/room/contextualBar/Info/hooks/useRoomActions.ts index 201e0131646b4..c6fbad7f9374b 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/hooks/useRoomActions.ts +++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/useRoomActions.ts @@ -1,4 +1,5 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { useUserSubscription } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -6,6 +7,7 @@ import { useRoomConvertToTeam } from './actions/useRoomConvertToTeam'; import { useRoomLeave } from './actions/useRoomLeave'; import { useRoomMoveToTeam } from './actions/useRoomMoveToTeam'; import { useHideRoomAction } from '../../../../../hooks/useHideRoomAction'; +import { useUnhideRoomAction } from '../../../../../hooks/useUnhideRoomAction'; import { useDeleteRoom } from '../../../../hooks/roomActions/useDeleteRoom'; type UseRoomActionsOptions = { @@ -18,21 +20,24 @@ export const useRoomActions = (room: IRoom, options: UseRoomActionsOptions) => { const { onClickEnterRoom, onClickEdit, resetState } = options; const { t } = useTranslation(); + const subscription = useUserSubscription(room._id); + const isRoomHidden = subscription?.open === false; const handleLeave = useRoomLeave(room); const { handleDelete, canDeleteRoom } = useDeleteRoom(room, { reload: resetState }); const handleMoveToTeam = useRoomMoveToTeam(room); const handleConvertToTeam = useRoomConvertToTeam(room); const handleHide = useHideRoomAction({ rid: room._id, type: room.t, name: room.name ?? '' }); + const handleUnhide = useUnhideRoomAction({ rid: room._id, type: room.t }); return useMemo(() => { const memoizedActions = { items: [ { - id: 'hide', - content: t('Hide'), - icon: 'eye-off' as const, - onClick: handleHide, + id: isRoomHidden ? 'unhide' : 'hide', + content: isRoomHidden ? t('Unhide') : t('Hide'), + icon: isRoomHidden ? ('eye' as const) : ('eye-off' as const), + onClick: isRoomHidden ? handleUnhide : handleHide, }, ...(onClickEnterRoom @@ -100,5 +105,17 @@ export const useRoomActions = (room: IRoom, options: UseRoomActionsOptions) => { }; return memoizedActions; - }, [canDeleteRoom, handleConvertToTeam, handleDelete, handleHide, handleLeave, handleMoveToTeam, onClickEdit, onClickEnterRoom, t]); + }, [ + canDeleteRoom, + handleConvertToTeam, + handleDelete, + handleHide, + handleUnhide, + handleLeave, + handleMoveToTeam, + isRoomHidden, + onClickEdit, + onClickEnterRoom, + t, + ]); }; diff --git a/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts b/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts index a7a136153587f..df1da34a9a9ac 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts +++ b/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts @@ -1,10 +1,12 @@ import type { IRoom } from '@rocket.chat/core-typings'; +import { useUserSubscription } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useConvertToChannel } from './useConvertToChannel'; import { useLeaveTeam } from './useLeaveTeam'; import { useHideRoomAction } from '../../../../hooks/useHideRoomAction'; +import { useUnhideRoomAction } from '../../../../hooks/useUnhideRoomAction'; import { useDeleteRoom } from '../../../hooks/roomActions/useDeleteRoom'; type GenProps = { @@ -13,7 +15,10 @@ type GenProps = { export const useTeamActions = (room: IRoom, { onClickEdit }: GenProps) => { const { t } = useTranslation(); + const subscription = useUserSubscription(room._id); + const isRoomHidden = subscription?.open === false; const hideTeam = useHideRoomAction({ rid: room._id, type: room.t, name: room.name ?? '' }); + const unhideTeam = useUnhideRoomAction({ rid: room._id, type: room.t }); const convertToChannel = useConvertToChannel(room); const { handleDelete, canDeleteRoom } = useDeleteRoom(room); const leaveTeam = useLeaveTeam(room); @@ -22,10 +27,10 @@ export const useTeamActions = (room: IRoom, { onClickEdit }: GenProps) => { () => ({ items: [ { - id: 'hide', - content: t('Hide'), - icon: 'eye-off' as const, - onClick: hideTeam, + id: isRoomHidden ? 'unhide' : 'hide', + content: isRoomHidden ? t('Unhide') : t('Hide'), + icon: isRoomHidden ? ('eye' as const) : ('eye-off' as const), + onClick: isRoomHidden ? unhideTeam : hideTeam, }, ...(onClickEdit ? [ @@ -70,6 +75,6 @@ export const useTeamActions = (room: IRoom, { onClickEdit }: GenProps) => { : []), ], }), - [t, hideTeam, leaveTeam, onClickEdit, handleDelete, canDeleteRoom, convertToChannel], + [t, hideTeam, unhideTeam, isRoomHidden, leaveTeam, onClickEdit, handleDelete, canDeleteRoom, convertToChannel], ); }; diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 465ee29d3896b..9bac467abd08c 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2498,6 +2498,8 @@ "Hide_video": "Hide video", "High": "High", "High_scalability": "High scalability", + "Unhide": "Unhide", + "Room_unhidden_successfully": "Room unhidden successfully", "Highest": "Highest", "Highlighted_chosen_word": "Highlighted chosen word", "Highlights": "Highlights",