mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
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>
This commit is contained in:
parent
81ef403230
commit
812689030b
@ -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": "It’ll 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user