From c2e8f72c838f027969b39fe609bee97a6aa2a4f4 Mon Sep 17 00:00:00 2001 From: Satyansh Singh Date: Sat, 21 Feb 2026 10:47:29 +0000 Subject: [PATCH 1/4] feat: 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 Fixes inconsistent hide behavior and missing UI affordances --- .../meteor/client/hooks/useRoomMenuActions.ts | 13 +++-- .../client/hooks/useUnhideRoomAction.tsx | 53 +++++++++++++++++++ .../Info/hooks/useRoomActions.ts | 27 ++++++++-- .../contextualBar/info/useTeamActions.ts | 16 ++++-- packages/i18n/src/locales/en.i18n.json | 2 + 5 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 apps/meteor/client/hooks/useUnhideRoomAction.tsx 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..ecd670792c5a1 --- /dev/null +++ b/apps/meteor/client/hooks/useUnhideRoomAction.tsx @@ -0,0 +1,53 @@ +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; +}; + +const OPEN_ENDPOINTS_BY_ROOM_TYPE = { + p: '/v1/groups.open', // private + c: '/v1/channels.open', // channel + d: '/v1/im.open', // direct message + l: '/v1/channels.open', // livechat +} as const; + +export const useUnhideRoomAction = ({ rid: roomId, type }: UnhideRoomProps) => { + const { t } = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const userId = useUserId(); + + const openRoomEndpoint = useEndpoint('POST', OPEN_ENDPOINTS_BY_ROOM_TYPE[type]); + + 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..7af6372888ee5 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,11 @@ 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 +28,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 +76,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", From a930d8953c4338f0e997fcce29b765e52093f2b0 Mon Sep 17 00:00:00 2001 From: Satyansh Singh Date: Sat, 21 Feb 2026 11:11:49 +0000 Subject: [PATCH 2/4] chore: Add changeset for unhide toggle feature --- .changeset/add-unhide-channel-toggle.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/add-unhide-channel-toggle.md 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 From 65d7a57782bb75b18625ee6832621f2ac6997fd2 Mon Sep 17 00:00:00 2001 From: Satyansh Singh Date: Sat, 21 Feb 2026 16:47:19 +0530 Subject: [PATCH 3/4] Update apps/meteor/client/hooks/useUnhideRoomAction.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- apps/meteor/client/hooks/useUnhideRoomAction.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/meteor/client/hooks/useUnhideRoomAction.tsx b/apps/meteor/client/hooks/useUnhideRoomAction.tsx index ecd670792c5a1..582d21fbef5e2 100644 --- a/apps/meteor/client/hooks/useUnhideRoomAction.tsx +++ b/apps/meteor/client/hooks/useUnhideRoomAction.tsx @@ -11,19 +11,12 @@ type UnhideRoomProps = { type: RoomType; }; -const OPEN_ENDPOINTS_BY_ROOM_TYPE = { - p: '/v1/groups.open', // private - c: '/v1/channels.open', // channel - d: '/v1/im.open', // direct message - l: '/v1/channels.open', // livechat -} as const; - export const useUnhideRoomAction = ({ rid: roomId, type }: UnhideRoomProps) => { const { t } = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); const userId = useUserId(); - const openRoomEndpoint = useEndpoint('POST', OPEN_ENDPOINTS_BY_ROOM_TYPE[type]); + const openRoomEndpoint = useEndpoint('POST', '/v1/rooms.open'); const unhideRoom = useMutation({ mutationFn: () => openRoomEndpoint({ roomId }), From 12af6c6a94d4e075e29019f988deb676e1c0c31b Mon Sep 17 00:00:00 2001 From: Satyansh Singh Date: Sat, 21 Feb 2026 16:47:47 +0530 Subject: [PATCH 4/4] Update apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../client/views/teams/contextualBar/info/useTeamActions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts b/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts index 7af6372888ee5..df1da34a9a9ac 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts +++ b/apps/meteor/client/views/teams/contextualBar/info/useTeamActions.ts @@ -17,7 +17,6 @@ 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);