diff options
Diffstat (limited to 'web/react')
-rw-r--r-- | web/react/components/login.jsx | 80 | ||||
-rw-r--r-- | web/react/components/signup_team.jsx | 106 | ||||
-rw-r--r-- | web/react/components/signup_user_complete.jsx | 51 | ||||
-rw-r--r-- | web/react/components/signup_user_oauth.jsx | 87 | ||||
-rw-r--r-- | web/react/components/team_signup_choose_auth.jsx | 88 | ||||
-rw-r--r-- | web/react/components/team_signup_with_email.jsx | 82 | ||||
-rw-r--r-- | web/react/components/team_signup_with_sso.jsx | 137 | ||||
-rw-r--r-- | web/react/pages/signup_team.jsx | 6 | ||||
-rw-r--r-- | web/react/pages/signup_user_oauth.jsx | 11 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 15 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 1 |
11 files changed, 458 insertions, 206 deletions
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx index 678a2ff87..489ff6960 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -10,7 +10,9 @@ var Constants = require('../utils/constants.jsx'); export default class Login extends React.Component { constructor(props) { super(props); + this.handleSubmit = this.handleSubmit.bind(this); + this.state = {}; } handleSubmit(e) { @@ -96,19 +98,28 @@ export default class Login extends React.Component { var authServices = JSON.parse(this.props.authServices); var loginMessage = []; - if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) { + if (authServices.indexOf(Constants.GITLAB_SERVICE) !== -1) { loginMessage.push( - <div className='form-group form-group--small'> - <span><a href={'/' + teamName + '/login/gitlab'}>{'Log in with GitLab'}</a></span> - </div> - ); + <a + className='btn btn-custom-login gitlab' + href={'/' + teamName + '/login/gitlab'} + > + <span className='icon' /> + <span>with GitLab</span> + </a> + ); } - if (authServices.indexOf(Constants.GOOGLE_SERVICE) >= 0) { + + if (authServices.indexOf(Constants.GOOGLE_SERVICE) !== -1) { loginMessage.push( - <div className='form-group form-group--small'> - <span><a href={'/' + teamName + '/login/google'}>{'Log in with Google'}</a></span> - </div> - ); + <a + className='btn btn-custom-login google' + href={'/' + teamName + '/login/google'} + > + <span className='icon' /> + <span>with Google Apps</span> + </a> + ); } var errorClass = ''; @@ -116,15 +127,10 @@ export default class Login extends React.Component { errorClass = ' has-error'; } - return ( - <div className='signup-team__container'> - <h5 className='margin--less'>Sign in to:</h5> - <h2 className='signup-team__name'>{teamDisplayName}</h2> - <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> - <form onSubmit={this.handleSubmit}> - <div className={'form-group' + errorClass}> - {serverError} - </div> + var emailSignup; + if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) { + emailSignup = ( + <div> <div className={'form-group' + errorClass}> <input autoFocus={focusEmail} @@ -154,13 +160,43 @@ export default class Login extends React.Component { Sign in </button> </div> + </div> + ); + } + + var forgotPassword; + if (loginMessage.length > 0 && emailSignup) { + loginMessage = ( + <div> + {loginMessage} + <div className='or__container'> + <span>or</span> + </div> + </div> + ); + + forgotPassword = ( + <div className='form-group'> + <a href={'/' + teamName + '/reset_password'}>I forgot my password</a> + </div> + ); + } + + return ( + <div className='signup-team__container'> + <h5 className='margin--less'>Sign in to:</h5> + <h2 className='signup-team__name'>{teamDisplayName}</h2> + <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> + <form onSubmit={this.handleSubmit}> + <div className={'form-group' + errorClass}> + {serverError} + </div> {loginMessage} + {emailSignup} <div className='form-group margin--extra form-group--small'> <span><a href='/find_team'>{'Find other ' + strings.TeamPlural}</a></span> </div> - <div className='form-group'> - <a href={'/' + teamName + '/reset_password'}>I forgot my password</a> - </div> + {forgotPassword} <div className='margin--extra'> <span>{'Want to create your own ' + strings.Team + '? '} <a diff --git a/web/react/components/signup_team.jsx b/web/react/components/signup_team.jsx index edd48e0b9..13640b1e5 100644 --- a/web/react/components/signup_team.jsx +++ b/web/react/components/signup_team.jsx @@ -1,69 +1,49 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); - -module.exports = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - var team = {}; - var state = { server_error: "" }; - - team.email = this.refs.email.getDOMNode().value.trim().toLowerCase(); - if (!team.email || !utils.isEmail(team.email)) { - state.email_error = "Please enter a valid email address"; - state.inValid = true; +var ChoosePage = require('./team_signup_choose_auth.jsx'); +var EmailSignUpPage = require('./team_signup_with_email.jsx'); +var SSOSignupPage = require('./team_signup_with_sso.jsx'); +var Constants = require('../utils/constants.jsx'); + +export default class TeamSignUp extends React.Component { + constructor(props) { + super(props); + + this.updatePage = this.updatePage.bind(this); + + if (props.services.length === 1) { + if (props.services[0] === Constants.EMAIL_SERVICE) { + this.state = {page: 'email', service: ''}; + } else { + this.state = {page: 'service', service: props.services[0]}; + } + } else { + this.state = {page: 'choose', service: ''}; } - else { - state.email_error = ""; - } - - if (state.inValid) { - this.setState(state); - return; + } + updatePage(page, service) { + this.setState({page: page, service: service}); + } + render() { + if (this.state.page === 'email') { + return <EmailSignUpPage />; + } else if (this.state.page === 'service' && this.state.service !== '') { + return <SSOSignupPage service={this.state.service} />; + } else { + return ( + <ChoosePage + services={this.props.services} + updatePage={this.updatePage} + /> + ); } - - client.signupTeam(team.email, - function(data) { - if (data["follow_link"]) { - window.location.href = data["follow_link"]; - } - else { - window.location.href = "/signup_team_confirm/?email=" + encodeURIComponent(team.email); - } - }.bind(this), - function(err) { - state.server_error = err.message; - this.setState(state); - }.bind(this) - ); - }, - getInitialState: function() { - return { }; - }, - render: function() { - - var email_error = this.state.email_error ? <label className='control-label'>{ this.state.email_error }</label> : null; - var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null; - - return ( - <form role="form" onSubmit={this.handleSubmit}> - <div className={ email_error ? "form-group has-error" : "form-group" }> - <input autoFocus={true} type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" /> - { email_error } - </div> - { server_error } - <div className="form-group"> - <button className="btn btn-md btn-primary" type="submit">Sign up</button> - </div> - <div className="form-group margin--extra-2x"> - <span><a href="/find_team">{"Find my " + strings.Team}</a></span> - </div> - </form> - ); } -}); - - +} + +TeamSignUp.defaultProps = { + services: [] +}; +TeamSignUp.propTypes = { + services: React.PropTypes.array +}; diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 0393e0413..e5c602c16 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -171,7 +171,35 @@ module.exports = React.createClass({ ); } - if (signupMessage.length > 0) { + var emailSignup; + if (authServices.indexOf(Constants.EMAIL_SERVICE) !== -1) { + emailSignup = ( + <div> + <div className='inner__content'> + {email} + {yourEmailIs} + <div className='margin--extra'> + <h5><strong>Choose your username</strong></h5> + <div className={nameDivStyle}> + <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' /> + {nameError} + <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p> + </div> + </div> + <div className='margin--extra'> + <h5><strong>Choose your password</strong></h5> + <div className={passwordDivStyle}> + <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> + {passwordError} + </div> + </div> + </div> + <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p> + </div> + ); + } + + if (signupMessage.length > 0 && emailSignup) { signupMessage = ( <div> {signupMessage} @@ -196,26 +224,7 @@ module.exports = React.createClass({ <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> <h4 className='color--light'>Let's create your account</h4> {signupMessage} - <div className='inner__content'> - {email} - {yourEmailIs} - <div className='margin--extra'> - <h5><strong>Choose your username</strong></h5> - <div className={nameDivStyle}> - <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' /> - {nameError} - <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p> - </div> - </div> - <div className='margin--extra'> - <h5><strong>Choose your password</strong></h5> - <div className={passwordDivStyle}> - <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> - {passwordError} - </div> - </div> - </div> - <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p> + {emailSignup} {serverError} {termsDisclaimer} </form> diff --git a/web/react/components/signup_user_oauth.jsx b/web/react/components/signup_user_oauth.jsx deleted file mode 100644 index 8b2800bde..000000000 --- a/web/react/components/signup_user_oauth.jsx +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - - -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var UserStore = require('../stores/user_store.jsx'); -var BrowserStore = require('../stores/browser_store.jsx'); - -module.exports = React.createClass({ - handleSubmit: function(e) { - e.preventDefault(); - - if (!this.state.user.username) { - this.setState({name_error: "This field is required", email_error: "", password_error: "", server_error: ""}); - return; - } - - var username_error = utils.isValidUsername(this.state.user.username); - if (username_error === "Cannot use a reserved word as a username.") { - this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""}); - return; - } else if (username_error) { - this.setState({name_error: "Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'.", email_error: "", password_error: "", server_error: ""}); - return; - } - - this.setState({name_error: "", server_error: ""}); - - this.state.user.allow_marketing = this.refs.email_service.getDOMNode().checked; - - var user = this.state.user; - client.createUser(user, "", "", - function(data) { - client.track('signup', 'signup_user_oauth_02'); - UserStore.setCurrentUser(data); - UserStore.setLastEmail(data.email); - - window.location.href = '/' + this.props.teamName + '/login/' + user.auth_service + '?login_hint=' + user.email; - }.bind(this), - function(err) { - this.state.server_error = err.message; - this.setState(this.state); - }.bind(this) - ); - }, - handleChange: function() { - var user = this.state.user; - user.username = this.refs.name.getDOMNode().value; - this.setState({ user: user }); - }, - getInitialState: function() { - var user = JSON.parse(this.props.user); - return { user: user }; - }, - render: function() { - - client.track('signup', 'signup_user_oauth_01'); - - var name_error = this.state.name_error ? <label className='control-label'>{ this.state.name_error }</label> : null; - var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className='control-label'>{ this.state.server_error }</label></div> : null; - - var yourEmailIs = this.state.user.email == "" ? "" : <span>Your email address is <b>{ this.state.user.email }.</b></span>; - - return ( - <div> - <img className="signup-team-logo" src="/static/images/logo.png" /> - <h4>Welcome to { config.SiteName }</h4> - <p>{"To continue signing up with " + this.state.user.auth_service + ", please register a username."}</p> - <p>Your username can be made of lowercase letters and numbers.</p> - <label className="control-label">Username</label> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" value={this.state.user.username} onChange={this.handleChange} /> - { name_error } - </div> - <p>{"Pick something " + strings.Team + "mates will recognize. Your username is how you will appear to others."}</p> - <p>{ yourEmailIs } You’ll use this address to sign in to {config.SiteName}.</p> - <div className="checkbox"><label><input type="checkbox" ref="email_service" /> It's ok to send me occassional email with updates about the {config.SiteName} service. </label></div> - <p><button onClick={this.handleSubmit} className="btn-primary btn">Create Account</button></p> - { server_error } - <p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p> - </div> - ); - } -}); - - diff --git a/web/react/components/team_signup_choose_auth.jsx b/web/react/components/team_signup_choose_auth.jsx new file mode 100644 index 000000000..2d35785c2 --- /dev/null +++ b/web/react/components/team_signup_choose_auth.jsx @@ -0,0 +1,88 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Constants = require('../utils/constants.jsx'); + +export default class ChooseAuthPage extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + render() { + var buttons = []; + if (this.props.services.indexOf(Constants.GITLAB_SERVICE) !== -1) { + buttons.push( + <a + className='btn btn-custom-login gitlab btn-full' + href='#' + onClick={ + function clickGit(e) { + e.preventDefault(); + this.props.updatePage('service', Constants.GITLAB_SERVICE); + }.bind(this) + } + > + <span className='icon' /> + <span>Create new {strings.Team} with GitLab Account</span> + </a> + ); + } + + if (this.props.services.indexOf(Constants.GOOGLE_SERVICE) !== -1) { + buttons.push( + <a + className='btn btn-custom-login google btn-full' + href='#' + onClick={ + function clickGoogle(e) { + e.preventDefault(); + this.props.updatePage('service', Constants.GOOGLE_SERVICE); + }.bind(this) + } + > + <span className='icon' /> + <span>Create new {strings.Team} with Google Apps Account</span> + </a> + ); + } + + if (this.props.services.indexOf(Constants.EMAIL_SERVICE) !== -1) { + buttons.push( + <a + className='btn btn-custom-login email btn-full' + href='#' + onClick={ + function clickEmail(e) { + e.preventDefault(); + this.props.updatePage('email', ''); + }.bind(this) + } + > + <span className='fa fa-envelope' /> + <span>Create new {strings.Team} with email address</span> + </a> + ); + } + + if (buttons.length === 0) { + buttons = <span>No sign-up methods configured, please contact your system administrator.</span>; + } + + return ( + <div> + {buttons} + <div className='form-group margin--extra-2x'> + <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> + </div> + </div> + ); + } +} + +ChooseAuthPage.defaultProps = { + services: [] +}; +ChooseAuthPage.propTypes = { + services: React.PropTypes.array, + updatePage: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_with_email.jsx b/web/react/components/team_signup_with_email.jsx new file mode 100644 index 000000000..c7204880f --- /dev/null +++ b/web/react/components/team_signup_with_email.jsx @@ -0,0 +1,82 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var utils = require('../utils/utils.jsx'); +var client = require('../utils/client.jsx'); + +export default class EmailSignUpPage extends React.Component { + constructor() { + super(); + + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = {}; + } + handleSubmit(e) { + e.preventDefault(); + var team = {}; + var state = {serverError: ''}; + + team.email = this.refs.email.getDOMNode().value.trim().toLowerCase(); + if (!team.email || !utils.isEmail(team.email)) { + state.emailError = 'Please enter a valid email address'; + state.inValid = true; + } else { + state.emailError = ''; + } + + if (state.inValid) { + this.setState(state); + return; + } + + client.signupTeam(team.email, + function success(data) { + if (data.follow_link) { + window.location.href = data.follow_link; + } else { + window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email); + } + }, + function fail(err) { + state.serverError = err.message; + this.setState(state); + }.bind(this) + ); + } + render() { + return ( + <form + role='form' + onSubmit={this.handleSubmit} + > + <div className='form-group'> + <input + autoFocus={true} + type='email' + ref='email' + className='form-control' + placeholder='Email Address' + maxLength='128' + /> + </div> + <div className='form-group'> + <button + className='btn btn-md btn-primary' + type='submit' + > + Sign up + </button> + </div> + <div className='form-group margin--extra-2x'> + <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> + </div> + </form> + ); + } +} + +EmailSignUpPage.defaultProps = { +}; +EmailSignUpPage.propTypes = { +}; diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx new file mode 100644 index 000000000..57996d7cc --- /dev/null +++ b/web/react/components/team_signup_with_sso.jsx @@ -0,0 +1,137 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var utils = require('../utils/utils.jsx'); +var client = require('../utils/client.jsx'); +var Constants = require('../utils/constants.jsx'); + +export default class SSOSignUpPage extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + this.nameChange = this.nameChange.bind(this); + + this.state = {name: ''}; + } + handleSubmit(e) { + e.preventDefault(); + var team = {}; + var state = this.state; + state.nameError = null; + state.serverError = null; + + team.display_name = this.state.name; + + if (team.display_name.length <= 3) { + return; + } + + if (!team.display_name) { + state.nameError = 'Please enter a team name'; + this.setState(state); + return; + } + + team.name = utils.cleanUpUrlable(team.display_name); + team.type = 'O'; + + client.createTeamWithSSO(team, + this.props.service, + function success(data) { + if (data.follow_link) { + window.location.href = data.follow_link; + } else { + window.location.href = '/'; + } + }, + function fail(err) { + state.serverError = err.message; + this.setState(state); + }.bind(this) + ); + } + nameChange() { + this.setState({name: this.refs.teamname.getDOMNode().value.trim()}); + } + render() { + var nameError = null; + var nameDivClass = 'form-group'; + if (this.state.nameError) { + nameError = <label className='control-label'>{this.state.nameError}</label>; + nameDivClass += ' 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 disabled = false; + if (this.state.name.length <= 3) { + disabled = true; + } + + var button = null; + + if (this.props.service === Constants.GITLAB_SERVICE) { + button = ( + <a + className='btn btn-custom-login gitlab btn-full' + href='#' + onClick={this.handleSubmit} + disabled={disabled} + > + <span className='icon'/> + <span>Create {strings.Team} with GitLab Account</span> + </a> + ); + } else if (this.props.service === Constants.GOOGLE_SERVICE) { + button = ( + <a + className='btn btn-custom-login google btn-full' + href='#' + onClick={this.handleSubmit} + disabled={disabled} + > + <span className='icon'/> + <span>Create {strings.Team} with Google Apps Account</span> + </a> + ); + } + + return ( + <form + role='form' + onSubmit={this.handleSubmit} + > + <div className={nameDivClass}> + <input + autoFocus={true} + type='text' + ref='teamname' + className='form-control' + placeholder='Enter name of new team' + maxLength='128' + onChange={this.nameChange} + /> + {nameError} + </div> + <div className='form-group'> + {button} + {serverError} + </div> + <div className='form-group margin--extra-2x'> + <span><a href='/find_team'>{'Find my ' + strings.Team}</a></span> + </div> + </form> + ); + } +} + +SSOSignUpPage.defaultProps = { + service: '' +}; +SSOSignUpPage.propTypes = { + service: React.PropTypes.string +}; diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx index 37c441d4f..4b58025ac 100644 --- a/web/react/pages/signup_team.jsx +++ b/web/react/pages/signup_team.jsx @@ -5,11 +5,13 @@ var SignupTeam = require('../components/signup_team.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -global.window.setup_signup_team_page = function() { +global.window.setup_signup_team_page = function(authServices) { AsyncClient.getConfig(); + var services = JSON.parse(authServices); + React.render( - <SignupTeam />, + <SignupTeam services={services} />, document.getElementById('signup-team') ); }; diff --git a/web/react/pages/signup_user_oauth.jsx b/web/react/pages/signup_user_oauth.jsx deleted file mode 100644 index 6a0707702..000000000 --- a/web/react/pages/signup_user_oauth.jsx +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -var SignupUserOAuth = require('../components/signup_user_oauth.jsx'); - -global.window.setup_signup_user_oauth_page = function(user, team_name, team_display_name) { - React.render( - <SignupUserOAuth user={user} teamName={team_name} teamDisplayName={team_display_name} />, - document.getElementById('signup-user-complete') - ); -}; diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 70220c71e..082f82a08 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -70,6 +70,21 @@ module.exports.createTeamFromSignup = function(teamSignup, success, error) { }); }; +module.exports.createTeamWithSSO = function(team, service, success, error) { + $.ajax({ + url: '/api/v1/teams/create_with_sso/' + service, + dataType: 'json', + contentType: 'application/json', + type: 'POST', + data: JSON.stringify(team), + success: success, + error: function onError(xhr, status, err) { + var e = handleError('createTeamWithSSO', xhr, status, err); + error(e); + } + }); +}; + module.exports.createUser = function(user, data, emailHash, success, error) { $.ajax({ url: '/api/v1/users/create?d=' + encodeURIComponent(data) + '&h=' + encodeURIComponent(emailHash), diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 82fc3da22..6678790e2 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -61,6 +61,7 @@ module.exports = { OFFTOPIC_CHANNEL: 'off-topic', GITLAB_SERVICE: 'gitlab', GOOGLE_SERVICE: 'google', + EMAIL_SERVICE: 'email', POST_CHUNK_SIZE: 60, MAX_POST_CHUNKS: 3, POST_LOADING: 'loading', |