From cff23f92392dc783851cf5f9d529e8d7a851f711 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:56:45 +0530 Subject: [PATCH] fix: Disable composer actions without subscription to channel (#41202) Co-authored-by: Douglas Fabris --- .changeset/light-geckos-start.md | 5 +++ .../MessageBoxActionsToolbar.tsx | 24 +++++++------ .../hooks/useCreateDiscussionAction.tsx | 4 +-- .../hooks/useShareLocationAction.tsx | 4 +-- .../hooks/useTimestampAction.tsx | 3 +- .../tests/e2e/page-objects/directory.ts | 15 +++++++- .../tests/e2e/preview-public-channel.spec.ts | 35 +++++++++++-------- 7 files changed, 59 insertions(+), 31 deletions(-) create mode 100644 .changeset/light-geckos-start.md diff --git a/.changeset/light-geckos-start.md b/.changeset/light-geckos-start.md new file mode 100644 index 0000000000000..4730b2b67b0d0 --- /dev/null +++ b/.changeset/light-geckos-start.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Disables more actions on message composer during public channel preview. diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx index 5b9deef2845ec..30dfd370831d4 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/MessageBoxActionsToolbar.tsx @@ -1,11 +1,10 @@ import type { IRoom, IMessage } from '@rocket.chat/core-typings'; -import type { Icon } from '@rocket.chat/fuselage'; import { isTruthy } from '@rocket.chat/tools'; import { GenericMenu, type GenericMenuItemProps } from '@rocket.chat/ui-client'; import { MessageComposerAction, MessageComposerActionsDivider } from '@rocket.chat/ui-composer'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation, useLayoutHiddenActions } from '@rocket.chat/ui-contexts'; -import type { ComponentProps, MouseEvent } from 'react'; +import type { MouseEvent } from 'react'; import { memo } from 'react'; import { useAudioMessageAction } from './hooks/useAudioMessageAction'; @@ -55,13 +54,15 @@ const MessageBoxActionsToolbar = ({ const room = useRoom(); - const audioMessageAction = useAudioMessageAction(!canSend || isRecording || isMicrophoneDenied, isMicrophoneDenied); - const videoMessageAction = useVideoMessageAction(!canSend || isRecording); - const fileUploadAction = useFileUploadAction(!canSend || isRecording || isEditing); - const webdavActions = useWebdavActions(!canSend || isRecording || isEditing); - const createDiscussionAction = useCreateDiscussionAction(room); - const shareLocationAction = useShareLocationAction(room, tmid); - const timestampAction = useTimestampAction(chatContext.composer); + const disableBasicActions = !canSend || isRecording || isEditing; + + const audioMessageAction = useAudioMessageAction(disableBasicActions || isMicrophoneDenied, isMicrophoneDenied); + const videoMessageAction = useVideoMessageAction(disableBasicActions); + const fileUploadAction = useFileUploadAction(disableBasicActions); + const webdavActions = useWebdavActions(disableBasicActions); + const createDiscussionAction = useCreateDiscussionAction(disableBasicActions, room); + const shareLocationAction = useShareLocationAction(disableBasicActions, room, tmid); + const timestampAction = useTimestampAction(!canSend || isRecording, chatContext.composer); const apps = useMessageboxAppsActionButtons(); const { composerToolbox: hiddenActions } = useLayoutHiddenActions(); @@ -112,7 +113,7 @@ const MessageBoxActionsToolbar = ({ .filter((item) => !hiddenActions.includes(item.id)) .map((item) => ({ id: item.id, - icon: item.icon as ComponentProps['name'], + icon: item.icon, content: t(item.label), onClick: (event?: MouseEvent) => item.action({ @@ -122,6 +123,7 @@ const MessageBoxActionsToolbar = ({ chat: chatContext, }), gap: Boolean(!item.icon), + disabled: disableBasicActions, })); return { @@ -147,7 +149,7 @@ const MessageBoxActionsToolbar = ({ {featured.map((action) => action && renderAction(action))} { +export const useCreateDiscussionAction = (disabled: boolean, room?: IRoom): GenericMenuItemProps => { const t = useTranslation(); const setModal = useSetModal(); @@ -28,7 +28,7 @@ export const useCreateDiscussionAction = (room?: IRoom): GenericMenuItemProps => id: 'create-discussion', content: t('Discussion'), icon: 'discussion', - disabled: !allowDiscussion, + disabled: !allowDiscussion || disabled, onClick: handleCreateDiscussion, }; }; diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useShareLocationAction.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useShareLocationAction.tsx index 71b0c35ce516c..34d20abb6ca9c 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useShareLocationAction.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useShareLocationAction.tsx @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'; import ShareLocationModal from '../../../../ShareLocation/ShareLocationModal'; -export const useShareLocationAction = (room?: IRoom, tmid?: IMessage['tmid']): GenericMenuItemProps => { +export const useShareLocationAction = (disabled: boolean, room?: IRoom, tmid?: IMessage['tmid']): GenericMenuItemProps => { if (!room) { throw new Error('Invalid room'); } @@ -28,6 +28,6 @@ export const useShareLocationAction = (room?: IRoom, tmid?: IMessage['tmid']): G content: t('Location'), icon: 'map-pin', onClick: handleShareLocation, - disabled: !allowGeolocation, + disabled: !allowGeolocation || disabled, }; }; diff --git a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useTimestampAction.tsx b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useTimestampAction.tsx index 07d48d6cba0a8..6a5fa90ebb201 100644 --- a/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useTimestampAction.tsx +++ b/apps/meteor/client/views/room/composer/messageBox/MessageBoxActionsToolbar/hooks/useTimestampAction.tsx @@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'; import { TimestampPickerModal } from '../../../../../../components/message/toolbar/items/actions/Timestamp/TimestampPicker/TimestampPickerModal'; import type { ComposerAPI } from '../../../../../../lib/chats/ChatAPI'; -export const useTimestampAction = (composer: ComposerAPI | undefined): GenericMenuItemProps | undefined => { +export const useTimestampAction = (disabled: boolean, composer: ComposerAPI | undefined): GenericMenuItemProps | undefined => { const setModal = useSetModal(); const { t } = useTranslation(); @@ -22,5 +22,6 @@ export const useTimestampAction = (composer: ComposerAPI | undefined): GenericMe icon: 'clock', content: t('Timestamp'), onClick: handleClick, + disabled, }; }; diff --git a/apps/meteor/tests/e2e/page-objects/directory.ts b/apps/meteor/tests/e2e/page-objects/directory.ts index 8d2c6ca605fd9..21ae3e8ea1c67 100644 --- a/apps/meteor/tests/e2e/page-objects/directory.ts +++ b/apps/meteor/tests/e2e/page-objects/directory.ts @@ -1,4 +1,4 @@ -import type { Page } from '@playwright/test'; +import type { Locator, Page } from '@playwright/test'; export class Directory { public readonly page: Page; @@ -19,4 +19,17 @@ export class Directory { await this.searchChannel(name); await this.getSearchByChannelName(name).click(); } + + async goto() { + await this.page.goto('/directory'); + await this.waitForDirectory(); + } + + private get directoryHeader(): Locator { + return this.page.locator('main').getByRole('heading', { name: 'Directory', exact: true }); + } + + private async waitForDirectory(): Promise { + await this.directoryHeader.waitFor(); + } } diff --git a/apps/meteor/tests/e2e/preview-public-channel.spec.ts b/apps/meteor/tests/e2e/preview-public-channel.spec.ts index 3fa4357174260..a33fd2011423b 100644 --- a/apps/meteor/tests/e2e/preview-public-channel.spec.ts +++ b/apps/meteor/tests/e2e/preview-public-channel.spec.ts @@ -35,15 +35,28 @@ test.describe('Preview public channel', () => { test.describe('User', () => { test.use({ storageState: Users.user1.state }); - test('should let user preview public rooms messages', async ({ page }) => { - await page.goto('/home'); - - await poHomeChannel.navbar.btnDirectory.click(); + test('should let user preview public rooms messages', async () => { + await poDirectory.goto(); await poDirectory.openChannel(targetChannel); await expect(poHomeChannel.content.lastUserMessageBody).toContainText(targetChannelMessage); }); + test('should disable all composer toolbar actions during channel preview', async () => { + await poDirectory.goto(); + await poDirectory.openChannel(targetChannel); + await poHomeChannel.content.waitForChannel(); + + await expect(poHomeChannel.composer.btnJoinRoom).toBeVisible(); + + const actions = await poHomeChannel.composer.allPrimaryActions.all(); + await Promise.all( + actions.map(async (action) => { + await expect(action).toBeDisabled(); + }), + ); + }); + test('should let user view direct rooms', async ({ api, page }) => { await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin'] }] }); await createDirectMessage(api); @@ -56,12 +69,10 @@ test.describe('Preview public channel', () => { await expect(poHomeChannel.composer.inputMessage).toBeEnabled(); }); - test('should not let user role preview public rooms', async ({ api, page }) => { + test('should not let user role preview public rooms', async ({ api }) => { await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin'] }] }); - await page.goto('/home'); - - await poHomeChannel.navbar.btnDirectory.click(); + await poDirectory.goto(); await poDirectory.openChannel(targetChannel); await expect(poHomeChannel.btnJoinChannel).toBeVisible(); @@ -76,9 +87,7 @@ test.describe('Preview public channel', () => { test('should prevent user from join the room', async ({ api, page }) => { await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] }] }); - await page.goto('/home'); - - await poHomeChannel.navbar.btnDirectory.click(); + await poDirectory.goto(); await poDirectory.openChannel(targetChannel); await expect(poHomeChannel.content.lastUserMessageBody).toContainText(targetChannelMessage); @@ -95,9 +104,7 @@ test.describe('Preview public channel', () => { test('should prevent user from join the room without preview permission', async ({ api, page }) => { await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin'] }] }); - await page.goto('/home'); - - await poHomeChannel.navbar.btnDirectory.click(); + await poDirectory.goto(); await poDirectory.openChannel(targetChannel); await expect(poHomeChannel.content.lastUserMessageBody).not.toBeVisible();