Mm 50013 - readd invite members screen cloud version (#22971)

* MM-50013-readd-invite-members-screen-cloud-version

* add the copy invite and fade in the skip buttons

* unify cloud and self hosted version

* add logic to send emails, and finish launching workspace

* fix styles

* remove unintended change

* fix translations

* fix unit tests

* fix styles

* fix styles

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Pablo Andrés Vélez Vidal
2023-04-26 18:09:22 +02:00
коммит произвёл GitHub
родитель 81ef403230
Коммит 812689030b
13 изменённых файлов: 516 добавлений и 117 удалений

Просмотреть файл

@@ -71,7 +71,145 @@ exports[`InviteMembers component should match snapshot 1`] = `
</div>
<div
class="PageLine PageLine--no-left"
style="margin-top: 50px; margin-left: 50px; height: calc(30vh);"
style="margin-top: 50px; margin-left: 50px; height: calc(35vh);"
/>
</div>
</div>
</div>
</div>
`;
exports[`InviteMembers component should match snapshot when it is cloud 1`] = `
<div>
<div
class="InviteMembers-body test-class"
>
<div
class="SingleColumnLayout"
style="width: 547px;"
>
<div>
<div
class="PageLine PageLine--no-left"
style="margin-bottom: 50px; margin-left: 50px; height: calc(25vh);"
/>
<div>
Previous step
</div>
<h1
class="PreparingWorkspaceTitle"
>
<span>
Who works with you?
</span>
</h1>
<p
class="PreparingWorkspaceDescription"
>
<span>
Collaboration is tough by yourself. Invite a few team members. Separate each email address with a space or comma.
</span>
</p>
<div
class="PreparingWorkspacePageBody"
>
<div
class="UsersEmailsInput empty no-selections css-2b097c-container"
>
<span
aria-live="assertive"
class="css-1laao21-a11yText"
>
<p
id="aria-selection-event"
>
 
</p>
<p
id="aria-context"
>
 
0 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu.
</p>
</span>
<div
class="users-emails-input__control users-emails-input__control--is-focused users-emails-input__control--menu-is-open css-1pahdxg-control"
>
<div
class="users-emails-input__value-container users-emails-input__value-container--is-multi css-1hwfws3"
>
<div
class="users-emails-input__placeholder css-1lxtzh0-placeholder"
>
Enter email addresses
</div>
<div
class="css-gftnqw-Input"
>
<div
class="users-emails-input__input"
style="display: inline-block;"
>
<input
aria-autocomplete="list"
aria-label="Invite People"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
id="react-select-2-input"
spellcheck="false"
style="box-sizing: content-box; width: 2px; border: 0px; opacity: 1; outline: 0; padding: 0px;"
tabindex="0"
type="text"
value=""
/>
<div
style="position: absolute; top: 0px; left: 0px; visibility: hidden; height: 0px; overflow: scroll; white-space: pre; font-family: -webkit-small-control; letter-spacing: normal; text-transform: none;"
/>
</div>
</div>
</div>
</div>
<div
class="users-emails-input__menu css-26l3qy-menu"
>
<div
class="users-emails-input__menu-list users-emails-input__menu-list--is-multi css-4ljt47-MenuList"
/>
</div>
</div>
</div>
<div
class="InviteMembers__submit"
>
<button
class="primary-button"
disabled=""
>
<span>
Send invites
</span>
</button>
<div
class="InviteMembersLink"
>
<button
class="InviteMembersLink__button--single"
data-testid="shareLinkInputButton"
>
<i
class="icon icon-content-copy"
/>
<span>
Copy Link
</span>
</button>
</div>
</div>
<div
class="PageLine PageLine--no-left"
style="margin-top: 50px; margin-left: 50px; height: calc(35vh);"
/>
</div>
</div>

Просмотреть файл

@@ -1,6 +1,26 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/preparing-workspace/invite_members_link should match snapshot 1`] = `
<div>
<div
class="InviteMembersLink"
>
<button
class="InviteMembersLink__button--single"
data-testid="shareLinkInputButton"
>
<i
class="icon icon-content-copy"
/>
<span>
Copy Link
</span>
</button>
</div>
</div>
`;
exports[`components/preparing-workspace/invite_members_link should match snapshot when displayed including the input field 1`] = `
<div>
<div
class="InviteMembersLink"

Просмотреть файл

@@ -1,23 +1,70 @@
@import 'utils/mixins';
// UX decision to show no more than about 5 1/4 lines of users/emails at a time.
$less-than-6-user-lines-height: 227px;
.InviteMembers-body {
display: flex;
// page width - channels preview width - progress dots width - people overlap width
max-width: calc(100vw - 600px - 120px - 30px);
.PreparingWorkspaceDescription span {
display: inline-block;
max-width: 530px;
}
.UsersEmailsInput {
max-width: 420px;
max-width: 540px;
&.no-selections {
// placeholder and input position is difficult to change.
// This overrides the default positioning of the input & placeholders
// to make the taller than normal input look ok when nothing has yet been selected
.users-emails-input__value-container {
margin-top: 6px;
}
}
.users-emails-input__control {
overflow: auto;
min-height: 108px;
max-height: $less-than-6-user-lines-height;
align-items: flex-start;
}
}
}
.InviteMembers {
&__submit {
display: flex;
width: 400px;
align-items: center;
justify-content: flex-start;
}
}
.InviteMembersLink__button--single {
width: fit-content;
min-width: 148px;
margin-right: 8px;
margin-left: 12px;
}
.fade-in-skip-button {
animation: fade-in 2s forwards;
opacity: 0;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@include simple-in-and-out-before("InviteMembers");
.ChannelsPreview--enter-from-after {

Просмотреть файл

@@ -9,6 +9,7 @@ import InviteMembers from './invite_members';
describe('InviteMembers component', () => {
let defaultProps: ComponentProps<any>;
const setEmailsFn = jest.fn();
beforeEach(() => {
defaultProps = {
@@ -21,8 +22,12 @@ describe('InviteMembers component', () => {
onPageView: jest.fn(),
previous: <div>{'Previous step'}</div>,
next: jest.fn(),
setEmails: setEmailsFn,
show: true,
transitionDirection: 'forward',
inferredProtocol: null,
isSelfHosted: true,
emails: [],
};
});
@@ -32,6 +37,17 @@ describe('InviteMembers component', () => {
expect(container).toMatchSnapshot();
});
it('should match snapshot when it is cloud', () => {
const component = withIntl(
<InviteMembers
{...defaultProps}
isSelfHosted={false}
/>,
);
const {container} = render(component);
expect(container).toMatchSnapshot();
});
it('renders invite URL', () => {
const component = withIntl(<InviteMembers {...defaultProps}/>);
render(component);
@@ -68,4 +84,16 @@ describe('InviteMembers component', () => {
fireEvent.click(button);
expect(defaultProps.next).toHaveBeenCalled();
});
it('shows send invites button when in cloud', () => {
const component = withIntl(
<InviteMembers
{...defaultProps}
isSelfHosted={false}
/>,
);
render(component);
const button = screen.getByRole('button', {name: 'Send invites'});
expect(button).toBeInTheDocument();
});
});

Просмотреть файл

@@ -1,9 +1,16 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useMemo, useEffect} from 'react';
import React, {useState, useMemo, useEffect} from 'react';
import {CSSTransition} from 'react-transition-group';
import {FormattedMessage} from 'react-intl';
import {FormattedMessage, useIntl} from 'react-intl';
import {UserProfile} from '@mattermost/types/users';
import {t} from 'utils/i18n';
import {Constants} from 'utils/constants';
import UsersEmailsInput from 'components/widgets/inputs/users_emails_input';
import {Animations, mapAnimationReasonToClass, Form, PreparingWorkspacePageProps} from './steps';
@@ -12,20 +19,30 @@ import Description from './description';
import PageBody from './page_body';
import SingleColumnLayout from './single_column_layout';
import InviteMembersLink from './invite_members_link';
import PageLine from './page_line';
import InviteMembersLink from './invite_members_link';
import './invite_members.scss';
type Props = PreparingWorkspacePageProps & {
disableEdits: boolean;
className?: string;
teamInviteId?: string;
emails: Form['teamMembers']['invites'];
setEmails: (emails: Form['teamMembers']['invites']) => void;
teamInviteId: string;
formUrl: Form['url'];
configSiteUrl?: string;
browserSiteUrl: string;
inferredProtocol: 'http' | 'https' | null;
isSelfHosted: boolean;
show: boolean;
}
const InviteMembers = (props: Props) => {
const [email, setEmail] = useState('');
const [showSkipButton, setShowSkipButton] = useState(false);
const {formatMessage} = useIntl();
let className = 'InviteMembers-body';
if (props.className) {
className += ' ' + props.className;
@@ -33,6 +50,30 @@ const InviteMembers = (props: Props) => {
useEffect(props.onPageView, []);
useEffect(() => {
setShowSkipButton(false);
const timer = setTimeout(() => {
setShowSkipButton(true);
}, 3000);
return () => clearTimeout(timer);
}, [props.show]);
const placeholder = formatMessage({
id: 'onboarding_wizard.invite_members.placeholder',
defaultMessage: 'Enter email addresses',
});
const errorProperties = {
showError: false,
errorMessageId: t(
'invitation_modal.invite_members.exceeded_max_add_members_batch',
),
errorMessageDefault: 'No more than **{text}** people can be invited at once',
errorMessageValues: {
text: Constants.MAX_ADD_MEMBERS_BATCH.toString(),
},
};
const inviteURL = useMemo(() => {
let urlBase = '';
if (props.configSiteUrl && !props.configSiteUrl.includes('localhost')) {
@@ -45,14 +86,128 @@ const InviteMembers = (props: Props) => {
return `${urlBase}/signup_user_complete/?id=${props.teamInviteId}`;
}, [props.teamInviteId, props.configSiteUrl, props.browserSiteUrl, props.formUrl]);
const description = (
<FormattedMessage
id={'onboarding_wizard.invite_members.description_link'}
defaultMessage='Collaboration is tough by yourself. Invite a few team members using the invitation link below.'
let suppressNoOptionsMessage = true;
if (props.emails?.length > Constants.MAX_ADD_MEMBERS_BATCH) {
errorProperties.showError = true;
// We want to suppress the no options message, unless the message that is going to be displayed
// is the max users warning
suppressNoOptionsMessage = false;
}
const cloudInviteMembersInput = (
<UsersEmailsInput
{...errorProperties}
usersLoader={() => Promise.resolve([])}
placeholder={placeholder}
ariaLabel={formatMessage({
id: 'invitation_modal.members.search_and_add.title',
defaultMessage: 'Invite People',
})}
onChange={(emails: Array<UserProfile | string>) => {
// There should not be any users found or passed,
// because the usersLoader should never return any.
// Filtering them out in case there are any
// and to resolve Typescript errors
props.setEmails(emails.filter((x) => typeof x === 'string') as string[]);
}}
value={props.emails}
onInputChange={setEmail}
inputValue={email}
emailInvitationsEnabled={true}
autoFocus={true}
validAddressMessageId={t('invitation_modal.members.users_emails_input.valid_email')}
validAddressMessageDefault={'Invite **{email}** as a team member'}
suppressNoOptionsMessage={suppressNoOptionsMessage}
/>
);
const inviteInteraction = <InviteMembersLink inviteURL={inviteURL}/>;
const inviteLink = (
<InviteMembersLink
inviteURL={inviteURL}
inputAndButtonStyle={props.isSelfHosted}
/>
);
const inviteMemberBodyContent = () => {
if (props.isSelfHosted) {
return (
<>
<Title>
<FormattedMessage
id={'onboarding_wizard.invite_members.title'}
defaultMessage='Invite your team members'
/>
</Title>
<Description>
<FormattedMessage
id={'onboarding_wizard.invite_members.description_link'}
defaultMessage='Collaboration is tough by yourself. Invite a few team members using the invitation link below.'
/>
</Description>
<PageBody>
{inviteLink}
</PageBody>
<div className='InviteMembers__submit'>
<button
className='primary-button'
disabled={props.disableEdits}
onClick={props.next}
>
<FormattedMessage
id={'onboarding_wizard.invite_members.next_link'}
defaultMessage='Finish setup'
/>
</button>
</div>
</>
);
}
return (
<>
<Title>
<FormattedMessage
id={'onboarding_wizard.invite_members_cloud.title'}
defaultMessage='Who works with you?'
/>
</Title>
<Description>
<FormattedMessage
id={'onboarding_wizard.invite_members.description'}
defaultMessage='Collaboration is tough by yourself. Invite a few team members. Separate each email address with a space or comma.'
/>
</Description>
<PageBody>
{cloudInviteMembersInput}
</PageBody>
<div className='InviteMembers__submit'>
<button
className='primary-button'
disabled={props.disableEdits || props.emails.length === 0}
onClick={props.next}
>
<FormattedMessage
id={'onboarding_wizard.invite_members.next'}
defaultMessage='Send invites'
/>
</button>
{inviteLink}
{showSkipButton &&
<button
className='link-style fade-in-skip-button'
onClick={props.skip}
>
<FormattedMessage
id={'onboarding_wizard.invite_members.skip'}
defaultMessage='Skip'
/>
</button>
}
</div>
</>
);
};
return (
<CSSTransition
@@ -73,35 +228,12 @@ const InviteMembers = (props: Props) => {
noLeft={true}
/>
{props.previous}
<Title>
<FormattedMessage
id={'onboarding_wizard.invite_members.title'}
defaultMessage='Invite your team members'
/>
</Title>
<Description>
{description}
</Description>
<PageBody>
{inviteInteraction}
</PageBody>
<div className='InviteMembers__submit'>
<button
className='primary-button'
disabled={props.disableEdits}
onClick={props.next}
>
<FormattedMessage
id={'onboarding_wizard.invite_members.next_link'}
defaultMessage='Finish setup'
/>
</button>
</div>
{inviteMemberBodyContent()}
<PageLine
style={{
marginTop: '50px',
marginLeft: '50px',
height: 'calc(30vh)',
height: 'calc(35vh)',
}}
noLeft={true}
/>

Просмотреть файл

@@ -1,3 +1,5 @@
@import 'utils/mixins';
.InviteMembersLink {
display: flex;
@@ -9,35 +11,21 @@
border-right: 0;
border-bottom: 1px solid rgba(var(--center-channel-color-rgb), 0.2);
border-left: 1px solid rgba(var(--center-channel-color-rgb), 0.2);
background: rgba(var(--center-channel-color-rgb), 0.04);
background: var(--center-channel-color-rgb);
border-radius: 4px 0 0 4px;
color: rgba(var(--center-channel-color-rgb), 0.56);
font-size: 16px;
}
&__button {
button {
display: flex;
width: 180px;
max-width: 382px;
height: 48px;
flex-grow: 0;
align-items: center;
justify-content: center;
border: 1px solid var(--button-bg);
background: var(--center-channel-bg);
border-radius: 0 4px 4px 0;
color: var(--button-bg);
font-size: 16px;
font-weight: 600;
&:hover {
background: rgba(var(--button-bg-rgb), 0.08);
}
&:active {
background: rgba(var(--button-bg-rgb), 0.08);
}
span {
display: inline-block;
height: 24px;
@@ -48,4 +36,28 @@
fill: var(--button-bg);
}
}
&__button {
width: 180px;
height: 48px;
border: 1px solid var(--button-bg);
background: var(--center-channel-bg);
border-radius: 0 4px 4px 0;
color: var(--button-bg);
&:hover {
background: rgba(var(--button-bg-rgb), 0.08);
}
&:active {
background: rgba(var(--button-bg-rgb), 0.08);
}
}
&__button--single {
@include tertiary-button;
height: 40px;
border-radius: 4px;
}
}

Просмотреть файл

@@ -21,8 +21,39 @@ describe('components/preparing-workspace/invite_members_link', () => {
expect(container).toMatchSnapshot();
});
it('should match snapshot when displayed including the input field', () => {
const component = withIntl(
<InviteMembersLink
inviteURL={inviteURL}
inputAndButtonStyle={true}
/>,
);
const {container} = render(component);
expect(container).toMatchSnapshot();
});
it('renders only with the button if the inputAndButton option is false', () => {
const component = withIntl(
<InviteMembersLink
inviteURL={inviteURL}
inputAndButtonStyle={false}
/>,
);
render(component);
const input = screen.queryByText(inviteURL);
expect(input).not.toBeInTheDocument();
const button = screen.getByRole('button', {name: /copy link/i});
expect(button).toBeInTheDocument();
});
it('renders an input field with the invite URL', () => {
const component = withIntl(<InviteMembersLink inviteURL={inviteURL}/>);
const component = withIntl(
<InviteMembersLink
inviteURL={inviteURL}
inputAndButtonStyle={true}
/>,
);
render(component);
const input = screen.getByDisplayValue(inviteURL);
expect(input).toBeInTheDocument();

Просмотреть файл

@@ -11,30 +11,36 @@ import './invite_members_link.scss';
type Props = {
inviteURL: string;
inputAndButtonStyle?: boolean;
}
const InviteMembersLink = (props: Props) => {
const InviteMembersLink = ({
inviteURL,
inputAndButtonStyle = false,
}: Props) => {
const copyText = useCopyText({
trackCallback: () => trackEvent('first_admin_setup', 'admin_setup_click_copy_invite_link'),
text: props.inviteURL,
text: inviteURL,
});
const intl = useIntl();
return (
<div className='InviteMembersLink'>
<input
className='InviteMembersLink__input'
type='text'
readOnly={true}
value={props.inviteURL}
aria-label={intl.formatMessage({
id: 'onboarding_wizard.invite_members.copy_link_input',
defaultMessage: 'team invite link',
})}
data-testid='shareLinkInput'
/>
{inputAndButtonStyle &&
<input
className='InviteMembersLink__input'
type='text'
readOnly={true}
value={inviteURL}
aria-label={intl.formatMessage({
id: 'onboarding_wizard.invite_members.copy_link_input',
defaultMessage: 'team invite link',
})}
data-testid='shareLinkInput'
/>
}
<button
className='InviteMembersLink__button'
className={`InviteMembersLink__button${inputAndButtonStyle ? '' : '--single'}`}
onClick={copyText.onClick}
data-testid='shareLinkInputButton'
>
@@ -48,7 +54,7 @@ const InviteMembersLink = (props: Props) => {
</>
) : (
<>
<i className='icon icon-link-variant'/>
{inputAndButtonStyle ? <i className='icon icon-link-variant'/> : <i className='icon icon-content-copy'/>}
<FormattedMessage
id='onboarding_wizard.invite_members.copy_link'
defaultMessage='Copy Link'

Просмотреть файл

@@ -10,7 +10,6 @@ import MultiSelectCards from 'components/common/multi_select_cards';
import GithubSVG from 'components/common/svg_images_components/github_svg';
import GitlabSVG from 'components/common/svg_images_components/gitlab_svg';
import CelebrateSVG from 'components/common/svg_images_components/celebrate_svg';
import JiraSVG from 'components/common/svg_images_components/jira_svg';
import ZoomSVG from 'components/common/svg_images_components/zoom_svg';
import TodoSVG from 'components/common/svg_images_components/todo_svg';
@@ -47,32 +46,19 @@ const Plugins = (props: Props) => {
className += ' ' + props.className;
}
let title = (
const title = (
<FormattedMessage
id={'onboarding_wizard.cloud_plugins.title'}
defaultMessage='Welcome to Mattermost!'
id={'onboarding_wizard.self_hosted_plugins.title'}
defaultMessage='What tools do you use?'
/>
);
let description = (
const description = (
<FormattedMessage
id={'onboarding_wizard.cloud_plugins.description'}
defaultMessage={'Mattermost is better when integrated with the tools your team uses for collaboration. Popular tools are below, select the ones your team uses and we\'ll add them to your workspace. Additional set up may be needed later.'}
id={'onboarding_wizard.self_hosted_plugins.description'}
defaultMessage={'Choose the tools you work with, and we\'ll add them to your workspace. Additional set up may be needed later.'}
/>
);
if (props.isSelfHosted) {
title = (
<FormattedMessage
id={'onboarding_wizard.self_hosted_plugins.title'}
defaultMessage='What tools do you use?'
/>
);
description = (
<FormattedMessage
id={'onboarding_wizard.self_hosted_plugins.description'}
defaultMessage={'Choose the tools you work with, and we\'ll add them to your workspace. Additional set up may be needed later.'}
/>
);
}
return (
<CSSTransition
@@ -95,16 +81,6 @@ const Plugins = (props: Props) => {
{props.previous}
<Title>
{title}
{!props.isSelfHosted && (
<div className='subtitle'>
<CelebrateSVG/>
<FormattedMessage
id={'onboarding_wizard.cloud_plugins.subtitle'}
defaultMessage='(almost there!)'
/>
</div>
)}
</Title>
<Description>{description}</Description>
<PageBody>

Просмотреть файл

@@ -133,6 +133,7 @@
// centering
margin: 0 auto;
margin-left: 300px;
}
@media screen and (max-width: 768px) {

Просмотреть файл

@@ -8,6 +8,7 @@ import {FormattedMessage, useIntl} from 'react-intl';
import {GeneralTypes} from 'mattermost-redux/action_types';
import {General} from 'mattermost-redux/constants';
import {sendEmailInvitesToTeamGracefully} from 'mattermost-redux/actions/teams';
import {getFirstAdminSetupComplete as getFirstAdminSetupCompleteAction} from 'mattermost-redux/actions/general';
import {ActionResult} from 'mattermost-redux/types/actions';
import {Team} from '@mattermost/types/teams';
@@ -114,18 +115,19 @@ const PreparingWorkspace = (props: Props) => {
// In cloud instances created from portal,
// new admin user has a team in myTeams but not in currentTeam.
let team = currentTeam || myTeams?.[0];
const team = currentTeam || myTeams?.[0];
const config = useSelector(getConfig);
const pluginsEnabled = config.PluginsEnabled === 'true';
const showOnMountTimeout = useRef<NodeJS.Timeout>();
const configSiteUrl = config.SiteURL;
const isConfigSiteUrlDefault = Boolean(config.SiteURL && config.SiteURL === Constants.DEFAULT_SITE_URL);
const isSelfHosted = useSelector(getLicense).Cloud !== 'true';
const stepOrder = [
isSelfHosted && WizardSteps.Organization,
pluginsEnabled && WizardSteps.Plugins,
isSelfHosted && WizardSteps.InviteMembers,
WizardSteps.InviteMembers,
WizardSteps.LaunchingWorkspace,
].filter((x) => Boolean(x)) as WizardStep[];
@@ -225,16 +227,15 @@ const PreparingWorkspace = (props: Props) => {
const sendFormStart = Date.now();
setSubmissionState(SubmissionStates.Submitting);
if (form.organization && !isSelfHosted) {
if (!form.teamMembers.skipped && !isConfigSiteUrlDefault && !isSelfHosted) {
try {
const {error, newTeam} = await createTeam(form.organization);
if (error !== null) {
redirectWithError(WizardSteps.Organization, genericSubmitError);
const inviteResult = await dispatch(sendEmailInvitesToTeamGracefully(team.id, form.teamMembers.invites));
if ((inviteResult as ActionResult).error) {
redirectWithError(WizardSteps.InviteMembers, genericSubmitError);
return;
}
team = newTeam as Team;
} catch (e) {
redirectWithError(WizardSteps.Organization, genericSubmitError);
redirectWithError(WizardSteps.InviteMembers, genericSubmitError);
return;
}
}
@@ -435,16 +436,10 @@ const PreparingWorkspace = (props: Props) => {
next={() => {
const pluginChoices = {...form.plugins};
delete pluginChoices.skipped;
if (!isSelfHosted) {
setSubmissionState(SubmissionStates.UserRequested);
}
makeNext(WizardSteps.Plugins)(pluginChoices);
skipPlugins(false);
}}
skip={() => {
if (!isSelfHosted) {
setSubmissionState(SubmissionStates.UserRequested);
}
makeNext(WizardSteps.Plugins, true)();
skipPlugins(true);
}}
@@ -489,6 +484,18 @@ const PreparingWorkspace = (props: Props) => {
configSiteUrl={configSiteUrl}
formUrl={form.url}
browserSiteUrl={browserSiteUrl}
emails={form.teamMembers.invites}
setEmails={(emails: string[]) => {
setForm({
...form,
teamMembers: {
...form.teamMembers,
invites: emails,
},
});
}}
inferredProtocol={form.inferredProtocol}
isSelfHosted={isSelfHosted}
/>
<LaunchingWorkspace
onPageView={onPageViews[WizardSteps.LaunchingWorkspace]}

Просмотреть файл

@@ -4318,14 +4318,16 @@
"notify_here.question": "By using **@here** you are about to send notifications to up to **{totalMembers} other people**. Are you sure you want to do this?",
"notify_here.question_timezone": "By using **@here** you are about to send notifications to up to **{totalMembers} other people** in **{timezones, number} {timezones, plural, one {timezone} other {timezones}}**. Are you sure you want to do this?",
"numMembers": "{num, number} {num, plural, one {member} other {members}}",
"onboarding_wizard.cloud_plugins.description": "Mattermost is better when integrated with the tools your team uses for collaboration. Popular tools are below, select the ones your team uses and we'll add them to your workspace. Additional set up may be needed later.",
"onboarding_wizard.cloud_plugins.subtitle": "(almost there!)",
"onboarding_wizard.cloud_plugins.title": "Welcome to Mattermost!",
"onboarding_wizard.invite_members_cloud.title": "Who works with you?",
"onboarding_wizard.invite_members.copied_link": "Link Copied",
"onboarding_wizard.invite_members.copy_link": "Copy Link",
"onboarding_wizard.invite_members.copy_link_input": "team invite link",
"onboarding_wizard.invite_members.description": "Collaboration is tough by yourself. Invite a few team members. Separate each email address with a space or comma.",
"onboarding_wizard.invite_members.description_link": "Collaboration is tough by yourself. Invite a few team members using the invitation link below.",
"onboarding_wizard.invite_members.next": "Send invites",
"onboarding_wizard.invite_members.next_link": "Finish setup",
"onboarding_wizard.invite_members.placeholder": "Enter email addresses",
"onboarding_wizard.invite_members.skip": "Skip",
"onboarding_wizard.invite_members.title": "Invite your team members",
"onboarding_wizard.launching_workspace.description": "Itll be ready in a moment",
"onboarding_wizard.launching_workspace.title": "Launching your workspace now",

Просмотреть файл

@@ -19,7 +19,6 @@ body {
&.admin-onboarding {
background-image: url('images/admin-onboarding-background.jpg');
background-position: 50%;
background-repeat: no-repeat;
background-size: cover;
}