From 708d046c1b4a2357d866d4a2dc20e82dc91ed844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Mon, 6 May 2024 19:19:10 +0200 Subject: [PATCH] Adding more visibility to the free edition in the interface (#26698) * Initial version of make more evident the mattermost edition used * Updating tests * Adding i18n strings * Fixing css property order * tweaks to badge and use proper MM logo svg * update snapshot * update language, tweaks to menu trial block * fix style lint issue * tweak letter-spacing * fix lint * update language * Removing trial related info from the main menu, and simplifying showing the license * Applying proposed changes * Fixing linter errors * Fixing tests * Updating i18n strings * fixing some styles and a bug * Fix linter error * updated login css and fixed lint issue * Moving to styled components the Free edition badge * Fixing linter error * Addressing PR review comments * Removing old snapshot * Addressing PR review comments * Fixing linter errors --------- Co-authored-by: Matthew Birtch --- .../logo_dark_blue_svg.tsx | 13 +- .../__snapshots__/product_menu.test.tsx.snap | 178 ++++++++++++++---- .../product_branding_team_edition/index.ts | 4 + .../product_branding_team_edition.tsx | 53 ++++++ .../product_menu/product_menu.test.tsx | 38 +++- .../product_menu/product_menu.tsx | 12 +- .../product_menu_list.test.tsx.snap | 33 ---- .../product_menu_list/product_menu_list.tsx | 7 - .../header_footer_route/header.scss | 26 ++- .../components/header_footer_route/header.tsx | 48 ++++- .../channels/src/components/login/login.scss | 13 +- .../widgets/menu/menu_items/menu_item.scss | 18 +- .../menu/menu_items/menu_start_trial.test.tsx | 83 +++++++- .../menu/menu_items/menu_start_trial.tsx | 79 ++++---- webapp/channels/src/i18n/en.json | 7 +- 15 files changed, 466 insertions(+), 146 deletions(-) create mode 100644 webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/index.ts create mode 100644 webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx diff --git a/webapp/channels/src/components/common/svg_images_components/logo_dark_blue_svg.tsx b/webapp/channels/src/components/common/svg_images_components/logo_dark_blue_svg.tsx index acf2b5d487..afdd04ac2c 100644 --- a/webapp/channels/src/components/common/svg_images_components/logo_dark_blue_svg.tsx +++ b/webapp/channels/src/components/common/svg_images_components/logo_dark_blue_svg.tsx @@ -2,14 +2,23 @@ // See LICENSE.txt for license information. import React from 'react'; +import styled from 'styled-components'; type Props = { width?: number; height?: number; + className?: string; } +const Svg = styled.svg.attrs({ + version: '1.1', + xmlns: 'http://www.w3.org/2000/svg', + xmlnsXlink: 'http://www.w3.org/1999/xlink', +})``; + export default (props: Props) => ( - ( d='M20.1373 12.2575C20.1373 12.2575 20.1838 14.3044 18.7644 15.695C17.3448 17.0856 15.6012 16.9585 14.467 16.5745C13.3326 16.1905 11.8704 15.2323 11.5873 13.2655C11.3045 11.2986 12.5848 9.70069 12.5848 9.70069L15.3747 6.23118L16.9996 4.24963L18.3944 2.52282C18.3944 2.52282 19.0345 1.66544 19.2169 1.48849C19.2531 1.45342 19.2901 1.43042 19.3265 1.4127L19.3529 1.39919L19.3577 1.39736C19.4344 1.36431 19.5228 1.35719 19.6081 1.38604C19.6915 1.41435 19.7562 1.47169 19.7971 1.54236L19.8056 1.55605L19.8131 1.57176C19.833 1.60938 19.8496 1.65229 19.8575 1.70525C19.8948 1.95671 19.8825 3.02665 19.8825 3.02665L19.9415 5.24543L20.0286 7.8066L20.1373 12.2575ZM23.7488 2.86301C28.9088 6.61849 31.2753 13.4339 29.1235 19.8082C26.4751 27.653 17.9863 31.8597 10.1633 29.2041C2.34031 26.5484 -1.85455 18.0361 0.79367 10.1914C2.9491 3.80658 8.97324 -0.167855 15.3585 0.00544665L13.3118 2.43039C9.52375 3.11721 6.25309 5.71126 4.96759 9.51933C3.05491 15.185 6.26074 21.3926 12.1281 23.3842C17.9952 25.376 24.3022 22.3977 26.2149 16.7321C27.4962 12.9366 26.4802 8.89825 23.9063 6.04672L23.7488 2.86301Z' fill='#1E325C' /> - + ); diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/__snapshots__/product_menu.test.tsx.snap b/webapp/channels/src/components/global_header/left_controls/product_menu/__snapshots__/product_menu.test.tsx.snap index 426056498a..dd81899c3c 100644 --- a/webapp/channels/src/components/global_header/left_controls/product_menu/__snapshots__/product_menu.test.tsx.snap +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/__snapshots__/product_menu.test.tsx.snap @@ -54,45 +54,78 @@ exports[`components/global/product_switcher should have an active button state w isMessaging={true} onClick={[Function]} /> + + + `; exports[`components/global/product_switcher should match snapshot 1`] = ` - - - +
+ + + + + + + + + + + + + + + +
`; exports[`components/global/product_switcher should match snapshot with product switcher menu 1`] = ` @@ -149,6 +182,75 @@ exports[`components/global/product_switcher should match snapshot with product s isMessaging={true} onClick={[Function]} /> + + + + + + +`; + +exports[`components/global/product_switcher should match snapshot without license 1`] = ` +
+ + + + + + + + + + + + +
@@ -208,6 +310,11 @@ exports[`components/global/product_switcher should render once when there are no isMessaging={true} onClick={[Function]} /> + + + @@ -267,6 +374,11 @@ exports[`components/global/product_switcher should render the correct amount of isMessaging={true} onClick={[Function]} /> + + + diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/index.ts b/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/index.ts new file mode 100644 index 0000000000..ac91b1b90d --- /dev/null +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export {default} from './product_branding_team_edition'; diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx b/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx new file mode 100644 index 0000000000..9af4fc7354 --- /dev/null +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_branding_team_edition/product_branding_team_edition.tsx @@ -0,0 +1,53 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import styled from 'styled-components'; + +import Logo from 'components/common/svg_images_components/logo_dark_blue_svg'; + +const ProductBrandingTeamEditionContainer = styled.div` + display: flex; + align-items: center; + + > * + * { + margin-left: 8px; + } +`; + +const StyledLogo = styled(Logo)` + path { + fill: rgba(var(--sidebar-text-rgb), 0.75); + } +`; + +const Badge = styled.div` + display: flex; + align-self: center; + padding: 2px 6px; + position: relative; + top: 1px; + border-radius: var(--radius-s); + margin-left: 12px; + background: rgba(var(--sidebar-text-rgb), 0.08); + color: rgba(var(--sidebar-text-rgb), 0.75); + font-family: 'Open Sans', sans-serif; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.025em; + line-height: 16px; +`; + +const ProductBrandingTeamEdition = (): JSX.Element => { + return ( + + + {'FREE EDITION'} + + ); +}; + +export default ProductBrandingTeamEdition; diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.test.tsx b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.test.tsx index 846190ca37..621e44f4e1 100644 --- a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.test.tsx +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.test.tsx @@ -40,10 +40,38 @@ describe('components/global/product_switcher', () => { }, }, }; + useSelectorMock.mockReturnValue(true); + useSelectorMock.mockReturnValueOnce(true); + useSelectorMock.mockReturnValueOnce({IsLicensed: 'true'}); const store = mockStore(state); const dummyDispatch = jest.fn(); useDispatchMock.mockReturnValue(dummyDispatch); - const wrapper = shallow(); + const wrapper = shallow(, { + wrappingComponent: reactRedux.Provider, + wrappingComponentProps: {store}, + }); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should match snapshot without license', () => { + const state = { + views: { + productMenu: { + switcherOpen: false, + }, + }, + }; + useSelectorMock.mockReturnValue(true); + useSelectorMock.mockReturnValueOnce(true); + useSelectorMock.mockReturnValueOnce({IsLicensed: 'false'}); + const store = mockStore(state); + const dummyDispatch = jest.fn(); + useDispatchMock.mockReturnValue(dummyDispatch); + const wrapper = shallow(, { + wrappingComponent: reactRedux.Provider, + wrappingComponentProps: {store}, + }); expect(wrapper).toMatchSnapshot(); }); @@ -63,6 +91,8 @@ describe('components/global/product_switcher', () => { const dummyDispatch = jest.fn(); useDispatchMock.mockReturnValue(dummyDispatch); useSelectorMock.mockReturnValue(true); + useSelectorMock.mockReturnValueOnce(true); + useSelectorMock.mockReturnValueOnce({IsLicensed: 'true'}); const wrapper = shallow(, { wrappingComponent: reactRedux.Provider, wrappingComponentProps: {store}, @@ -87,6 +117,8 @@ describe('components/global/product_switcher', () => { const dummyDispatch = jest.fn(); useDispatchMock.mockReturnValue(dummyDispatch); useSelectorMock.mockReturnValue(true); + useSelectorMock.mockReturnValueOnce(true); + useSelectorMock.mockReturnValueOnce({IsLicensed: 'true'}); const wrapper = shallow(, { wrappingComponent: reactRedux.Provider, wrappingComponentProps: {store}, @@ -115,6 +147,8 @@ describe('components/global/product_switcher', () => { const dummyDispatch = jest.fn(); useDispatchMock.mockReturnValue(dummyDispatch); useSelectorMock.mockReturnValue(true); + useSelectorMock.mockReturnValueOnce(true); + useSelectorMock.mockReturnValueOnce({IsLicensed: 'true'}); const wrapper = shallow(, { wrappingComponent: reactRedux.Provider, wrappingComponentProps: {store}, @@ -141,6 +175,8 @@ describe('components/global/product_switcher', () => { const dummyDispatch = jest.fn(); useDispatchMock.mockReturnValue(dummyDispatch); useSelectorMock.mockReturnValue(true); + useSelectorMock.mockReturnValueOnce(true); + useSelectorMock.mockReturnValueOnce({IsLicensed: 'true'}); const wrapper = shallow(, { wrappingComponent: reactRedux.Provider, wrappingComponentProps: {store}, diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx index e72ecb2642..28ff3f142b 100644 --- a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx @@ -8,7 +8,7 @@ import styled from 'styled-components'; import IconButton from '@mattermost/compass-components/components/icon-button'; // eslint-disable-line no-restricted-imports -import {getConfig} from 'mattermost-redux/selectors/entities/general'; +import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; import {getInt} from 'mattermost-redux/selectors/entities/preferences'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; @@ -34,6 +34,7 @@ import {useCurrentProductId, useProducts, isChannels} from 'utils/products'; import type {GlobalState} from 'types/store'; import ProductBranding from './product_branding'; +import ProductBrandingTeamEdition from './product_branding_team_edition'; import ProductMenuItem from './product_menu_item'; import ProductMenuList from './product_menu_list'; @@ -74,6 +75,7 @@ const ProductMenu = (): JSX.Element => { const switcherOpen = useSelector(isSwitcherOpen); const menuRef = useRef(null); const currentProductID = useCurrentProductId(); + const license = useSelector(getLicense); const enableTutorial = useSelector(getConfig).EnableTutorial === 'true'; const currentUserId = useSelector(getCurrentUserId); @@ -137,7 +139,8 @@ const ProductMenu = (): JSX.Element => { aria-label={formatMessage({id: 'global_header.productSwitchMenu', defaultMessage: 'Product switch menu'})} aria-controls='product-switcher-menu' /> - + {license.IsLicensed === 'false' && } + {license.IsLicensed === 'true' && } { onClick={handleClick} handleVisitConsoleClick={handleVisitConsoleClick} /> + + + diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu_list/__snapshots__/product_menu_list.test.tsx.snap b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu_list/__snapshots__/product_menu_list.test.tsx.snap index bf0ee289ee..85cea752fe 100644 --- a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu_list/__snapshots__/product_menu_list.test.tsx.snap +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu_list/__snapshots__/product_menu_list.test.tsx.snap @@ -11,17 +11,6 @@ exports[`components/global/product_switcher_menu should match snapshot with id 1 - - - - - - - - - {
- - - { - const {EnableCustomBrand, SiteName} = useSelector(getConfig); + const {SiteName} = useSelector(getConfig); + const license = useSelector(getLicense); - const ariaLabel = EnableCustomBrand === 'true' && SiteName ? SiteName : 'Mattermost'; + const ariaLabel = SiteName || 'Mattermost'; + + let freeBanner = null; + if (license.IsLicensed === 'false') { + freeBanner = <>{'FREE EDITION'}; + } + + let title: React.ReactNode = SiteName; + if (title === 'Mattermost') { + if (freeBanner) { + title = ''; + } else { + title = ; + } + } return (
- - {EnableCustomBrand === 'true' || SiteName !== 'Mattermost' ? SiteName : } - +
+ {freeBanner && + + {freeBanner} + + } + {title && + + {title} + + } +
{alternateLink}
{onBackButtonClick && ( diff --git a/webapp/channels/src/components/login/login.scss b/webapp/channels/src/components/login/login.scss index e0262ca71d..1c8b410dca 100644 --- a/webapp/channels/src/components/login/login.scss +++ b/webapp/channels/src/components/login/login.scss @@ -6,6 +6,7 @@ .login-body { display: flex; flex-direction: column; + padding-bottom: 32px; margin: auto; .login-body-action { @@ -43,7 +44,7 @@ align-self: flex-start; .login-body-message-title { - padding-right: 48px; + padding-right: 24px; color: var(--title-color-indigo-500); font-family: Metropolis; font-size: 80px; @@ -229,7 +230,7 @@ } .login-body-message .login-body-message-title { - padding-right: 110px; + padding-right: 80px; margin-top: 48px; } } @@ -254,7 +255,7 @@ padding: 24px; .login-body-message-title { - padding-right: 180px; + padding-right: 80px; font-size: 64px; line-height: 76px; } @@ -305,8 +306,13 @@ .login-body-card-content { .login-body-card-title { display: block; + margin-bottom: 24px; font-size: 32px; line-height: 40px; + + &:has(+ .login-body-custom-branding-markdown){ + margin-bottom: 12px; + } } .login-body-custom-branding-markdown, @@ -339,7 +345,6 @@ padding: 24px; .login-body-message-title { - max-width: 271px; padding-right: 0; font-size: 45px; line-height: 56px; diff --git a/webapp/channels/src/components/widgets/menu/menu_items/menu_item.scss b/webapp/channels/src/components/widgets/menu/menu_items/menu_item.scss index dadf4c065e..5676535126 100644 --- a/webapp/channels/src/components/widgets/menu/menu_items/menu_item.scss +++ b/webapp/channels/src/components/widgets/menu/menu_items/menu_item.scss @@ -313,12 +313,11 @@ display: flex; flex-direction: column; align-items: flex-start; - padding: 8px; - padding-left: 16px; + padding: 8px 20px; border-radius: 4px; - margin: 8px; margin-top: 0; - background-color: rgba(var(--button-bg-rgb), 0.04); + font-size: 12px; + line-height: 16px; button { padding: 3px 0; @@ -327,16 +326,21 @@ color: var(--link-color); font-style: normal; font-weight: 600; - } - button:hover { - background-color: transparent; + &:hover { + background: transparent; + text-decoration: underline; + } } .start_trial_content { position: relative; width: 100%; } + + .editionName { + text-transform: capitalize; + } } } diff --git a/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.test.tsx b/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.test.tsx index 45d8d6d0c6..e608556ed3 100644 --- a/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.test.tsx +++ b/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.test.tsx @@ -4,6 +4,8 @@ import React from 'react'; import * as reactRedux from 'react-redux'; +import {Permissions} from 'mattermost-redux/constants'; + import {mountWithIntl} from 'tests/helpers/intl-test-helper'; import mockStore from 'tests/test_store'; @@ -21,10 +23,17 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { entities: { users: { currentUserId: 'test_id', + profiles: { + test_id: { + id: 'test_id', + roles: 'system_user', + }, + }, }, general: { config: { EnableTutorial: 'true', + BuildEnterpriseReady: 'true', }, license: { IsLicensed: 'false', @@ -35,6 +44,13 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { IsLicensed: 'false', }, }, + roles: { + roles: { + system_user: { + permissions: [Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE], + }, + }, + }, preferences: { myPreferences: { 'tutorial_step-test_id': { @@ -59,10 +75,17 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { entities: { users: { currentUserId: 'test_id', + profiles: { + test_id: { + id: 'test_id', + roles: 'system_user', + }, + }, }, general: { config: { EnableTutorial: 'true', + BuildEnterpriseReady: 'true', }, license: { IsLicensed: 'false', @@ -74,6 +97,13 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { IsLicensed: 'true', }, }, + roles: { + roles: { + system_user: { + permissions: [Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE], + }, + }, + }, preferences: { myPreferences: { 'tutorial_step-test_id': { @@ -98,10 +128,17 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { entities: { users: { currentUserId: 'test_id', + profiles: { + test_id: { + id: 'test_id', + roles: 'system_user', + }, + }, }, general: { config: { EnableTutorial: 'true', + BuildEnterpriseReady: 'true', }, license: { IsLicensed: 'true', @@ -113,6 +150,13 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { IsLicensed: 'false', }, }, + roles: { + roles: { + system_user: { + permissions: [Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE], + }, + }, + }, preferences: { myPreferences: { 'tutorial_step-test_id': { @@ -132,15 +176,22 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { expect(wrapper.find('button').exists()).toEqual(false); }); - test('should render menu option that open the start trial benefits modal when is current licensed but is trial', () => { + test('should render null when is current licensed but is trial and you have no permissions to load the trial', () => { const state = { entities: { users: { currentUserId: 'test_id', + profiles: { + test_id: { + id: 'test_id', + roles: 'system_user', + }, + }, }, general: { config: { EnableTutorial: 'true', + BuildEnterpriseReady: 'true', }, license: { IsLicensed: 'true', @@ -152,6 +203,13 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { IsLicensed: 'false', }, }, + roles: { + roles: { + system_user: { + permissions: [], + }, + }, + }, preferences: { myPreferences: { 'tutorial_step-test_id': { @@ -168,19 +226,25 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { const dummyDispatch = jest.fn(); useDispatchMock.mockReturnValue(dummyDispatch); const wrapper = mountWithIntl(); - expect(wrapper.find('button').exists()).toEqual(true); - expect(wrapper.find('button').text()).toEqual('Learn More'); + expect(wrapper.find('button').exists()).toEqual(false); }); - test('should render menu option that open the start trial modal when has no license and no previous license', () => { + test('should render menu option that open the start trial modal when has no license and no previous license and you have permissions to load the trial', () => { const state = { entities: { users: { currentUserId: 'test_id', + profiles: { + test_id: { + id: 'test_id', + roles: 'system_user', + }, + }, }, general: { config: { EnableTutorial: 'true', + BuildEnterpriseReady: 'true', }, license: { IsLicensed: 'false', @@ -192,6 +256,13 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { IsLicensed: 'false', }, }, + roles: { + roles: { + system_user: { + permissions: [Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE], + }, + }, + }, preferences: { myPreferences: { 'tutorial_step-test_id': { @@ -209,7 +280,7 @@ describe('components/widgets/menu/menu_items/menu_start_trial', () => { useDispatchMock.mockReturnValue(dummyDispatch); const wrapper = mountWithIntl(); expect(wrapper.find('button').exists()).toEqual(true); - expect(wrapper.find('div.start_trial_content').text()).toEqual('Try Enterprise for free now!'); - expect(wrapper.find('button').text()).toEqual('Learn More'); + expect(wrapper.find('div.start_trial_content').text()).toEqual('This is the free edition of Mattermost, ideal for evaluation and small teams.'); + expect(wrapper.find('button').text()).toEqual('Start an Enterprise trial'); }); }); diff --git a/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.tsx b/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.tsx index 2fbbd76842..efcb11d8a1 100644 --- a/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.tsx +++ b/webapp/channels/src/components/widgets/menu/menu_items/menu_start_trial.tsx @@ -4,11 +4,14 @@ import React, {useEffect} from 'react'; import {useIntl} from 'react-intl'; import {useDispatch, useSelector} from 'react-redux'; +import styled from 'styled-components'; import type {GlobalState} from '@mattermost/types/store'; import {getPrevTrialLicense} from 'mattermost-redux/actions/admin'; -import {getLicense} from 'mattermost-redux/selectors/entities/general'; +import {Permissions} from 'mattermost-redux/constants'; +import {getLicense, getConfig} from 'mattermost-redux/selectors/entities/general'; +import {haveISystemPermission} from 'mattermost-redux/selectors/entities/roles'; import {trackEvent} from 'actions/telemetry_actions'; import {openModal} from 'actions/views/modals'; @@ -16,13 +19,27 @@ import {openModal} from 'actions/views/modals'; import {makeAsyncComponent} from 'components/async_load'; import {ModalIdentifiers, TELEMETRY_CATEGORIES} from 'utils/constants'; -import {isTrialLicense} from 'utils/license_utils'; import './menu_item.scss'; -const TrialBenefitsModal = makeAsyncComponent('TrialBenefitsModal', React.lazy(() => import('components/trial_benefits_modal/trial_benefits_modal'))); const LearnMoreTrialModal = makeAsyncComponent('LearnMoreTrialModal', React.lazy(() => import('components/learn_more_trial_modal/learn_more_trial_modal'))); +const FreeVersionBadge = styled.div` + position: relative; + top: 1px; + display: flex; + padding: 2px 6px; + border-radius: var(--radius-s); + margin-bottom: 6px; + background: rgba(var(--center-channel-color-rgb), 0.08); + color: rgba(var(--center-channel-color-rgb), 0.75); + font-family: 'Open Sans', sans-serif; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.025em; + line-height: 16px; +`; + type Props = { id: string; } @@ -35,6 +52,10 @@ const MenuStartTrial = (props: Props): JSX.Element | null => { dispatch(getPrevTrialLicense()); }, []); + const config = useSelector(getConfig); + const isE0 = config.BuildEnterpriseReady === 'true'; + const hasPermissionForStartTrial = useSelector((state: GlobalState) => haveISystemPermission(state, {permission: Permissions.SYSCONSOLE_WRITE_ABOUT_EDITION_AND_LICENSE})); + const openLearnMoreTrialModal = () => { trackEvent( TELEMETRY_CATEGORIES.SELF_HOSTED_START_TRIAL_MODAL, @@ -45,54 +66,42 @@ const MenuStartTrial = (props: Props): JSX.Element | null => { dialogType: LearnMoreTrialModal, })); }; - - const openTrialBenefitsModal = () => { - trackEvent( - TELEMETRY_CATEGORIES.SELF_HOSTED_START_TRIAL_MODAL, - 'open_trial_benefits_modal_from_menu', - ); - dispatch(openModal({ - modalId: ModalIdentifiers.TRIAL_BENEFITS_MODAL, - dialogType: TrialBenefitsModal, - })); - }; - const prevTrialLicense = useSelector((state: GlobalState) => state.entities.admin.prevTrialLicense); const license = useSelector(getLicense); const isPrevLicensed = prevTrialLicense?.IsLicensed; const isCurrentLicensed = license?.IsLicensed; - const isCurrentLicenseTrial = isTrialLicense(license); - // Show this CTA if the instance is currently not licensed and has never had a trial license loaded before - const show = (isCurrentLicensed === 'false' && isPrevLicensed === 'false') || isCurrentLicenseTrial; - if (!show) { + if (isCurrentLicensed === 'true') { return null; } + const showTrialButton = isCurrentLicensed === 'false' && isPrevLicensed === 'false' && hasPermissionForStartTrial && isE0; + return (
} + {!isE0 && +
+ {formatMessage({ + id: 'navbar_dropdown.versionTextTeamEdition', + defaultMessage: 'This is the free open source edition of Mattermost, ideal for evaluation and small teams.', + })} +
} + {showTrialButton && - - } + {formatMessage({id: 'navbar_dropdown.startAnEnterpriseTrial', defaultMessage: 'Start an Enterprise trial'})} + } ); }; diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index fe990bd570..32c697af57 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -4206,8 +4206,6 @@ "navbar_dropdown.invitePeople": "Invite People", "navbar_dropdown.invitePeopleExtraText": "Add people to the team", "navbar_dropdown.join": "Join Another Team", - "navbar_dropdown.learnMore": "Learn More", - "navbar_dropdown.learnMoreTrialBenefits": "Learn More", "navbar_dropdown.leave": "Leave Team", "navbar_dropdown.leave.icon": "Leave Team Icon", "navbar_dropdown.logout": "Log Out", @@ -4218,10 +4216,9 @@ "navbar_dropdown.nativeApps": "Download Apps", "navbar_dropdown.profileSettings": "Profile", "navbar_dropdown.report": "Report a Problem", - "navbar_dropdown.reviewTrialBenefits": "Review the features you get with Enterprise. ", + "navbar_dropdown.startAnEnterpriseTrial": "Start an Enterprise trial", "navbar_dropdown.switchTo": "Switch to ", "navbar_dropdown.teamSettings": "Team Settings", - "navbar_dropdown.tryTrialNow": "Try Enterprise for free now!", "navbar_dropdown.userGroups": "User Groups", "navbar_dropdown.userGroups.modal.messageAdminPostTrial": "User groups are a way to organize users and apply actions to all users within that group. Upgrade to the Professional plan to create unlimited user groups.", "navbar_dropdown.userGroups.modal.messageAdminPreTrial": "Create unlimited user groups with one of our paid plans. Get the full experience of Enterprise when you start a free, {trialLength} day trial.", @@ -4230,6 +4227,8 @@ "navbar_dropdown.userGroups.modal.titleAdminPreTrial": "Try unlimited user groups with a free trial", "navbar_dropdown.userGroups.modal.titleEndUser": "User groups available in paid plans", "navbar_dropdown.userGroups.tooltip.cloudFreeTrial": "During your trial you are able to create user groups. These user groups will be archived after your trial.", + "navbar_dropdown.versionTextE0": "This is the free edition of Mattermost, ideal for evaluation and small teams.", + "navbar_dropdown.versionTextTeamEdition": "This is the free open source edition of Mattermost, ideal for evaluation and small teams.", "navbar_dropdown.viewMembers": "View Members", "navbar.addGroups": "Add Groups", "navbar.addMembers": "Add Members",