diff --git a/e2e-tests/cypress/tests/integration/channels/channel/channel_info_rhs_spec.ts b/e2e-tests/cypress/tests/integration/channels/channel/channel_info_rhs_spec.ts
index b150617a02c..6f345a3e0ca 100644
--- a/e2e-tests/cypress/tests/integration/channels/channel/channel_info_rhs_spec.ts
+++ b/e2e-tests/cypress/tests/integration/channels/channel/channel_info_rhs_spec.ts
@@ -227,6 +227,39 @@ describe('Channel Info RHS', () => {
cy.uiGetRHS().findByText('header for the tests').should('be.visible');
});
});
+ it('should be able to rename channel from About area', () => {
+ // # Create a dedicated channel for renaming to avoid affecting other tests
+ cy.apiCreateChannel(testTeam.id, 'channel-to-rename', 'Channel To Rename', 'O').then(({channel}) => {
+ cy.apiAddUserToChannel(channel.id, admin.id);
+
+ // # Go to the channel
+ cy.visit(`/${testTeam.name}/channels/${channel.name}`);
+
+ // # Open Channel Info RHS
+ cy.get('#channel-info-btn').click();
+
+ // # Click edit on channel name (first Edit in About)
+ cy.uiGetRHS().findAllByLabelText('Edit').first().click({force: true});
+
+ // * Rename Channel modal appears
+ cy.findByRole('heading', {name: /rename channel/i}).should('be.visible');
+
+ // # Fill display name and URL
+ cy.findByPlaceholderText(/enter display name/i).clear().type('Renamed Channel');
+ cy.get('.url-input-button').click();
+ cy.get('.url-input-container input').clear().type('renamed-channel');
+ cy.get('.url-input-container button.url-input-button').click();
+
+ // # Save
+ cy.findByRole('button', {name: /save/i}).click();
+
+ // * URL updated
+ cy.location('pathname').should('include', `/${testTeam.name}/channels/renamed-channel`);
+
+ // * Header shows new name
+ cy.get('#channelHeaderTitle').should('contain', 'Renamed Channel');
+ });
+ });
});
describe('bottom menu', () => {
it('should be able to manage notifications', () => {
@@ -237,11 +270,29 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Notification Preferences"
- cy.uiGetRHS().findByText('Notification Preferences').should('be.visible').click();
+ cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Notification Preferences').scrollIntoView().should('be.visible').click();
// * Ensures the modal is there
cy.get('.ChannelNotificationModal').should('be.visible');
});
+ it('should open Channel Settings from RHS menu', () => {
+ // # Go to test channel
+ cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
+
+ // # Close RHS if it's open, then click on the channel info button
+ cy.get('body').then(($body) => {
+ if ($body.find('#rhsCloseButton').length > 0) {
+ cy.get('#rhsCloseButton').click();
+ }
+ cy.get('#channel-info-btn').should('be.visible').click();
+ });
+
+ // * Channel Settings item is visible in RHS menu
+ cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Channel Settings').scrollIntoView().should('be.visible').click();
+
+ // * Channel Settings modal opens
+ cy.get('.ChannelSettingsModal').should('be.visible');
+ });
it('should be able to view files and come back', () => {
// # Go to test channel
cy.visit(`/${testTeam.name}/channels/${testChannel.name}`);
@@ -250,7 +301,7 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Files"
- cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Files').should('be.visible').click();
+ cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Files').scrollIntoView().should('be.visible').click();
// * Ensure we see the files RHS
cy.uiGetRHS().findByText('No files yet').should('be.visible');
@@ -277,10 +328,10 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Pinned Messages"
- cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Pinned messages').should('be.visible').click();
+ cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Pinned messages').scrollIntoView().should('be.visible').click();
// * Ensure we see the Pinned Post RHS
- cy.uiGetRHS().findByText('Hello channel info rhs spec').should('be.visible');
+ cy.uiGetRHS().findByText('Hello channel info rhs spec').first().should('be.visible');
// # Click the Back Icon
cy.uiGetRHS().get('[aria-label="Back Icon"]').click();
@@ -296,7 +347,7 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Members"
- cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Members').should('be.visible').click();
+ cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Members').scrollIntoView().should('be.visible').click();
// * Ensure we see the members
cy.uiGetRHS().contains('sysadmin').should('be.visible');
@@ -399,7 +450,7 @@ describe('Channel Info RHS', () => {
cy.get('#channel-info-btn').click();
// # Click on "Notification Preferences"
- cy.uiGetRHS().findByText('Notification Preferences').should('be.visible').click();
+ cy.uiGetRHS().findByTestId('channel_info_rhs-menu').findByText('Notification Preferences').scrollIntoView().should('be.visible').click();
// * Ensures the modal is there
cy.get('.ChannelNotificationModal').should('be.visible');
@@ -489,6 +540,6 @@ describe('Channel Info RHS', () => {
function ensureRHSIsOpenOnChannelInfo(testChannel) {
cy.get('#rhsContainer').then((rhsContainer) => {
cy.wrap(rhsContainer).findByText('Info').should('be.visible');
- cy.wrap(rhsContainer).findByText(testChannel.display_name).should('be.visible');
+ cy.wrap(rhsContainer).find('.sidebar--right__title__subtitle').should('contain', testChannel.display_name);
});
}
diff --git a/webapp/channels/src/components/channel_info_rhs/about_area.tsx b/webapp/channels/src/components/channel_info_rhs/about_area.tsx
index b5d5d4a7eec..08af1e86d0b 100644
--- a/webapp/channels/src/components/channel_info_rhs/about_area.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/about_area.tsx
@@ -40,6 +40,7 @@ interface Props {
gmUsers?: UserProfile[];
canEditChannelProperties: boolean;
actions: {
+ editChannelName: () => void;
editChannelPurpose: () => void;
editChannelHeader: () => void;
};
diff --git a/webapp/channels/src/components/channel_info_rhs/about_area_channel.test.tsx b/webapp/channels/src/components/channel_info_rhs/about_area_channel.test.tsx
index 83629fd5f0b..506e32fb358 100644
--- a/webapp/channels/src/components/channel_info_rhs/about_area_channel.test.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/about_area_channel.test.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import type {Channel} from '@mattermost/types/channels';
import type {DeepPartial} from '@mattermost/types/utilities';
-import {renderWithContext, screen} from 'tests/react_testing_utils';
+import {renderWithContext, screen, fireEvent} from 'tests/react_testing_utils';
import type {GlobalState} from 'types/store';
@@ -112,12 +112,15 @@ describe('channel_info_rhs/about_area_channel', () => {
const defaultProps = {
channel: {
id: 'test-c-id',
+ name: 'my-channel',
header: 'my channel header',
purpose: 'my channel purpose',
+ display_name: 'My Channel',
} as Channel,
channelURL: 'https://my-url.mm',
canEditChannelProperties: true,
actions: {
+ editChannelName: jest.fn(),
editChannelPurpose: jest.fn(),
editChannelHeader: jest.fn(),
},
@@ -144,4 +147,25 @@ describe('channel_info_rhs/about_area_channel', () => {
expect(screen.getByText('my channel header')).toBeInTheDocument();
});
+
+ test('should trigger editChannelName when clicking channel display name', () => {
+ const props = {
+ ...defaultProps,
+ actions: {
+ ...defaultProps.actions,
+ editChannelName: jest.fn(),
+ },
+ };
+
+ renderWithContext(
+ ,
+ initialState,
+ );
+
+ const editButtons = screen.getAllByLabelText('Edit');
+ fireEvent.click(editButtons[0]);
+ expect(props.actions.editChannelName).toHaveBeenCalled();
+ });
});
diff --git a/webapp/channels/src/components/channel_info_rhs/about_area_channel.tsx b/webapp/channels/src/components/channel_info_rhs/about_area_channel.tsx
index 194eae5ca35..dd8bcd5b2b6 100644
--- a/webapp/channels/src/components/channel_info_rhs/about_area_channel.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/about_area_channel.tsx
@@ -7,17 +7,36 @@ import styled from 'styled-components';
import type {Channel} from '@mattermost/types/channels';
+import CopyButton from 'components/copy_button';
import Markdown from 'components/markdown';
import EditableArea from './components/editable_area';
import LineLimiter from './components/linelimiter';
-const ChannelId = styled.div`
+const ChannelName = styled.div`
margin-bottom: 12px;
+ font-size: 20px;
+ font-family: Metropolis, sans-serif;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+`;
+
+const ChannelId = styled.div`
+ padding: 4px 0;
+ margin-bottom: 8px;
font-size: 11px;
line-height: 16px;
letter-spacing: 0.02em;
color: rgba(var(--center-channel-color-rgb), 0.75);
+ &:not(:last-child) {
+ margin-bottom: 0px;
+ }
+ .post-code__clipboard {
+ opacity: 0;
+ }
+ &:hover .post-code__clipboard {
+ opacity: 1;
+ }
`;
const ChannelPurpose = styled.div`
@@ -29,23 +48,36 @@ const ChannelPurpose = styled.div`
const ChannelDescriptionHeading = styled.div`
color: rgba(var(--center-channel-color-rgb), 0.75);
- font-size: 12px;
+ font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 16px;
letter-spacing: 0.24px;
text-transform: uppercase;
- padding: 6px 0px;
+ padding: 4px 0px;
`;
const ChannelHeader = styled.div`
margin-bottom: 12px;
`;
+const SmallCopyButton = styled(CopyButton)`
+ i {
+ font-size: 14px;
+ margin-left: 4px;
+ color: rgba(var(--center-channel-color-rgb), 0.64);
+
+ &:hover {
+ color: rgba(var(--center-channel-color-rgb), 0.88);
+ }
+ }
+`;
+
interface Props {
channel: Channel;
canEditChannelProperties: boolean;
actions: {
+ editChannelName: () => void;
editChannelPurpose: () => void;
editChannelHeader: () => void;
};
@@ -56,6 +88,16 @@ const AboutAreaChannel = ({channel, canEditChannelProperties, actions}: Props) =
return (
<>
+
+ {channel.display_name}}
+ onEdit={actions.editChannelName}
+ editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_name', defaultMessage: 'Rename channel'})}
+ emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_name', defaultMessage: 'Rename channel'})}
+ />
+
+
{(channel.purpose || canEditChannelProperties) && (
@@ -74,6 +116,7 @@ const AboutAreaChannel = ({channel, canEditChannelProperties, actions}: Props) =
)}
onEdit={actions.editChannelPurpose}
+ editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_purpose', defaultMessage: 'Edit channel purpose'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_purpose', defaultMessage: 'Add a channel purpose'})}
/>
@@ -97,14 +140,27 @@ const AboutAreaChannel = ({channel, canEditChannelProperties, actions}: Props) =
)}
editable={canEditChannelProperties}
onEdit={actions.editChannelHeader}
+ editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_header', defaultMessage: 'Edit channel header'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_header', defaultMessage: 'Add a channel header'})}
/>
)}
- {formatMessage({id: 'channel_info_rhs.about_area_id', defaultMessage: 'ID:'})} {channel.id}
+ {formatMessage({id: 'channel_info_rhs.about_area_handle', defaultMessage: 'Channel handle:'})} {channel.name}
+
+
+ {formatMessage({id: 'channel_info_rhs.about_area_id', defaultMessage: 'ID:'})} {channel.id}
+
+
+
>
);
};
diff --git a/webapp/channels/src/components/channel_info_rhs/about_area_dm.tsx b/webapp/channels/src/components/channel_info_rhs/about_area_dm.tsx
index 1119d30c373..683172f2c90 100644
--- a/webapp/channels/src/components/channel_info_rhs/about_area_dm.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/about_area_dm.tsx
@@ -128,6 +128,7 @@ const AboutAreaDM = ({channel, dmUser, actions}: Props) => {
)}
editable={true}
onEdit={actions.editChannelHeader}
+ editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_header', defaultMessage: 'Edit channel header'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_header', defaultMessage: 'Add a channel header'})}
/>
diff --git a/webapp/channels/src/components/channel_info_rhs/about_area_gm.tsx b/webapp/channels/src/components/channel_info_rhs/about_area_gm.tsx
index 556a8c1dca2..703cfa54e8c 100644
--- a/webapp/channels/src/components/channel_info_rhs/about_area_gm.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/about_area_gm.tsx
@@ -120,6 +120,7 @@ const AboutAreaGM = ({channel, gmUsers, actions}: Props) => {
)}
editable={true}
onEdit={actions.editChannelHeader}
+ editTooltip={formatMessage({id: 'channel_info_rhs.about_area.edit_channel_header', defaultMessage: 'Edit channel header'})}
emptyLabel={formatMessage({id: 'channel_info_rhs.about_area.add_channel_header', defaultMessage: 'Add a channel header'})}
/>
diff --git a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx
index 79d1a899d61..47e071cee56 100644
--- a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.test.tsx
@@ -8,6 +8,7 @@ import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import {act, renderWithContext} from 'tests/react_testing_utils';
+import {ModalIdentifiers} from 'utils/constants';
import ChannelInfoRHS from './channel_info_rhs';
@@ -48,6 +49,7 @@ describe('channel_info_rhs', () => {
beforeEach(() => {
props = {...OriginalProps};
+ mockAboutArea.mockClear();
});
describe('about area', () => {
@@ -88,4 +90,27 @@ describe('channel_info_rhs', () => {
);
});
});
+
+ test('editChannelName opens Rename Channel modal', () => {
+ props.currentTeam = {name: 'team-1'} as Team;
+ renderWithContext(
+ ,
+ );
+
+ // Invoke the handler passed into the mocked AboutArea
+ const lastArgs = mockAboutArea.mock.calls[mockAboutArea.mock.calls.length - 1][0];
+ lastArgs.actions.editChannelName();
+
+ expect(props.actions.openModal).toHaveBeenCalledWith(
+ expect.objectContaining({
+ modalId: ModalIdentifiers.RENAME_CHANNEL,
+ dialogProps: expect.objectContaining({
+ channel: props.channel,
+ teamName: 'team-1',
+ }),
+ }),
+ );
+ });
});
diff --git a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx
index 903b87b792a..1443b391220 100644
--- a/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/channel_info_rhs.tsx
@@ -2,18 +2,24 @@
// See LICENSE.txt for license information.
import React, {memo} from 'react';
+import {FormattedMessage} from 'react-intl';
import styled from 'styled-components';
import type {Channel, ChannelStats} from '@mattermost/types/channels';
import type {Team} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
+import {Permissions} from 'mattermost-redux/constants';
+
import ChannelInviteModal from 'components/channel_invite_modal';
import ChannelNotificationsModal from 'components/channel_notifications_modal';
import Scrollbars from 'components/common/scrollbars';
import EditChannelHeaderModal from 'components/edit_channel_header_modal';
import EditChannelPurposeModal from 'components/edit_channel_purpose_modal';
import MoreDirectChannels from 'components/more_direct_channels';
+import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate';
+import RenameChannelModal from 'components/rename_channel_modal';
+import UnarchiveChannelModal from 'components/unarchive_channel_modal';
import Constants, {ModalIdentifiers} from 'utils/constants';
import {getSiteURL} from 'utils/url';
@@ -25,12 +31,44 @@ import Header from './header';
import Menu from './menu';
import TopButtons from './top_buttons';
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow-y: auto;
+`;
+
const Divider = styled.div`
width: 88%;
border: 1px solid rgba(var(--center-channel-color-rgb), 0.04);
margin: 0 auto;
`;
+const ArchivedNoticeContainer = styled.div`
+ margin: 24px 24px 0 24px;
+`;
+
+const ArchivedNotice = styled.div`
+ .sectionNoticeIcon {
+ width: 24px;
+ height: 24px;
+ }
+
+ .sectionNoticeTitle {
+ color: rgba(var(--center-channel-color-rgb), 0.88);
+ display: inline;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .sectionNoticeTitle .sectionNoticeButton {
+ margin: 0;
+ padding: 0;
+ display: inline;
+ margin: 0 0 2px 4px;
+ }
+`;
+
export interface DMUser {
user: UserProfile;
display_name: string;
@@ -129,12 +167,24 @@ const ChannelInfoRhs = ({
dialogProps: {channel},
});
+ const editChannelName = () => actions.openModal({
+ modalId: ModalIdentifiers.RENAME_CHANNEL,
+ dialogType: RenameChannelModal,
+ dialogProps: {channel, teamName: currentTeam.name},
+ });
+
const openNotificationSettings = () => actions.openModal({
modalId: ModalIdentifiers.CHANNEL_NOTIFICATIONS,
dialogType: ChannelNotificationsModal,
dialogProps: {channel, currentUser, focusOriginElement: 'channelInfoRHSNotificationSettings'},
});
+ const openUnarchiveChannel = () => actions.openModal({
+ modalId: ModalIdentifiers.UNARCHIVE_CHANNEL,
+ dialogType: UnarchiveChannelModal,
+ dialogProps: {channel},
+ });
+
const gmUsers = channelMembers.filter((user) => {
return user.id !== currentUser.id;
});
@@ -148,45 +198,83 @@ const ChannelInfoRhs = ({
>
-
-
-
-
+
+ {isArchived && (
+
+
+
+
+
+
+ {channel.name !== Constants.DEFAULT_CHANNEL && (
+
+
+
+ )}
+
+
+
+
+ )}
+
+
+
+
+
);
diff --git a/webapp/channels/src/components/channel_info_rhs/components/editable_area.tsx b/webapp/channels/src/components/channel_info_rhs/components/editable_area.tsx
index a2cd1663df8..eae2029d5e7 100644
--- a/webapp/channels/src/components/channel_info_rhs/components/editable_area.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/components/editable_area.tsx
@@ -5,13 +5,17 @@ import React from 'react';
import {useIntl} from 'react-intl';
import styled from 'styled-components';
+import WithTooltip from 'components/with_tooltip';
+
const EditButton = styled.button`
border: 0;
margin: 0px;
padding: 0px;
border-radius: 4px;
- background: rgba(var(--center-channel-color-rgb), 0.04);
- color: rgba(var(--center-channel-color-rgb), 0.75);
+ background: none;
+ position: relative;
+ top: -2px;
+ color: rgba(var(--center-channel-color-rgb), 0.64);
&:hover {
background: rgba(var(--center-channel-color-rgb), 0.08);
color: rgba(var(--center-channel-color-rgb), 0.75);
@@ -47,9 +51,10 @@ interface EditableAreaProps {
emptyLabel: string;
onEdit: () => void;
className?: string;
+ editTooltip?: string;
}
-const EditableAreaBase = ({editable, content, emptyLabel, onEdit, className}: EditableAreaProps) => {
+const EditableAreaBase = ({editable, content, emptyLabel, onEdit, className, editTooltip}: EditableAreaProps) => {
const {formatMessage} = useIntl();
const allowEditArea = editable && content;
@@ -70,12 +75,14 @@ const EditableAreaBase = ({editable, content, emptyLabel, onEdit, className}: Ed
{allowEditArea ? (
-
-
-
+
+
+
+
+
) : ''}
@@ -90,14 +97,13 @@ const EditableArea = styled(EditableAreaBase)`
margin-bottom:0;
}
}
- &:hover {
- &>.EditableArea__edit {
- visibility: visible;
- }
+ &:hover > .EditableArea__edit,
+ &:focus-within > .EditableArea__edit {
+ opacity: 1;
}
&>.EditableArea__edit {
- visibility: hidden;
+ opacity: 0;
width: 24px;
}
`;
diff --git a/webapp/channels/src/components/channel_info_rhs/header.test.tsx b/webapp/channels/src/components/channel_info_rhs/header.test.tsx
index 542770a9785..9e2551e572e 100644
--- a/webapp/channels/src/components/channel_info_rhs/header.test.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/header.test.tsx
@@ -10,16 +10,16 @@ import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import Header from './header';
describe('channel_info_rhs/header', () => {
- test('should the current channel name', () => {
+ test('renders the header title', () => {
renderWithContext(
{}}
/>,
);
+ expect(screen.getByText('Info')).toBeInTheDocument();
expect(screen.getByText('my channel title')).toBeInTheDocument();
});
test('should call onClose when clicking on the close icon', async () => {
@@ -29,7 +29,6 @@ describe('channel_info_rhs/header', () => {
,
);
@@ -45,7 +44,6 @@ describe('channel_info_rhs/header', () => {
,
);
@@ -54,28 +52,4 @@ describe('channel_info_rhs/header', () => {
expect(onClose).toHaveBeenCalled();
});
- test('should have archived icon when channel is archived', () => {
- const {container} = renderWithContext(
- {}}
- />,
- );
-
- expect(container.querySelector('i.icon-archive-outline')).toBeInTheDocument();
- });
- test('should not have archived icon when channel is archived', () => {
- const {container} = renderWithContext(
- {}}
- />,
- );
-
- expect(container.querySelector('i.icon-archive-outline')).not.toBeInTheDocument();
- });
});
diff --git a/webapp/channels/src/components/channel_info_rhs/header.tsx b/webapp/channels/src/components/channel_info_rhs/header.tsx
index 0f09aad73b8..867e326e3a4 100644
--- a/webapp/channels/src/components/channel_info_rhs/header.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/header.tsx
@@ -11,20 +11,15 @@ import WithTooltip from 'components/with_tooltip';
interface Props {
channel: Channel;
- isArchived: boolean;
isMobile: boolean;
onClose: () => void;
}
-const Icon = styled.i`
- font-size:12px;
-`;
-
const HeaderTitle = styled.span`
line-height: 2.4rem;
`;
-const Header = ({channel, isArchived, isMobile, onClose}: Props) => {
+const Header = ({channel, isMobile, onClose}: Props) => {
const {formatMessage} = useIntl();
return (
@@ -50,12 +45,10 @@ const Header = ({channel, isArchived, isMobile, onClose}: Props) => {
defaultMessage='Info'
/>
-
{channel.display_name &&
- {isArchived && ()}
{channel.display_name}
}
diff --git a/webapp/channels/src/components/channel_info_rhs/menu.test.tsx b/webapp/channels/src/components/channel_info_rhs/menu.test.tsx
index 73e1007978d..8867c8e119d 100644
--- a/webapp/channels/src/components/channel_info_rhs/menu.test.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/menu.test.tsx
@@ -5,16 +5,30 @@ import React from 'react';
import type {Channel, ChannelStats} from '@mattermost/types/channels';
+import {openModal} from 'actions/views/modals';
+import {canAccessChannelSettings} from 'selectors/views/channel_settings';
+
import {
act,
renderWithContext,
screen,
userEvent,
+ fireEvent,
} from 'tests/react_testing_utils';
-import Constants from 'utils/constants';
+import Constants, {ModalIdentifiers} from 'utils/constants';
+
+jest.mock('selectors/views/channel_settings', () => ({
+ canAccessChannelSettings: jest.fn(),
+}));
+jest.mock('actions/views/modals', () => ({
+ openModal: jest.fn(() => ({type: 'OPEN_MODAL'})),
+}));
import Menu from './menu';
+const mockedCanAccessChannelSettings = canAccessChannelSettings as unknown as jest.Mock;
+const mockedOpenModal = openModal as unknown as jest.Mock;
+
describe('channel_info_rhs/menu', () => {
const defaultProps = {
channel: {type: Constants.OPEN_CHANNEL} as Channel,
@@ -30,6 +44,8 @@ describe('channel_info_rhs/menu', () => {
};
beforeEach(() => {
+ mockedOpenModal.mockClear();
+ mockedCanAccessChannelSettings.mockReset();
defaultProps.actions = {
openNotificationSettings: jest.fn(),
showChannelFiles: jest.fn(),
@@ -182,4 +198,90 @@ describe('channel_info_rhs/menu', () => {
const membersItem = screen.queryByText('Members');
expect(membersItem).not.toBeInTheDocument();
});
+
+ test('should display Channel Settings and open modal on click (non-DM/GM, not archived, permitted)', async () => {
+ mockedCanAccessChannelSettings.mockReturnValue(true);
+ const props = {...defaultProps};
+
+ renderWithContext(
+ ,
+ );
+
+ await act(async () => {
+ props.actions.getChannelStats();
+ });
+
+ const settingsItem = screen.getByText('Channel Settings');
+ expect(settingsItem).toBeInTheDocument();
+
+ fireEvent.click(settingsItem);
+ expect(mockedOpenModal).toHaveBeenCalledWith(
+ expect.objectContaining({
+ modalId: ModalIdentifiers.CHANNEL_SETTINGS,
+ }),
+ );
+ });
+
+ test('should NOT display Channel Settings in DM', async () => {
+ mockedCanAccessChannelSettings.mockReturnValue(true);
+ const props = {
+ ...defaultProps,
+ channel: {type: Constants.DM_CHANNEL} as Channel,
+ };
+
+ renderWithContext(
+ ,
+ );
+ await act(async () => props.actions.getChannelStats());
+ expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
+ });
+
+ test('should NOT display Channel Settings in GM', async () => {
+ mockedCanAccessChannelSettings.mockReturnValue(true);
+ const props = {
+ ...defaultProps,
+ channel: {type: Constants.GM_CHANNEL} as Channel,
+ };
+
+ renderWithContext(
+ ,
+ );
+ await act(async () => props.actions.getChannelStats());
+ expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
+ });
+
+ test('should NOT display Channel Settings when archived', async () => {
+ mockedCanAccessChannelSettings.mockReturnValue(true);
+ const props = {
+ ...defaultProps,
+ isArchived: true,
+ };
+
+ renderWithContext(
+ ,
+ );
+ await act(async () => props.actions.getChannelStats());
+ expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
+ });
+
+ test('should NOT display Channel Settings without permission', async () => {
+ mockedCanAccessChannelSettings.mockReturnValue(false);
+ const props = {...defaultProps};
+
+ renderWithContext(
+ ,
+ );
+ await act(async () => props.actions.getChannelStats());
+ expect(screen.queryByText('Channel Settings')).not.toBeInTheDocument();
+ });
});
diff --git a/webapp/channels/src/components/channel_info_rhs/menu.tsx b/webapp/channels/src/components/channel_info_rhs/menu.tsx
index be850d3ee02..95ac9a38bdc 100644
--- a/webapp/channels/src/components/channel_info_rhs/menu.tsx
+++ b/webapp/channels/src/components/channel_info_rhs/menu.tsx
@@ -3,13 +3,20 @@
import React, {useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
+import {useDispatch, useSelector} from 'react-redux';
import styled from 'styled-components';
import type {Channel, ChannelStats} from '@mattermost/types/channels';
+import {openModal} from 'actions/views/modals';
+import {canAccessChannelSettings} from 'selectors/views/channel_settings';
+
+import ChannelSettingsModal from 'components/channel_settings_modal/channel_settings_modal';
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
-import {Constants} from 'utils/constants';
+import {Constants, ModalIdentifiers} from 'utils/constants';
+
+import type {GlobalState} from 'types/store';
const MenuContainer = styled.nav`
display: flex;
@@ -116,6 +123,7 @@ interface MenuProps {
export default function Menu(props: MenuProps) {
const {formatMessage} = useIntl();
+ const dispatch = useDispatch();
const {
channel,
channelStats,
@@ -128,7 +136,9 @@ export default function Menu(props: MenuProps) {
const showNotificationPreferences = channel.type !== Constants.DM_CHANNEL && !isArchived;
const showMembers = channel.type !== Constants.DM_CHANNEL;
+ const showChannelSettings = channel.type !== Constants.DM_CHANNEL && channel.type !== Constants.GM_CHANNEL && !isArchived;
const fileCount = channelStats?.files_count >= 0 ? channelStats?.files_count : 0;
+ const canAccessSettings = useSelector((state: GlobalState) => canAccessChannelSettings(state, channel.id));
useEffect(() => {
actions.getChannelStats(channel.id, true).then(() => {
@@ -139,6 +149,20 @@ export default function Menu(props: MenuProps) {
};
}, [channel.id]);
+ const openChannelSettings = () => {
+ dispatch(
+ openModal({
+ modalId: ModalIdentifiers.CHANNEL_SETTINGS,
+ dialogType: ChannelSettingsModal,
+ dialogProps: {
+ channelId: channel.id,
+ focusOriginElement: 'channelInfoRHSChannelSettings',
+ isOpen: true,
+ },
+ }),
+ );
+ };
+
return (
+ {showChannelSettings && canAccessSettings && (
+ }
+ text={formatMessage({
+ id: 'channel_header.channel_settings',
+ defaultMessage: 'Channel Settings',
+ })}
+ onClick={openChannelSettings}
+ />
+ )}
{showNotificationPreferences && (