summaryrefslogtreecommitdiffstats
path: root/web/react/components/signup_team_complete/components
diff options
context:
space:
mode:
Diffstat (limited to 'web/react/components/signup_team_complete/components')
-rw-r--r--web/react/components/signup_team_complete/components/signup_team_complete.jsx79
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx136
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_email_item.jsx86
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_finished.jsx15
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_password_page.jsx215
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx210
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_url_page.jsx205
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_username_page.jsx164
-rw-r--r--web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx234
9 files changed, 1344 insertions, 0 deletions
diff --git a/web/react/components/signup_team_complete/components/signup_team_complete.jsx b/web/react/components/signup_team_complete/components/signup_team_complete.jsx
new file mode 100644
index 000000000..5ad21e941
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/signup_team_complete.jsx
@@ -0,0 +1,79 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import BrowserStore from '../../../stores/browser_store.jsx';
+
+import {FormattedMessage} from 'mm-intl';
+
+import {browserHistory} from 'react-router';
+
+export default class SignupTeamComplete extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateParent = this.updateParent.bind(this);
+ }
+ componentWillMount() {
+ const data = JSON.parse(this.props.location.query.d);
+ this.hash = this.props.location.query.h;
+
+ var initialState = BrowserStore.getGlobalItem(this.hash);
+
+ if (!initialState) {
+ initialState = {};
+ initialState.wizard = 'welcome';
+ initialState.team = {};
+ initialState.team.email = data.email;
+ initialState.team.allowed_domains = '';
+ initialState.invites = [];
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.invites.push('');
+ initialState.user = {};
+ initialState.hash = this.hash;
+ initialState.data = this.props.location.query.d;
+ }
+
+ this.setState(initialState);
+ }
+ componentDidMount() {
+ browserHistory.push('/signup_team_complete/welcome');
+ }
+ updateParent(state, skipSet) {
+ BrowserStore.setGlobalItem(this.hash, state);
+
+ if (!skipSet) {
+ this.setState(state);
+ browserHistory.push('/signup_team_complete/' + state.wizard);
+ }
+ }
+ render() {
+ return (
+ <div>
+ <div className='signup-header'>
+ <a href='/'>
+ <span classNameName='fa fa-chevron-left'/>
+ <FormattedMessage id='web.header.back'/>
+ </a>
+ </div>
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <div id='signup-team-complete'>
+ {React.cloneElement(this.props.children, {
+ state: this.state,
+ updateParent: this.updateParent
+ })}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+SignupTeamComplete.defaultProps = {
+};
+SignupTeamComplete.propTypes = {
+ location: React.PropTypes.object,
+ children: React.PropTypes.node
+};
diff --git a/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx b/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx
new file mode 100644
index 000000000..280e53ce4
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_display_name_page.jsx
@@ -0,0 +1,136 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as utils from '../../../utils/utils.jsx';
+import * as client from '../../../utils/client.jsx';
+
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ required: {
+ id: 'team_signup_display_name.required',
+ defaultMessage: 'This field is required'
+ },
+ charLength: {
+ id: 'team_signup_display_name.charLength',
+ defaultMessage: 'Name must be 4 or more characters up to a maximum of 15'
+ }
+});
+
+class TeamSignupDisplayNamePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
+ e.preventDefault();
+ this.props.state.wizard = 'welcome';
+ this.props.updateParent(this.props.state);
+ }
+ submitNext(e) {
+ e.preventDefault();
+
+ const {formatMessage} = this.props.intl;
+ var displayName = ReactDOM.findDOMNode(this.refs.name).value.trim();
+ if (!displayName) {
+ this.setState({nameError: formatMessage(holders.required)});
+ return;
+ } else if (displayName.length < 4 || displayName.length > 15) {
+ this.setState({nameError: formatMessage(holders.charLength)});
+ return;
+ }
+
+ this.props.state.wizard = 'team_url';
+ this.props.state.team.display_name = displayName;
+ this.props.state.team.name = utils.cleanUpUrlable(displayName);
+ this.props.updateParent(this.props.state);
+ }
+ handleFocus(e) {
+ e.preventDefault();
+ e.currentTarget.select();
+ }
+ render() {
+ client.track('signup', 'signup_team_02_name');
+
+ var nameError = null;
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
+
+ return (
+ <div>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2>
+ <FormattedMessage
+ id='team_signup_display_name.teamName'
+ defaultMessage='Team Name'
+ />
+ </h2>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-9'>
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.display_name}
+ autoFocus={true}
+ onFocus={this.handleFocus}
+ spellCheck='false'
+ />
+ </div>
+ </div>
+ {nameError}
+ </div>
+ <div>
+ <FormattedMessage
+ id='team_signup_display_name.nameHelp'
+ defaultMessage='Name your team in any language. Your team name shows in menus and headings.'
+ />
+ </div>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ <FormattedMessage
+ id='team_signup_display_name.next'
+ defaultMessage='Next'
+ /><i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
+ <div className='margin--extra'>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ <FormattedMessage
+ id='team_signup_display_name.back'
+ defaultMessage='Back to previous step'
+ />
+ </a>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
+
+TeamSignupDisplayNamePage.propTypes = {
+ intl: intlShape.isRequired,
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
+
+export default injectIntl(TeamSignupDisplayNamePage);
diff --git a/web/react/components/signup_team_complete/components/team_signup_email_item.jsx b/web/react/components/signup_team_complete/components/team_signup_email_item.jsx
new file mode 100644
index 000000000..c87d6ec07
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_email_item.jsx
@@ -0,0 +1,86 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../../utils/utils.jsx';
+
+import {intlShape, injectIntl, defineMessages} from 'mm-intl';
+
+const holders = defineMessages({
+ validEmail: {
+ id: 'team_signup_email.validEmail',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ different: {
+ id: 'team_signup_email.different',
+ defaultMessage: 'Please use a different email than the one used at signup'
+ },
+ address: {
+ id: 'team_signup_email.address',
+ defaultMessage: 'Email Address'
+ }
+});
+
+class TeamSignupEmailItem extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.getValue = this.getValue.bind(this);
+ this.validate = this.validate.bind(this);
+
+ this.state = {};
+ }
+ getValue() {
+ return ReactDOM.findDOMNode(this.refs.email).value.trim();
+ }
+ validate(teamEmail) {
+ const {formatMessage} = this.props.intl;
+ const email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
+
+ if (!email) {
+ return true;
+ }
+
+ if (!Utils.isEmail(email)) {
+ this.setState({emailError: formatMessage(holders.validEmail)});
+ return false;
+ } else if (email === teamEmail) {
+ this.setState({emailError: formatMessage(holders.different)});
+ return false;
+ }
+
+ this.setState({emailError: ''});
+ return true;
+ }
+ render() {
+ let emailError = null;
+ let emailDivClass = 'form-group';
+ if (this.state.emailError) {
+ emailError = <label className='control-label'>{this.state.emailError}</label>;
+ emailDivClass += ' has-error';
+ }
+
+ return (
+ <div className={emailDivClass}>
+ <input
+ autoFocus={this.props.focus}
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder={this.props.intl.formatMessage(holders.address)}
+ defaultValue={this.props.email}
+ maxLength='128'
+ spellCheck='false'
+ />
+ {emailError}
+ </div>
+ );
+ }
+}
+
+TeamSignupEmailItem.propTypes = {
+ intl: intlShape.isRequired,
+ focus: React.PropTypes.bool,
+ email: React.PropTypes.string
+};
+
+export default injectIntl(TeamSignupEmailItem, {withRef: true});
diff --git a/web/react/components/signup_team_complete/components/team_signup_finished.jsx b/web/react/components/signup_team_complete/components/team_signup_finished.jsx
new file mode 100644
index 000000000..fc5f756e7
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_finished.jsx
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import {FormattedMessage} from 'mm-intl';
+
+export default class FinishedPage extends React.Component {
+ render() {
+ return (
+ <FormattedMessage
+ id='signup_team_complete.completed'
+ defaultMessage="You've already completed the signup process for this invitation or this invitation has expired."
+ />
+ );
+ }
+}
diff --git a/web/react/components/signup_team_complete/components/team_signup_password_page.jsx b/web/react/components/signup_team_complete/components/team_signup_password_page.jsx
new file mode 100644
index 000000000..490a11040
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_password_page.jsx
@@ -0,0 +1,215 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Client from '../../../utils/client.jsx';
+import BrowserStore from '../../../stores/browser_store.jsx';
+import UserStore from '../../../stores/user_store.jsx';
+import Constants from '../../../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+import {browserHistory} from 'react-router';
+
+const holders = defineMessages({
+ passwordError: {
+ id: 'team_signup_password.passwordError',
+ defaultMessage: 'Please enter at least {chars} characters'
+ },
+ creating: {
+ id: 'team_signup_password.creating',
+ defaultMessage: 'Creating team...'
+ }
+});
+
+class TeamSignupPasswordPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
+ e.preventDefault();
+ this.props.state.wizard = 'username';
+ this.props.updateParent(this.props.state);
+ }
+ submitNext(e) {
+ e.preventDefault();
+
+ var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
+ if (!password || password.length < Constants.MIN_PASSWORD_LENGTH) {
+ this.setState({passwordError: this.props.intl.formatMessage(holders.passwordError, {chars: Constants.MIN_PASSWORD_LENGTH})});
+ return;
+ }
+
+ this.setState({passwordError: null, serverError: null});
+ $('#finish-button').button('loading');
+ var teamSignup = JSON.parse(JSON.stringify(this.props.state));
+ teamSignup.user.password = password;
+ teamSignup.user.allow_marketing = true;
+ delete teamSignup.wizard;
+
+ Client.createTeamFromSignup(teamSignup,
+ () => {
+ Client.track('signup', 'signup_team_08_complete');
+
+ var props = this.props;
+
+ Client.loginByEmail(teamSignup.team.name, teamSignup.team.email, teamSignup.user.password,
+ () => {
+ UserStore.setLastEmail(teamSignup.team.email);
+ if (this.props.hash > 0) {
+ BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'}));
+ }
+
+ $('#sign-up-button').button('reset');
+ props.state.wizard = 'finished';
+ props.updateParent(props.state, true);
+
+ browserHistory.push('/' + teamSignup.team.name + '/channels/town-square');
+ },
+ (err) => {
+ if (err.id === 'api.user.login.not_verified.app_error') {
+ browserHistory.push('/verify_email?email=' + encodeURIComponent(teamSignup.team.email) + '&teamname=' + encodeURIComponent(teamSignup.team.name));
+ } else {
+ this.setState({serverError: err.message});
+ $('#finish-button').button('reset');
+ }
+ }
+ );
+ },
+ (err) => {
+ this.setState({serverError: err.message});
+ $('#finish-button').button('reset');
+ }
+ );
+ }
+ render() {
+ Client.track('signup', 'signup_team_07_password');
+
+ var passwordError = null;
+ var passwordDivStyle = 'form-group';
+ if (this.state.passwordError) {
+ passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>;
+ passwordDivStyle = ' has-error';
+ }
+
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ return (
+ <div>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2 className='margin--less'>
+ <FormattedMessage
+ id='team_signup_password.yourPassword'
+ defaultMessage='Your password'
+ />
+ </h2>
+ <h5 className='color--light'>
+ <FormattedMessage
+ id='team_signup_password.selectPassword'
+ defaultMessage="Select a password that you'll use to login with your email address:"
+ />
+ </h5>
+ <div className='inner__content margin--extra'>
+ <h5><strong>
+ <FormattedMessage
+ id='team_signup_password.email'
+ defaultMessage='Email'
+ />
+ </strong></h5>
+ <div className='block--gray form-group'>{this.props.state.team.email}</div>
+ <div className={passwordDivStyle}>
+ <div className='row'>
+ <div className='col-sm-11'>
+ <h5><strong>
+ <FormattedMessage
+ id='team_signup_password.choosePwd'
+ defaultMessage='Choose your password'
+ />
+ </strong></h5>
+ <input
+ autoFocus={true}
+ type='password'
+ ref='password'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ spellCheck='false'
+ />
+ <span className='color--light help-block'>
+ <FormattedMessage
+ id='team_signup_password.hint'
+ defaultMessage='Passwords must contain {min} to {max} characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.'
+ values={{
+ min: Constants.MIN_PASSWORD_LENGTH,
+ max: Constants.MAX_PASSWORD_LENGTH
+ }}
+ />
+ </span>
+ </div>
+ </div>
+ {passwordError}
+ {serverError}
+ </div>
+ </div>
+ <div className='form-group'>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ id='finish-button'
+ data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> ' + this.props.intl.formatMessage(holders.creating)}
+ onClick={this.submitNext}
+ >
+ <FormattedMessage
+ id='team_signup_password.finish'
+ defaultMessage='Finish'
+ />
+ </button>
+ </div>
+ <p>
+ <FormattedHTMLMessage
+ id='team_signup_password.agreement'
+ defaultMessage="By proceeding to create your account and use {siteName}, you agree to our <a href='/static/help/terms.html'>Terms of Service</a> and <a href='/static/help/privacy.html'>Privacy Policy</a>. If you do not agree, you cannot use {siteName}."
+ values={{
+ siteName: global.window.mm_config.SiteName
+ }}
+ />
+ </p>
+ <div className='margin--extra'>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ <FormattedMessage
+ id='team_signup_password.back'
+ defaultMessage='Back to previous step'
+ />
+ </a>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
+
+TeamSignupPasswordPage.defaultProps = {
+ state: {},
+ hash: ''
+};
+TeamSignupPasswordPage.propTypes = {
+ intl: intlShape.isRequired,
+ state: React.PropTypes.object,
+ hash: React.PropTypes.string,
+ updateParent: React.PropTypes.func
+};
+
+export default injectIntl(TeamSignupPasswordPage);
diff --git a/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx b/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx
new file mode 100644
index 000000000..5e987ef2c
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_send_invites_page.jsx
@@ -0,0 +1,210 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import EmailItem from './team_signup_email_item.jsx';
+import * as Client from '../../../utils/client.jsx';
+
+import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+export default class TeamSignupSendInvitesPage extends React.Component {
+ constructor(props) {
+ super(props);
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+ this.submitAddInvite = this.submitAddInvite.bind(this);
+ this.submitSkip = this.submitSkip.bind(this);
+ this.keySubmit = this.keySubmit.bind(this);
+ this.state = {
+ emailEnabled: global.window.mm_config.SendEmailNotifications === 'true'
+ };
+ }
+ submitBack(e) {
+ e.preventDefault();
+ this.props.state.wizard = 'team_url';
+
+ this.props.updateParent(this.props.state);
+ }
+ submitNext(e) {
+ e.preventDefault();
+
+ var valid = true;
+
+ if (this.state.emailEnabled) {
+ var emails = [];
+
+ for (var i = 0; i < this.props.state.invites.length; i++) {
+ if (this.refs['email_' + i].getWrappedInstance().validate(this.props.state.team.email)) {
+ emails.push(this.refs['email_' + i].getWrappedInstance().getValue());
+ } else {
+ valid = false;
+ }
+ }
+
+ if (valid) {
+ this.props.state.invites = emails;
+ }
+ }
+
+ if (valid) {
+ this.props.state.wizard = 'username';
+ this.props.updateParent(this.props.state);
+ }
+ }
+ submitAddInvite(e) {
+ e.preventDefault();
+ this.props.state.wizard = 'send_invites';
+ if (!this.props.state.invites) {
+ this.props.state.invites = [];
+ }
+ this.props.state.invites.push('');
+ this.props.updateParent(this.props.state);
+ }
+ submitSkip(e) {
+ e.preventDefault();
+ this.props.state.wizard = 'username';
+ this.props.updateParent(this.props.state);
+ }
+ keySubmit(e) {
+ if (e && e.keyCode === 13) {
+ this.submitNext(e);
+ }
+ }
+ componentDidMount() {
+ if (!this.state.emailEnabled) {
+ // Must use keypress not keyup due to event chain of pressing enter
+ $('body').keypress(this.keySubmit);
+ }
+ }
+ componentWillUnmount() {
+ if (!this.state.emailEnabled) {
+ $('body').off('keypress', this.keySubmit);
+ }
+ }
+ render() {
+ Client.track('signup', 'signup_team_05_send_invites');
+
+ var content = null;
+ var bottomContent = null;
+
+ if (this.state.emailEnabled) {
+ var emails = [];
+
+ for (var i = 0; i < this.props.state.invites.length; i++) {
+ if (i === 0) {
+ emails.push(
+ <EmailItem
+ focus={true}
+ key={i}
+ ref={'email_' + i}
+ email={this.props.state.invites[i]}
+ />
+ );
+ } else {
+ emails.push(
+ <EmailItem
+ focus={false}
+ key={i}
+ ref={'email_' + i}
+ email={this.props.state.invites[i]}
+ />
+ );
+ }
+ }
+
+ content = (
+ <div>
+ {emails}
+ <div className='form-group text-right'>
+ <a
+ href='#'
+ onClick={this.submitAddInvite}
+ >
+ <FormattedMessage
+ id='team_signup_send_invites.addInvitation'
+ defaultMessage='Add Invitation'
+ />
+ </a>
+ </div>
+ </div>
+ );
+
+ bottomContent = (
+ <p className='color--light'>
+ <FormattedHTMLMessage
+ id='team_signup_send_invites.prefer'
+ defaultMessage='if you prefer, you can invite team members later<br /> and '
+ />
+ <a
+ href='#'
+ onClick={this.submitSkip}
+ >
+ <FormattedMessage
+ id='team_signup_send_invites.skip'
+ defaultMessage='skip this step '
+ />
+ </a>
+ <FormattedMessage
+ id='team_signup_send_invites.forNow'
+ defaultMessage='for now.'
+ />
+ </p>
+ );
+ } else {
+ content = (
+ <div className='form-group color--light'>
+ <FormattedMessage
+ id='team_signup_send_invites.disabled'
+ defaultMessage='Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.'
+ />
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2>
+ <FormattedMessage
+ id='team_signup_send_invites.title'
+ defaultMessage='Invite Team Members'
+ />
+ </h2>
+ {content}
+ <div className='form-group'>
+ <button
+ type='submit'
+ className='btn-primary btn'
+ onClick={this.submitNext}
+ >
+ <FormattedMessage
+ id='team_signup_send_invites.next'
+ defaultMessage='Next'
+ /><i className='glyphicon glyphicon-chevron-right'/>
+ </button>
+ </div>
+ </form>
+ {bottomContent}
+ <div className='margin--extra'>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ <FormattedMessage
+ id='team_signup_send_invites.back'
+ defaultMessage='Back to previous step'
+ />
+ </a>
+ </div>
+ </div>
+ );
+ }
+}
+
+TeamSignupSendInvitesPage.propTypes = {
+ state: React.PropTypes.object.isRequired,
+ updateParent: React.PropTypes.func.isRequired
+};
diff --git a/web/react/components/signup_team_complete/components/team_signup_url_page.jsx b/web/react/components/signup_team_complete/components/team_signup_url_page.jsx
new file mode 100644
index 000000000..ec50e2d25
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_url_page.jsx
@@ -0,0 +1,205 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import Constants from '../../../utils/constants.jsx';
+
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ required: {
+ id: 'team_signup_url.required',
+ defaultMessage: 'This field is required'
+ },
+ regex: {
+ id: 'team_signup_url.regex',
+ defaultMessage: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."
+ },
+ charLength: {
+ id: 'team_signup_url.charLength',
+ defaultMessage: 'Name must be 4 or more characters up to a maximum of 15'
+ },
+ taken: {
+ id: 'team_signup_url.taken',
+ defaultMessage: 'URL is taken or contains a reserved word'
+ },
+ unavailable: {
+ id: 'team_signup_url.unavailable',
+ defaultMessage: 'This URL is unavailable. Please try another.'
+ }
+});
+
+class TeamSignupUrlPage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+ this.handleFocus = this.handleFocus.bind(this);
+
+ this.state = {nameError: ''};
+ }
+ submitBack(e) {
+ e.preventDefault();
+ this.props.state.wizard = 'team_display_name';
+ this.props.updateParent(this.props.state);
+ }
+ submitNext(e) {
+ e.preventDefault();
+
+ const {formatMessage} = this.props.intl;
+ const name = ReactDOM.findDOMNode(this.refs.name).value.trim();
+ if (!name) {
+ this.setState({nameError: formatMessage(holders.required)});
+ return;
+ }
+
+ const cleanedName = Utils.cleanUpUrlable(name);
+
+ const urlRegex = /^[a-z]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
+ if (cleanedName !== name || !urlRegex.test(name)) {
+ this.setState({nameError: formatMessage(holders.regex)});
+ return;
+ } else if (cleanedName.length < 4 || cleanedName.length > 15) {
+ this.setState({nameError: formatMessage(holders.charLength)});
+ return;
+ }
+
+ if (global.window.mm_config.RestrictTeamNames === 'true') {
+ for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) {
+ if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) {
+ this.setState({nameError: formatMessage(holders.taken)});
+ return;
+ }
+ }
+ }
+
+ Client.findTeamByName(name,
+ (data) => {
+ if (data) {
+ this.setState({nameError: formatMessage(holders.unavailable)});
+ } else {
+ if (global.window.mm_config.SendEmailNotifications === 'true') {
+ this.props.state.wizard = 'send_invites';
+ } else {
+ this.props.state.wizard = 'username';
+ }
+ this.props.state.team.type = 'O';
+
+ this.props.state.team.name = name;
+ this.props.updateParent(this.props.state);
+ }
+ },
+ (err) => {
+ this.setState({nameError: err.message});
+ }
+ );
+ }
+ handleFocus(e) {
+ e.preventDefault();
+
+ e.currentTarget.select();
+ }
+ render() {
+ $('body').tooltip({selector: '[data-toggle=tooltip]', trigger: 'hover click'});
+
+ Client.track('signup', 'signup_team_03_url');
+
+ let nameError = null;
+ let nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
+
+ const title = `${Utils.getWindowLocationOrigin()}/`;
+
+ return (
+ <div>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2>
+ <FormattedMessage
+ id='team_signup_url.teamUrl'
+ defaultMessage='Team URL'
+ />
+ </h2>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-11'>
+ <div className='input-group input-group--limit'>
+ <span
+ data-toggle='tooltip'
+ title={title}
+ className='input-group-addon'
+ >
+ {title}
+ </span>
+ <input
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ maxLength='128'
+ defaultValue={this.props.state.team.name}
+ autoFocus={true}
+ onFocus={this.handleFocus}
+ spellCheck='false'
+ />
+ </div>
+ </div>
+ </div>
+ {nameError}
+ </div>
+ <p>
+ <FormattedMessage
+ id='team_signup_url.webAddress'
+ defaultMessage='Choose the web address of your new team:'
+ />
+ </p>
+ <ul className='color--light'>
+ <FormattedHTMLMessage
+ id='team_signup_url.hint'
+ defaultMessage="<li>Short and memorable is best</li>
+ <li>Use lowercase letters, numbers and dashes</li>
+ <li>Must start with a letter and can't end in a dash</li>"
+ />
+ </ul>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ <FormattedMessage
+ id='team_signup_url.next'
+ defaultMessage='Next'
+ /><i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
+ <div className='margin--extra'>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ <FormattedMessage
+ id='team_signup_url.back'
+ defaultMessage='Back to previous step'
+ />
+ </a>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
+
+TeamSignupUrlPage.propTypes = {
+ intl: intlShape.isRequired,
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
+
+export default injectIntl(TeamSignupUrlPage);
diff --git a/web/react/components/signup_team_complete/components/team_signup_username_page.jsx b/web/react/components/signup_team_complete/components/team_signup_username_page.jsx
new file mode 100644
index 000000000..e56aa4cd7
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_username_page.jsx
@@ -0,0 +1,164 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import Constants from '../../../utils/constants.jsx';
+
+import {intlShape, injectIntl, defineMessages, FormattedMessage} from 'mm-intl';
+
+const holders = defineMessages({
+ reserved: {
+ id: 'team_signup_username.reserved',
+ defaultMessage: 'This username is reserved, please choose a new one.'
+ },
+ invalid: {
+ id: 'team_signup_username.invalid',
+ defaultMessage: 'Username must begin with a letter, and contain between {min} to {max} characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''
+ }
+});
+
+class TeamSignupUsernamePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitBack = this.submitBack.bind(this);
+ this.submitNext = this.submitNext.bind(this);
+
+ this.state = {};
+ }
+ submitBack(e) {
+ e.preventDefault();
+ if (global.window.mm_config.SendEmailNotifications === 'true') {
+ this.props.state.wizard = 'send_invites';
+ } else {
+ this.props.state.wizard = 'team_url';
+ }
+
+ this.props.updateParent(this.props.state);
+ }
+ submitNext(e) {
+ e.preventDefault();
+
+ const {formatMessage} = this.props.intl;
+ var name = ReactDOM.findDOMNode(this.refs.name).value.trim().toLowerCase();
+
+ var usernameError = Utils.isValidUsername(name);
+ if (usernameError === 'Cannot use a reserved word as a username.') { //this should be change to some kind of ID
+ this.setState({nameError: formatMessage(holders.reserved)});
+ return;
+ } else if (usernameError) {
+ this.setState({nameError: formatMessage(holders.invalid, {min: Constants.MIN_USERNAME_LENGTH, max: Constants.MAX_USERNAME_LENGTH})});
+ return;
+ }
+
+ this.props.state.wizard = 'password';
+ this.props.state.user.username = name;
+ this.props.updateParent(this.props.state);
+ }
+ render() {
+ Client.track('signup', 'signup_team_06_username');
+
+ var nameError = null;
+ var nameHelpText = (
+ <span className='color--light help-block'>
+ <FormattedMessage
+ id='team_signup_username.hint'
+ defaultMessage="Usernames must begin with a letter and contain between {min} to {max} characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'"
+ values={{
+ min: Constants.MIN_USERNAME_LENGTH,
+ max: Constants.MAX_USERNAME_LENGTH
+ }}
+ />
+ </span>
+ );
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameHelpText = '';
+ nameDivClass += ' has-error';
+ }
+
+ return (
+ <div>
+ <form>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h2 className='margin--less'>
+ <FormattedMessage
+ id='team_signup_username.username'
+ defaultMessage='Your username'
+ />
+ </h2>
+ <h5 className='color--light'>
+ <FormattedMessage
+ id='team_signup_username.memorable'
+ defaultMessage='Select a memorable username that makes it easy for teammates to identify you:'
+ />
+ </h5>
+ <div className='inner__content margin--extra'>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-11'>
+ <h5><strong>
+ <FormattedMessage
+ id='team_signup_username.chooseUsername'
+ defaultMessage='Choose your username'
+ />
+ </strong></h5>
+ <input
+ autoFocus={true}
+ type='text'
+ ref='name'
+ className='form-control'
+ placeholder=''
+ defaultValue={this.props.state.user.username}
+ maxLength={Constants.MAX_USERNAME_LENGTH}
+ spellCheck='false'
+ />
+ {nameHelpText}
+ </div>
+ </div>
+ {nameError}
+ </div>
+ </div>
+ <button
+ type='submit'
+ className='btn btn-primary margin--extra'
+ onClick={this.submitNext}
+ >
+ <FormattedMessage
+ id='team_signup_username.next'
+ defaultMessage='Next'
+ />
+ <i className='glyphicon glyphicon-chevron-right'></i>
+ </button>
+ <div className='margin--extra'>
+ <a
+ href='#'
+ onClick={this.submitBack}
+ >
+ <FormattedMessage
+ id='team_signup_username.back'
+ defaultMessage='Back to previous step'
+ />
+ </a>
+ </div>
+ </form>
+ </div>
+ );
+ }
+}
+
+TeamSignupUsernamePage.defaultProps = {
+ state: null
+};
+TeamSignupUsernamePage.propTypes = {
+ intl: intlShape.isRequired,
+ state: React.PropTypes.object,
+ updateParent: React.PropTypes.func
+};
+
+export default injectIntl(TeamSignupUsernamePage);
diff --git a/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx b/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx
new file mode 100644
index 000000000..97782e54a
--- /dev/null
+++ b/web/react/components/signup_team_complete/components/team_signup_welcome_page.jsx
@@ -0,0 +1,234 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../../utils/utils.jsx';
+import * as Client from '../../../utils/client.jsx';
+import BrowserStore from '../../../stores/browser_store.jsx';
+
+import {injectIntl, intlShape, defineMessages, FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
+
+import {browserHistory} from 'react-router';
+
+const holders = defineMessages({
+ storageError: {
+ id: 'team_signup_welcome.storageError',
+ defaultMessage: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'
+ },
+ validEmailError: {
+ id: 'team_signup_welcome.validEmailError',
+ defaultMessage: 'Please enter a valid email address'
+ },
+ address: {
+ id: 'team_signup_welcome.address',
+ defaultMessage: 'Email Address'
+ }
+});
+
+class TeamSignupWelcomePage extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submitNext = this.submitNext.bind(this);
+ this.handleDiffEmail = this.handleDiffEmail.bind(this);
+ this.handleDiffSubmit = this.handleDiffSubmit.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+
+ this.state = {useDiff: false};
+
+ document.addEventListener('keyup', this.handleKeyPress, false);
+ }
+ submitNext(e) {
+ if (!BrowserStore.isLocalStorageSupported()) {
+ this.setState({storageError: this.props.intl.formatMessage(holders.storageError)});
+ return;
+ }
+ e.preventDefault();
+ this.props.state.wizard = 'team_display_name';
+ this.props.updateParent(this.props.state);
+ }
+ handleDiffEmail(e) {
+ e.preventDefault();
+ this.setState({useDiff: true});
+ }
+ handleDiffSubmit(e) {
+ e.preventDefault();
+
+ const {formatMessage} = this.props.intl;
+ var state = {useDiff: true, serverError: ''};
+
+ var email = ReactDOM.findDOMNode(this.refs.email).value.trim().toLowerCase();
+ if (!email || !Utils.isEmail(email)) {
+ state.emailError = formatMessage(holders.validEmailError);
+ this.setState(state);
+ return;
+ } else if (!BrowserStore.isLocalStorageSupported()) {
+ state.emailError = formatMessage(holders.storageError);
+ this.setState(state);
+ return;
+ }
+ state.emailError = '';
+
+ Client.signupTeam(email,
+ function success(data) {
+ if (data.follow_link) {
+ window.location.href = data.follow_link;
+ } else {
+ this.props.state.wizard = 'finished';
+ this.props.updateParent(this.props.state);
+ browserHistory.push('/signup_team_confirm/?email=' + encodeURIComponent(email));
+ }
+ }.bind(this),
+ function error(err) {
+ let errorMsg = err.message;
+
+ if (err.detailed_error.indexOf('Invalid RCPT TO address provided') >= 0) {
+ errorMsg = formatMessage(holders.validEmailError);
+ }
+
+ this.setState({emailError: '', serverError: errorMsg});
+ }.bind(this)
+ );
+ }
+ handleKeyPress(event) {
+ if (event.keyCode === 13) {
+ this.submitNext(event);
+ }
+ }
+ componentWillUnmount() {
+ document.removeEventListener('keyup', this.handleKeyPress, false);
+ }
+ render() {
+ Client.track('signup', 'signup_team_01_welcome');
+
+ var storageError = null;
+ if (this.state.storageError) {
+ storageError = <label className='control-label'>{this.state.storageError}</label>;
+ }
+
+ var emailError = null;
+ var emailDivClass = 'form-group';
+ if (this.state.emailError) {
+ emailError = <label className='control-label'>{this.state.emailError}</label>;
+ emailDivClass += ' has-error';
+ }
+
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>{this.state.serverError}</label>
+ </div>
+ );
+ }
+
+ var differentEmailLinkClass = '';
+ var emailDivContainerClass = 'hidden';
+ if (this.state.useDiff) {
+ differentEmailLinkClass = 'hidden';
+ emailDivContainerClass = '';
+ }
+
+ return (
+ <div>
+ <img
+ className='signup-team-logo'
+ src='/static/images/logo.png'
+ />
+ <h3 className='sub-heading'>
+ <FormattedMessage
+ id='team_signup_welcome.welcome'
+ defaultMessage='Welcome to:'
+ />
+ </h3>
+ <h1 className='margin--top-none'>{global.window.mm_config.SiteName}</h1>
+ <p className='margin--less'>
+ <FormattedMessage
+ id='team_signup_welcome.lets'
+ defaultMessage="Let's set up your new team"
+ />
+ </p>
+ <div>
+ <FormattedMessage
+ id='team_signup_welcome.confirm'
+ defaultMessage='Please confirm your email address:'
+ />
+ <br/>
+ <div className='inner__content'>
+ <div className='block--gray'>{this.props.state.team.email}</div>
+ </div>
+ </div>
+ <p className='margin--extra color--light'>
+ <FormattedHTMLMessage
+ id='team_signup_welcome.admin'
+ defaultMessage='Your account will administer the new team site. <br />
+ You can add other administrators later.'
+ />
+ </p>
+ <div className='form-group'>
+ <button
+ className='btn-primary btn form-group'
+ type='submit'
+ onClick={this.submitNext}
+ >
+ <i className='glyphicon glyphicon-ok'></i>
+ <FormattedMessage
+ id='team_signup_welcome.yes'
+ defaultMessage='Yes, this address is correct'
+ />
+ </button>
+ {storageError}
+ </div>
+ <hr/>
+ <div className={emailDivContainerClass}>
+ <div className={emailDivClass}>
+ <div className='row'>
+ <div className='col-sm-9'>
+ <input
+ type='email'
+ ref='email'
+ className='form-control'
+ placeholder={this.props.intl.formatMessage(holders.address)}
+ maxLength='128'
+ spellCheck='false'
+ />
+ </div>
+ </div>
+ {emailError}
+ </div>
+ {serverError}
+ <button
+ className='btn btn-md btn-primary'
+ type='button'
+ onClick={this.handleDiffSubmit}
+ >
+ <FormattedMessage
+ id='team_signup_welcome.instead'
+ defaultMessage='Use this instead'
+ />
+ </button>
+ </div>
+ <a
+ href='#'
+ onClick={this.handleDiffEmail}
+ className={differentEmailLinkClass}
+ >
+ <FormattedMessage
+ id='team_signup_welcome.different'
+ defaultMessage='Use a different email'
+ />
+ </a>
+ </div>
+ );
+ }
+}
+
+TeamSignupWelcomePage.defaultProps = {
+ state: {}
+};
+TeamSignupWelcomePage.propTypes = {
+ intl: intlShape.isRequired,
+ updateParent: React.PropTypes.func.isRequired,
+ state: React.PropTypes.object
+};
+
+export default injectIntl(TeamSignupWelcomePage);