diff --git a/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/keyboard_shortcuts_1_spec.js b/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/keyboard_shortcuts_1_spec.js index 61fb30d804..9633e898db 100644 --- a/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/keyboard_shortcuts_1_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/keyboard_shortcuts/keyboard_shortcuts_1_spec.js @@ -59,13 +59,13 @@ describe('Keyboard Shortcuts', () => { }); // # Verify that we are in the test channel - cy.get('#channelIntro').contains('.channel-intro__title', `Beginning of ${testChannel.display_name}`).should('be.visible'); + cy.get('#channelIntro').contains('.channel-intro__title', `${testChannel.display_name}`).should('be.visible'); // # Verify that the right channel is displayed in LHS cy.uiGetLhsSection('CHANNELS').findByText(testChannel.display_name).should('be.visible'); // # Verify that the current user(sysadmin) created the channel - cy.get('#channelIntro').contains('.channel-intro__content', `This is the start of the ${testChannel.display_name} channel, created by sysadmin`).should('be.visible'); + cy.get('#channelIntro').contains('.channel-intro__created', 'Public channel created by sysadmin').should('be.visible'); }); }); diff --git a/e2e-tests/cypress/tests/integration/channels/messaging/ctrl_cmd_k_open_dm_with_mouse_spec.js b/e2e-tests/cypress/tests/integration/channels/messaging/ctrl_cmd_k_open_dm_with_mouse_spec.js index 3a81ae9e84..e5aeeba87d 100644 --- a/e2e-tests/cypress/tests/integration/channels/messaging/ctrl_cmd_k_open_dm_with_mouse_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/messaging/ctrl_cmd_k_open_dm_with_mouse_spec.js @@ -51,10 +51,10 @@ describe('Messaging', () => { // # Verify that we are in a DM channel cy.get('#channelIntro').should('be.visible').within(() => { - cy.get('.channel-intro-profile'). + cy.get('.channel-intro__title'). should('be.visible'). and('have.text', secondUser.username); - cy.get('.channel-intro-text'). + cy.get('.channel-intro__text'). should('be.visible'). and('contain', `This is the start of your direct message history with ${secondUser.username}.`). and('contain', 'Direct messages and files shared here are not shown to people outside this area.'); diff --git a/e2e-tests/cypress/tests/integration/channels/messaging/group_message_spec.js b/e2e-tests/cypress/tests/integration/channels/messaging/group_message_spec.js index 721bc985e4..c3ede992d9 100644 --- a/e2e-tests/cypress/tests/integration/channels/messaging/group_message_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/messaging/group_message_spec.js @@ -137,8 +137,8 @@ describe('Group Message', () => { }); // * Assert that intro message includes the right copy - const expectedChannelInfo = `This is the start of your group message history with ${sortedParticipants[0].username}, ${sortedParticipants[1].username}.Messages and files shared here are not shown to people outside this area.`; - cy.get('#channelIntro p.channel-intro-text').first().should('contain', expectedChannelInfo); + const expectedChannelInfo = 'This is the start of your group message history with these teammates.'; + cy.get('#channelIntro p.channel-intro__text').first().should('contain', expectedChannelInfo); cy.get('#channelIntro .profile-icon').should('have.length', '2'); cy.location().then((loc) => { diff --git a/e2e-tests/cypress/tests/integration/channels/messaging/send_message_via_profile_popover_spec.js b/e2e-tests/cypress/tests/integration/channels/messaging/send_message_via_profile_popover_spec.js index b561cc107c..4c28b8e1e8 100644 --- a/e2e-tests/cypress/tests/integration/channels/messaging/send_message_via_profile_popover_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/messaging/send_message_via_profile_popover_spec.js @@ -95,12 +95,12 @@ function verifyDMChannelViaSendMessage(postId, team, channel, profileSelector, u // * Verify that it redirects into the DM channel and matches channel intro cy.get('#channelIntro').should('be.visible').within(() => { cy.url().should('include', `/${team.name}/messages/@${user.username}`); - cy.get('.channel-intro-profile'). + cy.get('.channel-intro__title'). should('be.visible'). and('have.text', user.username); - cy.get('.channel-intro-text'). + cy.get('.channel-intro__text'). should('be.visible'). and('contain', `This is the start of your direct message history with ${user.username}.`). - and('contain', 'Direct messages and files shared here are not shown to people outside this area.'); + and('contain', 'Messages and files shared here are not shown to anyone else.'); }); } diff --git a/e2e-tests/cypress/tests/integration/channels/multi_team_and_dm/gm_add_user_spec.js b/e2e-tests/cypress/tests/integration/channels/multi_team_and_dm/gm_add_user_spec.js index 5e92e3fe16..8cded03fa1 100644 --- a/e2e-tests/cypress/tests/integration/channels/multi_team_and_dm/gm_add_user_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/multi_team_and_dm/gm_add_user_spec.js @@ -136,7 +136,7 @@ describe('Multi-user group messages', () => { // * Original messages does not exist cy.contains('.post-message__text', 'historical').should('not.exist'); - cy.contains('p.channel-intro-text span', 'This is the start of your group message history with'); + cy.contains('p.channel-intro__text', 'This is the start of your group message history with'); // * New user is added to the GM cy.get('.member-rhs__trigger').click(); diff --git a/webapp/channels/src/components/common/svg_images_components/channel_intro_private_svg.tsx b/webapp/channels/src/components/common/svg_images_components/channel_intro_private_svg.tsx new file mode 100644 index 0000000000..d92dd0bada --- /dev/null +++ b/webapp/channels/src/components/common/svg_images_components/channel_intro_private_svg.tsx @@ -0,0 +1,169 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; + +type SvgProps = { + width?: number; + height?: number; +}; + +const PrivateChannelIntroSvg = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default PrivateChannelIntroSvg; diff --git a/webapp/channels/src/components/common/svg_images_components/channel_intro_public_svg.tsx b/webapp/channels/src/components/common/svg_images_components/channel_intro_public_svg.tsx new file mode 100644 index 0000000000..bde8223d73 --- /dev/null +++ b/webapp/channels/src/components/common/svg_images_components/channel_intro_public_svg.tsx @@ -0,0 +1,255 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; + +type SvgProps = { + width?: number; + height?: number; +}; + +const PublicChannelIntroSvg = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default PublicChannelIntroSvg; diff --git a/webapp/channels/src/components/common/svg_images_components/channel_intro_town_square_svg.tsx b/webapp/channels/src/components/common/svg_images_components/channel_intro_town_square_svg.tsx new file mode 100644 index 0000000000..caa63bb848 --- /dev/null +++ b/webapp/channels/src/components/common/svg_images_components/channel_intro_town_square_svg.tsx @@ -0,0 +1,180 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as React from 'react'; + +type SvgProps = { + width?: number; + height?: number; +}; + +const SvgComponent = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export default SvgComponent; diff --git a/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.scss b/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.scss index cd8f9e95af..65c20bfc6c 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.scss +++ b/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.scss @@ -2,12 +2,10 @@ .MoreThanMaxFreeUsers { display: flex; flex-direction: row; - margin-top: 10px; .titleAndButton { display: flex; flex-direction: column; - padding: 20px 5px; margin-left: 20px; > span { @@ -17,56 +15,10 @@ line-height: 24px; } } - - button { - width: fit-content; - padding: 8px 16px; - border: none; - border-radius: 4px; - background-color: var(--button-bg); - color: var(--button-color); - font-size: 12px; - font-weight: 600; - line-height: 16px; - - span { - color: var(--button-color); - } - - i { - color: var(--button-color); - font-size: 14px; - } - - &:hover:not(.disabled) { - background: linear-gradient(0deg, rgba(var(--center-channel-color-rgb), 0.16), rgba(var(--center-channel-color-rgb), 0.16)), var(--button-bg); - color: var(--button-color); - text-decoration: none; - } - } } .MoreThanMaxFreeUsersWrapper { display: flex; flex-direction: row; - - .channelIntroButton { - margin-top: 10px; - } -} - -.channelIntroButton { - border-radius: 4px; - color: var(--button-bg); - font-size: 12px; - font-weight: 600; - line-height: 16px; - - i { - font-size: 14px; - } - - &:hover { - background-color: rgba(var(--button-bg-rgb), 0.04); - } + gap: 8px; } diff --git a/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.tsx b/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.tsx index 7d19923332..bcd4e15583 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.tsx +++ b/webapp/channels/src/components/post_view/channel_intro_message/add_members_button.tsx @@ -14,7 +14,6 @@ import {trackEvent} from 'actions/telemetry_actions'; import AddGroupsToChannelModal from 'components/add_groups_to_channel_modal'; import ChannelInviteModal from 'components/channel_invite_modal'; -import EmptyStateThemeableSvg from 'components/common/svg_images_components/empty_state_themeable_svg'; import InvitationModal from 'components/invitation_modal'; import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate'; import TeamPermissionGate from 'components/permissions_gates/team_permission_gate'; @@ -30,18 +29,16 @@ export interface AddMembersButtonProps { totalUsers?: number; usersLimit: number; channel: Channel; - setHeader?: React.ReactNode; pluginButtons?: React.ReactNode; } -const AddMembersButton: React.FC = ({totalUsers, usersLimit, channel, setHeader, pluginButtons}: AddMembersButtonProps) => { +const AddMembersButton: React.FC = ({totalUsers, usersLimit, channel, pluginButtons}: AddMembersButtonProps) => { const currentTeamId = useSelector(getCurrentTeamId); if (!totalUsers) { return (); } - const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; const inviteUsers = totalUsers < usersLimit; return ( @@ -49,63 +46,50 @@ const AddMembersButton: React.FC = ({totalUsers, usersLim teamId={currentTeamId} permissions={[Permissions.ADD_USER_TO_TEAM, Permissions.INVITE_GUEST]} > - {inviteUsers && !isPrivate ? ( + {inviteUsers ? ( ) : ( )} ); }; -const LessThanMaxFreeUsers = ({setHeader, pluginButtons}: {setHeader: React.ReactNode; pluginButtons: React.ReactNode}) => { +const LessThanMaxFreeUsers = ({pluginButtons}: {pluginButtons: React.ReactNode}) => { const {formatMessage} = useIntl(); return ( <> {pluginButtons} - {setHeader}
- -
- trackEvent('channel_intro_message', 'click_invite_button')} + > + - trackEvent('channel_intro_message', 'click_invite_button')} - > - - - -
+ +
); }; -const MoreThanMaxFreeUsers = ({channel, setHeader, pluginButtons}: {channel: Channel; setHeader: React.ReactNode; pluginButtons: React.ReactNode}) => { +const MoreThanMaxFreeUsers = ({channel, pluginButtons}: {channel: Channel; pluginButtons: React.ReactNode}) => { const {formatMessage} = useIntl(); const modalId = channel.group_constrained ? ModalIdentifiers.ADD_GROUPS_TO_CHANNEL : ModalIdentifiers.CHANNEL_INVITE; @@ -125,7 +109,7 @@ const MoreThanMaxFreeUsers = ({channel, setHeader, pluginButtons}: {channel: Cha permissions={[isPrivate ? Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS : Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS]} > - {isPrivate && channel.group_constrained && + {channel.group_constrained && } - {isPrivate && !channel.group_constrained && - } - {!isPrivate && + {!channel.group_constrained && } {pluginButtons} - {setHeader} ); }; diff --git a/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.test.tsx b/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.test.tsx index d6dd85f22b..8c86cf9a2c 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.test.tsx +++ b/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.test.tsx @@ -41,13 +41,19 @@ describe('components/post_view/ChannelIntroMessages', () => { fullWidth: true, locale: 'en', channelProfiles: [], + isReadOnly: false, + isFavorite: false, enableUserCreation: false, teamIsGroupConstrained: false, creatorName: 'creatorName', + currentUser: users[0], stats: {}, usersLimit: 10, + isMobileView: false, actions: { getTotalUsersStats: jest.fn().mockResolvedValue([]), + favoriteChannel: jest.fn().mockResolvedValue([]), + unfavoriteChannel: jest.fn().mockResolvedValue([]), }, }; @@ -89,13 +95,12 @@ describe('components/post_view/ChannelIntroMessages', () => { , initialState, ); - const beginningHeading = screen.getByText('Beginning of test channel'); + const beginningHeading = screen.getByText('test channel'); expect(beginningHeading).toBeInTheDocument(); expect(beginningHeading).toHaveClass('channel-intro__title'); - expect(screen.getByText(`This is the start of the test channel channel, created by ${baseProps.creatorName} on October 17, 2017.`)); - expect(screen.getByText('Any member can join and read this channel.')).toBeInTheDocument(); + expect(screen.getByText('This is the start of test channel. Any team member can join and read this channel.')).toBeInTheDocument(); }); }); @@ -116,13 +121,13 @@ describe('components/post_view/ChannelIntroMessages', () => { />, initialState, ); - expect(screen.queryByText('Beginning of test channel')).not.toBeInTheDocument(); + expect(screen.queryByText('test channel')).not.toBeInTheDocument(); expect(screen.queryByText('Any member can join and read this channel.')).not.toBeInTheDocument(); // there are no profiles in the dom, channel type is GM_CHANNEL, teammate text should be displayed - expect(screen.getByText('This is the start of your group message history with these teammates. Messages and files shared here are not shown to people outside this area.')).toBeInTheDocument(); + expect(screen.getByText('This is the start of your group message history with these teammates. ', {exact: false})).toBeInTheDocument(); - expect(screen.getByText('This is the start of your', {exact: false})).toHaveClass('channel-intro-text'); + expect(screen.getByText('This is the start of your', {exact: false})).toHaveClass('channel-intro__text'); }); test('should match component state, with profiles', () => { @@ -133,12 +138,12 @@ describe('components/post_view/ChannelIntroMessages', () => { />, initialState, ); - expect(screen.getByText('This is the start of your group message history with test channel', {exact: false})).toBeInTheDocument(); + expect(screen.getByText('This is the start of your group message history with these teammates. ', {exact: false})).toBeInTheDocument(); - const headerDialog = screen.getByLabelText('Set a Header dialog'); + const headerDialog = screen.getByLabelText('Set header dialog'); expect(headerDialog).toBeInTheDocument(); - expect(headerDialog).toHaveTextContent('Set a Header'); - expect(headerDialog).toHaveClass('style--none intro-links color--link channelIntroButton'); + expect(headerDialog).toHaveTextContent('Set header'); + expect(headerDialog).toHaveClass('action-button'); // one for user1 and one for guest @@ -150,12 +155,7 @@ describe('components/post_view/ChannelIntroMessages', () => { expect(image[1]).toHaveAttribute('src', '/api/v4/users/guest1/image?_=0'); expect(image[1]).toHaveAttribute('loading', 'lazy'); - const editIcon = screen.getByTitle('Edit Icon'); - - expect(editIcon).toBeInTheDocument(); - expect(editIcon).toHaveClass('icon-pencil-outline'); - - const notificationPreferencesButton = screen.getByText('Notification Preferences'); + const notificationPreferencesButton = screen.getByText('Notifications'); expect(notificationPreferencesButton).toBeInTheDocument(); }); }); @@ -177,9 +177,9 @@ describe('components/post_view/ChannelIntroMessages', () => { />, initialState, ); - const message = screen.getByText('This is the start of your direct message history with this teammate', {exact: false}); + const message = screen.getByText('This is the start of your direct message history with this teammate. Messages and files shared here are not shown to anyone else.', {exact: false}); expect(message).toBeInTheDocument(); - expect(message).toHaveClass('channel-intro-text'); + expect(message).toHaveClass('channel-intro__text'); }); test('should match component state, with teammate', () => { @@ -190,7 +190,7 @@ describe('components/post_view/ChannelIntroMessages', () => { teammateName='my teammate' />, initialState, ); - expect(screen.getByText('This is the start of your direct message history with my teammate', {exact: false})).toBeInTheDocument(); + expect(screen.getByText('This is the start of your direct message history with my teammate.', {exact: false})).toBeInTheDocument(); const teammate = screen.getByLabelText('my teammate'); @@ -204,16 +204,11 @@ describe('components/post_view/ChannelIntroMessages', () => { expect(image).toHaveAttribute('src', '/api/v4/users/user1/image?_=0'); expect(image).toHaveAttribute('loading', 'lazy'); - const headerDialog = screen.getByLabelText('Set a Header dialog'); + const headerDialog = screen.getByLabelText('Set header dialog'); expect(headerDialog).toBeInTheDocument(); - expect(headerDialog).toHaveTextContent('Set a Header'); - expect(headerDialog).toHaveClass('style--none intro-links color--link channelIntroButton'); - - const editIcon = screen.getByTitle('Edit Icon'); - - expect(editIcon).toBeInTheDocument(); - expect(editIcon).toHaveClass('icon-pencil-outline'); + expect(headerDialog).toHaveTextContent('Set header'); + expect(headerDialog).toHaveClass('action-button'); }); }); @@ -236,13 +231,12 @@ describe('components/post_view/ChannelIntroMessages', () => { />, initialState, ); - const beginningHeading = screen.getByText('Beginning of test channel'); + const beginningHeading = screen.getByText('test channel'); expect(beginningHeading).toBeInTheDocument(); expect(beginningHeading).toHaveClass('channel-intro__title'); - expect(screen.getByText('Welcome to test channel!')).toBeInTheDocument(); - expect(screen.getByText('Messages can only be posted by system admins. Everyone automatically becomes a permanent member of this channel when they join the team.', {exact: false})).toBeInTheDocument(); + expect(screen.getByText('Messages can only be posted by admins. Everyone automatically becomes a permanent member of this channel when they join the team.', {exact: false})).toBeInTheDocument(); }); test('should match component state without any permission', () => { @@ -256,12 +250,11 @@ describe('components/post_view/ChannelIntroMessages', () => { //no permission is given, invite link should not be in the dom expect(screen.queryByText('Add other groups to this team')).not.toBeInTheDocument(); - const beginningHeading = screen.getByText('Beginning of test channel'); + const beginningHeading = screen.getByText('test channel'); expect(beginningHeading).toBeInTheDocument(); expect(beginningHeading).toHaveClass('channel-intro__title'); - expect(screen.getByText('Welcome to test channel!')).toBeInTheDocument(); - expect(screen.getByText('Post messages here that you want everyone to see. Everyone automatically becomes a permanent member of this channel when they join the team.', {exact: false})).toBeInTheDocument(); + expect(screen.getByText('Post messages here that you want everyone to see. Everyone automatically becomes a member of this channel when they join the team.', {exact: false})).toBeInTheDocument(); }); }); @@ -283,12 +276,8 @@ describe('components/post_view/ChannelIntroMessages', () => { {...props} />, initialState, ); - expect(screen.getByText('Beginning of off-topic')).toBeInTheDocument(); screen.getByText('This is the start of off-topic, a channel for non-work-related conversations.'); - expect(screen.getByText('This is the start of off-topic, a channel for non-work-related conversations.')).toHaveClass('channel-intro__content'); - - // stats.total_users_count is not specified, loading icon should be in the dom - screen.getByTitle('Loading Icon'); + expect(screen.getByText('This is the start of off-topic, a channel for non-work-related conversations.')).toHaveClass('channel-intro__text'); }); }); }); diff --git a/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx b/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx index 402ea405d2..387a1a03f3 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx +++ b/webapp/channels/src/components/post_view/channel_intro_message/channel_intro_message.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {FormattedDate, FormattedMessage, defineMessages} from 'react-intl'; -import {BellRingOutlineIcon} from '@mattermost/compass-icons/components'; +import {BellRingOutlineIcon, GlobeIcon, PencilOutlineIcon, StarOutlineIcon, LockOutlineIcon, StarIcon} from '@mattermost/compass-icons/components'; import type {Channel, ChannelMembership} from '@mattermost/types/channels'; import type {UserProfile as UserProfileType} from '@mattermost/types/users'; @@ -14,6 +14,9 @@ import {isChannelMuted} from 'mattermost-redux/utils/channel_utils'; import AddGroupsToTeamModal from 'components/add_groups_to_team_modal'; import ChannelNotificationsModal from 'components/channel_notifications_modal'; +import ChannelIntroPrivateSvg from 'components/common/svg_images_components/channel_intro_private_svg'; +import ChannelIntroPublicSvg from 'components/common/svg_images_components/channel_intro_public_svg'; +import ChannelIntroTownSquareSvg from 'components/common/svg_images_components/channel_intro_town_square_svg'; import EditChannelHeaderModal from 'components/edit_channel_header_modal'; import FormattedMarkdownMessage from 'components/formatted_markdown_message'; import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate'; @@ -21,7 +24,6 @@ import TeamPermissionGate from 'components/permissions_gates/team_permission_gat import ProfilePicture from 'components/profile_picture'; import ToggleModalButton from 'components/toggle_modal_button'; import UserProfile from 'components/user_profile'; -import EditIcon from 'components/widgets/icons/fa_edit_icon'; import {Constants, ModalIdentifiers} from 'utils/constants'; import {getMonthLong} from 'utils/i18n'; @@ -38,19 +40,32 @@ type Props = { channelProfiles: UserProfileType[]; enableUserCreation?: boolean; isReadOnly?: boolean; + isFavorite: boolean; teamIsGroupConstrained?: boolean; creatorName: string; teammate?: UserProfileType; teammateName?: string; + currentUser: UserProfileType; stats: any; usersLimit: number; channelMember?: ChannelMembership; + isMobileView: boolean; actions: { getTotalUsersStats: () => any; + favoriteChannel: (channelId: string) => any; + unfavoriteChannel: (channelId: string) => any; }; } export default class ChannelIntroMessage extends React.PureComponent { + toggleFavorite = () => { + if (this.props.isFavorite) { + this.props.actions.unfavoriteChannel(this.props.channel.id); + } else { + this.props.actions.favoriteChannel(this.props.channel.id); + } + }; + componentDidMount() { if (!this.props.stats?.total_users_count) { this.props.actions.getTotalUsersStats(); @@ -61,18 +76,21 @@ export default class ChannelIntroMessage extends React.PureComponent { const { currentUserId, channel, - channelMember, - creatorName, fullWidth, locale, + channelProfiles, enableUserCreation, isReadOnly, - channelProfiles, + isFavorite, teamIsGroupConstrained, + creatorName, teammate, teammateName, + currentUser, stats, usersLimit, + channelMember, + isMobileView, } = this.props; let centeredIntro = ''; @@ -81,15 +99,15 @@ export default class ChannelIntroMessage extends React.PureComponent { } if (channel.type === Constants.DM_CHANNEL) { - return createDMIntroMessage(channel, centeredIntro, teammate, teammateName); + return createDMIntroMessage(channel, centeredIntro, currentUser, isFavorite, isMobileView, this.toggleFavorite, teammate, teammateName); } else if (channel.type === Constants.GM_CHANNEL) { - return createGMIntroMessage(channel, centeredIntro, channelProfiles, currentUserId, channelMember); + return createGMIntroMessage(channel, centeredIntro, isFavorite, isMobileView, this.toggleFavorite, channelProfiles, currentUserId, currentUser, channelMember); } else if (channel.name === Constants.DEFAULT_CHANNEL) { - return createDefaultIntroMessage(channel, centeredIntro, stats, usersLimit, enableUserCreation, isReadOnly, teamIsGroupConstrained); + return createDefaultIntroMessage(channel, centeredIntro, currentUser, isFavorite, isMobileView, this.toggleFavorite, stats, usersLimit, enableUserCreation, isReadOnly, teamIsGroupConstrained); } else if (channel.name === Constants.OFFTOPIC_CHANNEL) { - return createOffTopicIntroMessage(channel, centeredIntro, stats, usersLimit); + return createOffTopicIntroMessage(channel, centeredIntro, isFavorite, isMobileView, currentUser, this.toggleFavorite, stats, usersLimit); } else if (channel.type === Constants.OPEN_CHANNEL || channel.type === Constants.PRIVATE_CHANNEL) { - return createStandardIntroMessage(channel, centeredIntro, stats, usersLimit, locale, creatorName); + return createStandardIntroMessage(channel, centeredIntro, currentUser, isFavorite, isMobileView, this.toggleFavorite, stats, usersLimit, locale, creatorName); } return null; } @@ -135,47 +153,62 @@ const getGMIntroMessageSpecificPart = (userProfile: UserProfileType | undefined, ); }; -function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles: UserProfileType[], currentUserId: string, channelMembership?: ChannelMembership) { +function createGMIntroMessage( + channel: Channel, + centeredIntro: string, + isFavorite: boolean, + isMobileView: boolean, + toggleFavorite: () => void, + profiles: UserProfileType[], + currentUserId: string, + currentUser: UserProfileType, + channelMembership?: ChannelMembership, +) { const channelIntroId = 'channelIntro'; if (profiles.length > 0) { const currentUserProfile = profiles.find((v) => v.id === currentUserId); + const pictures = profiles. filter((profile) => profile.id !== currentUserId). map((profile) => ( )); + const actionButtons = ( +
+ {createFavoriteButton(isFavorite, toggleFavorite)} + {createSetHeaderButton(channel)} + {!isMobileView && createNotificationPreferencesButton(channel, currentUser)} + +
+ ); + return (
-
+
{pictures}
-

+

+ {channel.display_name} +

+

, - }} + id='intro_messages.group_message' + defaultMessage={'This is the start of your group message history with these teammates. '} /> {getGMIntroMessageSpecificPart(currentUserProfile, channelMembership)}

-
- {createNotificationPreferencesButton(channel, currentUserProfile)} - - {createSetHeaderButton(channel)} -
+ {actionButtons}
); } @@ -185,7 +218,7 @@ function createGMIntroMessage(channel: Channel, centeredIntro: string, profiles: id={channelIntroId} className={'channel-intro ' + centeredIntro} > -

+

void, + teammate?: UserProfileType, + teammateName?: string, +) { const channelIntroId = 'channelIntro'; if (teammate) { const src = teammate ? Utils.imageURLForUser(teammate.id, teammate.last_picture_update) : ''; @@ -207,6 +249,14 @@ function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate? setHeaderButton = createSetHeaderButton(channel); } + const actionButtons = ( +

+ {createFavoriteButton(isFavorite, toggleFavorite)} + {setHeaderButton} + {pluggableButton} +
+ ); + return (
-
+

-

-

+ +

-
- {pluggableButton} - {setHeaderButton} -
+ {actionButtons}
); } @@ -249,22 +297,34 @@ function createDMIntroMessage(channel: Channel, centeredIntro: string, teammate? id={channelIntroId} className={'channel-intro ' + centeredIntro} > -

+

); } -function createOffTopicIntroMessage(channel: Channel, centeredIntro: string, stats: any, usersLimit: number) { +function createOffTopicIntroMessage( + channel: Channel, + centeredIntro: string, + isFavorite: boolean, + isMobileView: boolean, + currentUser: UserProfileType, + toggleFavorite: () => void, + stats: any, + usersLimit: number, +) { const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; const children = createSetHeaderButton(channel); const totalUsers = stats.total_users_count; + const inviteUsers = totalUsers < usersLimit; let setHeaderButton = null; + let actionButtons = null; + if (children) { setHeaderButton = ( ); + if (inviteUsers) { + actionButtons = ( +
+ {actionButtons = channelInviteButton} +
+ ); + } else { + actionButtons = ( +
+ {createFavoriteButton(isFavorite, toggleFavorite)} + {setHeaderButton} + {createNotificationPreferencesButton(channel, currentUser)} +
+ ); + } + return (
+

- + {channel.display_name}

-

+

- {channelInviteButton} + {actionButtons}
); } @@ -318,6 +388,10 @@ function createOffTopicIntroMessage(channel: Channel, centeredIntro: string, sta function createDefaultIntroMessage( channel: Channel, centeredIntro: string, + currentUser: UserProfileType, + isFavorite: boolean, + isMobileView: boolean, + toggleFavorite: () => void, stats: any, usersLimit: number, enableUserCreation?: boolean, @@ -327,9 +401,12 @@ function createDefaultIntroMessage( let teamInviteLink = null; const totalUsers = stats.total_users_count; const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; + const inviteUsers = totalUsers < usersLimit; let setHeaderButton = null; let pluginButtons = null; + let actionButtons = null; + if (!isReadOnly) { pluginButtons = ; const children = createSetHeaderButton(channel); @@ -358,7 +435,6 @@ function createDefaultIntroMessage( > {!teamIsGroupConstrained && + {actionButtons = teamInviteLink} + + ); + } else { + actionButtons = ( +
+ {createFavoriteButton(isFavorite, toggleFavorite)} + {setHeaderButton} + {createNotificationPreferencesButton(channel, currentUser)} + {teamIsGroupConstrained && pluginButtons} +
+ ); + } + return (
+

- + {channel.display_name}

-

+

{!isReadOnly && - } {isReadOnly && - }

- {teamInviteLink} - {teamIsGroupConstrained && pluginButtons} - {teamIsGroupConstrained && setHeaderButton} -
+ {actionButtons}
); } -function createStandardIntroMessage(channel: Channel, centeredIntro: string, stats: any, usersLimit: number, locale: string, creatorName: string) { +function createStandardIntroMessage( + channel: Channel, + centeredIntro: string, + currentUser: UserProfileType, + isFavorite: boolean, + isMobileView: boolean, + toggleFavorite: () => void, + stats: any, + usersLimit: number, + locale: string, + creatorName: string, +) { const uiName = channel.display_name; let memberMessage; + let teamInviteLink = null; const channelIsArchived = channel.delete_at !== 0; const totalUsers = stats.total_users_count; + const inviteUsers = totalUsers < usersLimit; if (channelIsArchived) { memberMessage = ''; @@ -440,14 +538,20 @@ function createStandardIntroMessage(channel: Channel, centeredIntro: string, sta memberMessage = ( ); } else { memberMessage = ( ); } @@ -467,7 +571,7 @@ function createStandardIntroMessage(channel: Channel, centeredIntro: string, sta createMessage = ( ); @@ -475,7 +579,7 @@ function createStandardIntroMessage(channel: Channel, centeredIntro: string, sta createMessage = ( ); @@ -485,7 +589,7 @@ function createStandardIntroMessage(channel: Channel, centeredIntro: string, sta - - - ); - } else if (channel.type === Constants.OPEN_CHANNEL) { - purposeMessage = ( - - - - ); - } + purposeMessage = ( + + + + ); } const isPrivate = channel.type === Constants.PRIVATE_CHANNEL; let setHeaderButton = null; + let actionButtons = null; const children = createSetHeaderButton(channel); if (children) { setHeaderButton = ( @@ -550,37 +643,51 @@ function createStandardIntroMessage(channel: Channel, centeredIntro: string, sta ); } - const channelInviteButton = ( + teamInviteLink = ( } /> ); + if (inviteUsers) { + actionButtons = ( +
+ {actionButtons = teamInviteLink} +
+ ); + } else { + actionButtons = ( +
+ {createFavoriteButton(isFavorite, toggleFavorite)} + {teamInviteLink} + {setHeaderButton} + {!isMobileView && createNotificationPreferencesButton(channel, currentUser)} + +
+ ); + } + return (
+ {isPrivate ? : }

- + {channel.display_name}

-

+

+ {isPrivate ? : } {createMessage} +
+

{memberMessage} {purposeMessage} -

- {channelInviteButton} + {actionButtons}
); } @@ -594,38 +701,63 @@ function createSetHeaderButton(channel: Channel) { return ( - + ); } -function createNotificationPreferencesButton(channel: Channel, currentUser?: UserProfileType) { - const isGM = channel.type === 'G'; - if (!isGM || !currentUser) { - return null; +function createFavoriteButton(isFavorite: boolean, toggleFavorite: () => void, classes?: string) { + let favoriteText; + if (isFavorite) { + favoriteText = ( + ); + } else { + favoriteText = ( + ); } + return ( + + ); +} +function createNotificationPreferencesButton(channel: Channel, currentUser: UserProfileType) { return ( - + ); diff --git a/webapp/channels/src/components/post_view/channel_intro_message/index.ts b/webapp/channels/src/components/post_view/channel_intro_message/index.ts index 6c3c5e6739..2774cd88cb 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/index.ts +++ b/webapp/channels/src/components/post_view/channel_intro_message/index.ts @@ -5,14 +5,16 @@ import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import type {Dispatch} from 'redux'; +import {favoriteChannel, unfavoriteChannel} from 'mattermost-redux/actions/channels'; import {getTotalUsersStats} from 'mattermost-redux/actions/users'; -import {getCurrentChannel, getDirectTeammate, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels'; +import {getCurrentChannel, getDirectTeammate, getMyCurrentChannelMembership, isCurrentChannelFavorite} from 'mattermost-redux/selectors/entities/channels'; import {getConfig} from 'mattermost-redux/selectors/entities/general'; import {get} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; -import {getProfilesInCurrentChannel, getCurrentUserId, getUser, getTotalUsersStats as getTotalUsersStatsSelector} from 'mattermost-redux/selectors/entities/users'; +import {getCurrentUser, getProfilesInCurrentChannel, getCurrentUserId, getUser, getTotalUsersStats as getTotalUsersStatsSelector} from 'mattermost-redux/selectors/entities/users'; import {getCurrentLocale} from 'selectors/i18n'; +import {getIsMobileView} from 'selectors/views/browser'; import {Preferences} from 'utils/constants'; import {getDisplayNameByUser} from 'utils/utils'; @@ -29,6 +31,7 @@ function mapStateToProps(state: GlobalState) { const channel = getCurrentChannel(state) || {}; const channelMember = getMyCurrentChannelMembership(state); const teammate = getDirectTeammate(state, channel.id); + const currentUser = getCurrentUser(state); const creator = getUser(state, channel.creator_id); const usersLimit = 10; @@ -43,13 +46,16 @@ function mapStateToProps(state: GlobalState) { channelProfiles: getProfilesInCurrentChannel(state), enableUserCreation, isReadOnly, + isFavorite: isCurrentChannelFavorite(state), teamIsGroupConstrained: Boolean(team.group_constrained), creatorName: getDisplayNameByUser(state, creator), teammate, teammateName: getDisplayNameByUser(state, teammate), + currentUser, stats, usersLimit, channelMember, + isMobileView: getIsMobileView(state), }; } @@ -57,6 +63,8 @@ function mapDispatchToProps(dispatch: Dispatch) { return { actions: bindActionCreators({ getTotalUsersStats, + favoriteChannel, + unfavoriteChannel, }, dispatch), }; } diff --git a/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx b/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx index a411ba8f0b..7ccd662cf5 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx +++ b/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx @@ -23,7 +23,7 @@ const PluggableIntroButtons = React.memo((props: Props) => { return (