diff options
-rw-r--r-- | api/api.go | 1 | ||||
-rw-r--r-- | api/config.go | 34 | ||||
-rw-r--r-- | web/react/components/invite_member_modal.jsx | 66 | ||||
-rw-r--r-- | web/react/components/signup_team_complete.jsx | 609 | ||||
-rw-r--r-- | web/react/pages/channel.jsx | 21 | ||||
-rw-r--r-- | web/react/pages/signup_team.jsx | 8 | ||||
-rw-r--r-- | web/react/stores/config_store.jsx | 56 | ||||
-rw-r--r-- | web/react/utils/async_client.jsx | 26 | ||||
-rw-r--r-- | web/react/utils/client.jsx | 15 | ||||
-rw-r--r-- | web/react/utils/constants.jsx | 2 |
10 files changed, 546 insertions, 292 deletions
diff --git a/api/api.go b/api/api.go index 2ea27ed9f..9770930f7 100644 --- a/api/api.go +++ b/api/api.go @@ -40,6 +40,7 @@ func InitApi() { InitWebSocket(r) InitFile(r) InitCommand(r) + InitConfig(r) templatesDir := utils.FindDir("api/templates") l4g.Debug("Parsing server templates at %v", templatesDir) diff --git a/api/config.go b/api/config.go new file mode 100644 index 000000000..142d1ca66 --- /dev/null +++ b/api/config.go @@ -0,0 +1,34 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + l4g "code.google.com/p/log4go" + "encoding/json" + "github.com/gorilla/mux" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + "net/http" + "strconv" +) + +func InitConfig(r *mux.Router) { + l4g.Debug("Initializing config api routes") + + sr := r.PathPrefix("/config").Subrouter() + sr.Handle("/get_all", ApiAppHandler(getConfig)).Methods("GET") +} + +func getConfig(c *Context, w http.ResponseWriter, r *http.Request) { + settings := make(map[string]string) + + settings["ByPassEmail"] = strconv.FormatBool(utils.Cfg.EmailSettings.ByPassEmail) + + if bytes, err := json.Marshal(settings); err != nil { + c.Err = model.NewAppError("getConfig", "Unable to marshall configuration data", err.Error()) + return + } else { + w.Write(bytes) + } +} diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 75538c8fe..5b6924891 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -2,6 +2,7 @@ // See License.txt for license information. var utils = require('../utils/utils.jsx'); +var ConfigStore = require('../stores/config_store.jsx'); var Client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var ConfirmModal = require('./confirm_modal.jsx'); @@ -35,6 +36,10 @@ module.exports = React.createClass({ }); }, handleSubmit: function(e) { + if (!this.state.emailEnabled) { + return; + } + var inviteIds = this.state.inviteIds; var count = inviteIds.length; var invites = []; @@ -147,12 +152,18 @@ module.exports = React.createClass({ idCount: 0, emailErrors: {}, firstNameErrors: {}, - lastNameErrors: {} + lastNameErrors: {}, + emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false) }; }, render: function() { var currentUser = UserStore.getCurrentUser(); + var inputDisabled = ''; + if (!this.state.emailEnabled) { + inputDisabled = 'disabled'; + } + if (currentUser != null) { var inviteSections = []; var inviteIds = this.state.inviteIds; @@ -195,13 +206,13 @@ module.exports = React.createClass({ nameFields = (<div className='row--invite'> <div className='col-sm-6'> <div className={firstNameClass}> - <input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' /> + <input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' disabled={!this.state.emailEnabled}/> {firstNameError} </div> </div> <div className='col-sm-6'> <div className={lastNameClass}> - <input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' /> + <input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' disabled={!this.state.emailEnabled}/> {lastNameError} </div> </div> @@ -212,7 +223,7 @@ module.exports = React.createClass({ <div key={'key' + index}> {removeButton} <div className={emailClass}> - <input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' /> + <input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' disabled={!this.state.emailEnabled}/> {emailError} </div> {nameFields} @@ -225,6 +236,45 @@ module.exports = React.createClass({ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; } + var content = null; + var sendButton = null; + if (this.state.emailEnabled) { + content = ( + <div> + {serverError} + <button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button> + <br/> + <br/> + <span>People invited automatically join Town Square channel.</span> + </div> + ); + + sendButton = <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button> + } else { + var teamInviteLink = null; + if (currentUser && this.props.teamType === 'O') { + var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id; + var link = <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={linkUrl} onClick={ + function() { + $('#invite_member').modal('hide'); + } + }>Team Invite Link</a>; + + teamInviteLink = ( + <p> + You can also invite people using the {link}. + </p> + ); + } + + content = ( + <div> + <p>Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.</p> + {teamInviteLink} + </div> + ); + } + return ( <div> <div className='modal fade' ref='modal' id='invite_member' tabIndex='-1' role='dialog' aria-hidden='true'> @@ -238,15 +288,11 @@ module.exports = React.createClass({ <form role='form'> {inviteSections} </form> - {serverError} - <button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button> - <br/> - <br/> - <span>People invited automatically join Town Square channel.</span> + {content} </div> <div className='modal-footer'> <button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button> - <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button> + {sendButton} </div> </div> </div> diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 3f35a5912..e27fcd19d 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -1,8 +1,8 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - var utils = require('../utils/utils.jsx'); +var ConfigStore = require('../stores/config_store.jsx'); var client = require('../utils/client.jsx'); var UserStore = require('../stores/user_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); @@ -11,111 +11,132 @@ var constants = require('../utils/constants.jsx'); WelcomePage = React.createClass({ submitNext: function (e) { if (!BrowserStore.isLocalStorageSupported()) { - this.setState({ storage_error: "This service requires local storage to be enabled. Please enable it or exit private browsing."} ); + this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'}); return; } e.preventDefault(); - this.props.state.wizard = "team_display_name"; + this.props.state.wizard = 'team_display_name'; this.props.updateParent(this.props.state); }, handleDiffEmail: function (e) { e.preventDefault(); - this.setState({ use_diff: true }); + this.setState({useDiff: true}); }, handleDiffSubmit: function (e) { e.preventDefault(); - var state = { use_diff: true, server_error: "" }; + var state = {useDiff: true, serverError: ''}; var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); if (!email || !utils.isEmail(email)) { - state.email_error = "Please enter a valid email address"; + state.emailError = 'Please enter a valid email address'; this.setState(state); return; - } - else if (!BrowserStore.isLocalStorageSupported()) { - state.email_error = "This service requires local storage to be enabled. Please enable it or exit private browsing."; + } else if (!BrowserStore.isLocalStorageSupported()) { + state.emailError = 'This service requires local storage to be enabled. Please enable it or exit private browsing.'; this.setState(state); return; - } - else { - state.email_error = ""; + } else { + state.emailError = ''; } client.signupTeam(email, function(data) { - if (data["follow_link"]) { - window.location.href = data["follow_link"]; + if (data['follow_link']) { + window.location.href = data['follow_link']; } else { - this.props.state.wizard = "finished"; + this.props.state.wizard = 'finished'; this.props.updateParent(this.props.state); - window.location.href = "/signup_team_confirm/?email=" + encodeURIComponent(team.email); + window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email); } }.bind(this), function(err) { - this.state.server_error = err.message; + this.state.serverError = err.message; this.setState(this.state); }.bind(this) ); }, getInitialState: function() { - return { use_diff: false }; + return {useDiff: false}; }, handleKeyPress: function(event) { - if (event.keyCode == 13) { + if (event.keyCode === 13) { this.submitNext(event); } }, componentWillMount: function() { - document.addEventListener("keyup", this.handleKeyPress, false); + document.addEventListener('keyup', this.handleKeyPress, false); }, componentWillUnmount: function() { - document.removeEventListener("keyup", this.handleKeyPress, false); + document.removeEventListener('keyup', this.handleKeyPress, false); }, render: function() { - client.track('signup', 'signup_team_01_welcome'); - var storage_error = this.state.storage_error ? <label className="control-label">{ this.state.storage_error }</label> : null; - 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; + 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> <p> - <img className="signup-team-logo" src="/static/images/logo.png" /> - <h3 className="sub-heading">Welcome to:</h3> - <h1 className="margin--top-none">{config.SiteName}</h1> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h3 className='sub-heading'>Welcome to:</h3> + <h1 className='margin--top-none'>{config.SiteName}</h1> </p> - <p className="margin--less">Let's set up your new team</p> + <p className='margin--less'>Let's set up your new team</p> <p> Please confirm your email address:<br /> - <div className="inner__content"> - <div className="block--gray">{ this.props.state.team.email }</div> + <div className='inner__content'> + <div className='block--gray'>{this.props.state.team.email}</div> </div> </p> - <p className="margin--extra color--light"> + <p className='margin--extra color--light'> 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>Yes, this address is correct</button> - { storage_error } + <div className='form-group'> + <button className='btn-primary btn form-group' type='submit' onClick={this.submitNext}><i className='glyphicon glyphicon-ok'></i>Yes, this address is correct</button> + {storageError} </div> <hr /> - <div className={ this.state.use_diff ? "" : "hidden" }> - <div className={ email_error ? "form-group has-error" : "form-group" }> - <div className="row"> - <div className="col-sm-9"> - <input type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" /> + <div className={emailDivContainerClass}> + <div className={emailDivClass}> + <div className='row'> + <div className='col-sm-9'> + <input type='email' ref='email' className='form-control' placeholder='Email Address' maxLength='128' /> </div> </div> - { email_error } + {emailError} </div> - { server_error } - <button className="btn btn-md btn-primary" type="button" onClick={this.handleDiffSubmit} type="submit">Use this instead</button> + {serverError} + <button className='btn btn-md btn-primary' type='button' onClick={this.handleDiffSubmit} type='submit'>Use this instead</button> </div> - <a href="#" onClick={this.handleDiffEmail} className={ this.state.use_diff ? "hidden" : "" }>Use a different email</a> + <a href='#' onClick={this.handleDiffEmail} className={differentEmailLinkClass}>Use a different email</a> </div> ); } @@ -124,7 +145,7 @@ WelcomePage = React.createClass({ TeamDisplayNamePage = React.createClass({ submitBack: function (e) { e.preventDefault(); - this.props.state.wizard = "welcome"; + this.props.state.wizard = 'welcome'; this.props.updateParent(this.props.state); }, submitNext: function (e) { @@ -132,17 +153,17 @@ TeamDisplayNamePage = React.createClass({ var display_name = this.refs.name.getDOMNode().value.trim(); if (!display_name) { - this.setState({name_error: "This field is required"}); + this.setState({nameError: 'This field is required'}); return; } - this.props.state.wizard = "team_url"; + this.props.state.wizard = 'team_url'; this.props.state.team.display_name = display_name; this.props.state.team.name = utils.cleanUpUrlable(display_name); this.props.updateParent(this.props.state); }, getInitialState: function() { - return { }; + return {}; }, handleFocus: function(e) { e.preventDefault(); @@ -150,31 +171,35 @@ TeamDisplayNamePage = React.createClass({ e.currentTarget.select(); }, render: function() { - client.track('signup', 'signup_team_02_name'); - var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null; + 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" /> + <img className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{utils.toTitleCase(strings.Team) + " Name"}</h2> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <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} /> + <h2>{utils.toTitleCase(strings.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} /> + </div> + </div> + {nameError} </div> - </div> - { name_error } - </div> - <div>{"Name your " + strings.Team + " in any language. Your " + strings.Team + " name shows in menus and headings."}</div> - <button type="submit" className="btn btn-primary margin--extra" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> - <div className="margin--extra"> - <a href="#" onClick={this.submitBack}>Back to previous step</a> - </div> - </form> + <div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div> + <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <div className='margin--extra'> + <a href='#' onClick={this.submitBack}>Back to previous step</a> + </div> + </form> </div> ); } @@ -183,7 +208,7 @@ TeamDisplayNamePage = React.createClass({ TeamURLPage = React.createClass({ submitBack: function (e) { e.preventDefault(); - this.props.state.wizard = "team_display_name"; + this.props.state.wizard = 'team_display_name'; this.props.updateParent(this.props.state); }, submitNext: function (e) { @@ -191,25 +216,24 @@ TeamURLPage = React.createClass({ var name = this.refs.name.getDOMNode().value.trim(); if (!name) { - this.setState({name_error: "This field is required"}); + this.setState({nameError: 'This field is required'}); return; } - var cleaned_name = utils.cleanUpUrlable(name); + var cleanedName = utils.cleanUpUrlable(name); var urlRegex = /^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g; - if (cleaned_name != name || !urlRegex.test(name)) { - this.setState({name_error: "Must be lowercase alphanumeric characters"}); + if (cleanedName !== name || !urlRegex.test(name)) { + this.setState({nameError: 'Must be lowercase alphanumeric characters'}); return; - } - else if (cleaned_name.length <= 3 || cleaned_name.length > 15) { - this.setState({name_error: "Name must be 4 or more characters up to a maximum of 15"}) + } else if (cleanedName.length <= 3 || cleanedName.length > 15) { + this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'}); return; } for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) { - if (cleaned_name.indexOf(constants.RESERVED_TEAM_NAMES[index]) == 0) { - this.setState({name_error: "This team name is unavailable"}) + if (cleanedName.indexOf(constants.RESERVED_TEAM_NAMES[index]) === 0) { + this.setState({nameError: 'This team name is unavailable'}); return; } } @@ -218,28 +242,27 @@ TeamURLPage = React.createClass({ function(data) { if (!data) { if (config.AllowSignupDomainsWizard) { - this.props.state.wizard = "allowed_domains"; + this.props.state.wizard = 'allowed_domains'; } else { - this.props.state.wizard = "send_invites"; + this.props.state.wizard = 'send_invites'; this.props.state.team.type = 'O'; } this.props.state.team.name = name; this.props.updateParent(this.props.state); - } - else { - this.state.name_error = "This URL is unavailable. Please try another."; + } else { + this.state.nameError = 'This URL is unavailable. Please try another.'; this.setState(this.state); } }.bind(this), function(err) { - this.state.name_error = err.message; + this.state.nameError = err.message; this.setState(this.state); }.bind(this) ); }, getInitialState: function() { - return { }; + return {}; }, handleFocus: function(e) { e.preventDefault(); @@ -247,40 +270,44 @@ TeamURLPage = React.createClass({ e.currentTarget.select(); }, render: function() { - $('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} ); client.track('signup', 'signup_team_03_url'); - var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null; + 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>{utils.toTitleCase(strings.Team) + " URL"}</h2> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <div className="row"> - <div className="col-sm-11"> - <div className="input-group input-group--limit"> - <span data-toggle="tooltip" title={ utils.getWindowLocationOrigin() + "/" } className="input-group-addon">{ utils.getWindowLocationOrigin() + "/" }</span> - <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h2>{utils.toTitleCase(strings.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={utils.getWindowLocationOrigin() + '/'} className='input-group-addon'>{utils.getWindowLocationOrigin() + '/'}</span> + <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/> + </div> + </div> </div> + {nameError} </div> - </div> - { name_error } - </div> - <p>{"Choose the web address of your new " + strings.Team + ":"}</p> - <ul className="color--light"> - <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}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> - <div className="margin--extra"> - <a href="#" onClick={this.submitBack}>Back to previous step</a> - </div> - </form> + <p>{'Choose the web address of your new ' + strings.Team + ':'}</p> + <ul className='color--light'> + <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}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <div className='margin--extra'> + <a href='#' onClick={this.submitBack}>Back to previous step</a> + </div> + </form> </div> ); } @@ -289,14 +316,14 @@ TeamURLPage = React.createClass({ AllowedDomainsPage = React.createClass({ submitBack: function (e) { e.preventDefault(); - this.props.state.wizard = "team_url"; + this.props.state.wizard = 'team_url'; this.props.updateParent(this.props.state); }, submitNext: function (e) { e.preventDefault(); if (this.refs.open_network.getDOMNode().checked) { - this.props.state.wizard = "send_invites"; + this.props.state.wizard = 'send_invites'; this.props.state.team.type = 'O'; this.props.updateParent(this.props.state); return; @@ -304,65 +331,72 @@ AllowedDomainsPage = React.createClass({ if (this.refs.allow.getDOMNode().checked) { var name = this.refs.name.getDOMNode().value.trim(); - var domainRegex = /^\w+\.\w+$/ + var domainRegex = /^\w+\.\w+$/; if (!name) { - this.setState({name_error: "This field is required"}); + this.setState({nameError: 'This field is required'}); return; } - if(!name.trim().match(domainRegex)) { - this.setState({name_error: "The domain doesn't appear valid"}); + if (!name.trim().match(domainRegex)) { + this.setState({nameError: 'The domain doesn\'t appear valid'}); return; } - this.props.state.wizard = "send_invites"; + this.props.state.wizard = 'send_invites'; this.props.state.team.allowed_domains = name; this.props.state.team.type = 'I'; this.props.updateParent(this.props.state); - } - else { - this.props.state.wizard = "send_invites"; + } else { + this.props.state.wizard = 'send_invites'; this.props.state.team.type = 'I'; this.props.updateParent(this.props.state); } }, getInitialState: function() { - return { }; + return {}; }, render: function() { - client.track('signup', 'signup_team_04_allow_domains'); - var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null; + 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>Email Domain</h2> - <p> - <div className="checkbox"><label><input type="checkbox" ref="allow" defaultChecked />{" Allow sign up and " + strings.Team + " discovery with a " + strings.Company + " email address."}</label></div> - </p> - <p>{"Check this box to allow your " + strings.Team + " members to sign up using their " + strings.Company + " email addresses if you share the same domain--otherwise, you need to invite everyone yourself."}</p> - <h4>{"Your " + strings.Team + "'s domain for emails"}</h4> - <div className={ name_error ? "form-group has-error" : "form-group" }> - <div className="row"> - <div className="col-sm-9"> - <div className="input-group"> - <span className="input-group-addon">@</span> - <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h2>Email Domain</h2> + <p> + <div className='checkbox'> + <label><input type='checkbox' ref='allow' defaultChecked />{' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}</label> + </div> + </p> + <p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p> + <h4>{'Your ' + strings.Team + '\'s domain for emails'}</h4> + <div className={nameDivClass}> + <div className='row'> + <div className='col-sm-9'> + <div className='input-group'> + <span className='input-group-addon'>@</span> + <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/> + </div> + </div> </div> + {nameError} </div> - </div> - { name_error } - </div> - <p>To allow signups from multiple domains, separate each with a comma.</p> - <p> - <div className="checkbox"><label><input type="checkbox" ref="open_network" defaultChecked={this.props.state.team.type == 'O'} /> Allow anyone to signup to this domain without an invitation.</label></div> - </p> - <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button> - <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button> - </form> + <p>To allow signups from multiple domains, separate each with a comma.</p> + <p> + <div className='checkbox'> + <label><input type='checkbox' ref='open_network' defaultChecked={this.props.state.team.type === 'O'} /> Allow anyone to signup to this domain without an invitation.</label> + </div> + </p> + <button type='button' className='btn btn-default' onClick={this.submitBack}><i className='glyphicon glyphicon-chevron-left'></i> Back</button> + <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + </form> </div> ); } @@ -370,10 +404,10 @@ AllowedDomainsPage = React.createClass({ EmailItem = React.createClass({ getInitialState: function() { - return { }; + return {}; }, getValue: function() { - return this.refs.email.getDOMNode().value.trim() + return this.refs.email.getDOMNode().value.trim(); }, validate: function(teamEmail) { var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); @@ -383,43 +417,44 @@ EmailItem = React.createClass({ } if (!utils.isEmail(email)) { - this.state.email_error = "Please enter a valid email address"; + this.state.emailError = 'Please enter a valid email address'; this.setState(this.state); return false; - } - else if (email === teamEmail) { - this.state.email_error = "Please use a different email than the one used at signup"; + } else if (email === teamEmail) { + this.state.emailError = 'Please use a different email than the one used at signup'; this.setState(this.state); return false; - } - else { - this.state.email_error = ""; + } else { + this.state.emailError = ''; this.setState(this.state); return true; } }, render: function() { - - var email_error = this.state.email_error ? <label className="control-label">{ this.state.email_error }</label> : null; + var emailError = null; + var emailDivClass = 'form-group'; + if (this.state.emailError) { + emailError = <label className='control-label'>{ this.state.emailError }</label>; + emailDivClass += ' has-error'; + } return ( - <div className={ email_error ? "form-group has-error" : "form-group" }> - <input autoFocus={this.props.focus} type="email" ref="email" className="form-control" placeholder="Email Address" defaultValue={this.props.email} maxLength="128" /> - { email_error } + <div className={emailDivClass}> + <input autoFocus={this.props.focus} type='email' ref='email' className='form-control' placeholder='Email Address' defaultValue={this.props.email} maxLength='128' /> + {emailError} </div> ); } }); - SendInivtesPage = React.createClass({ submitBack: function (e) { e.preventDefault(); if (config.AllowSignupDomainsWizard) { - this.props.state.wizard = "allowed_domains"; + this.props.state.wizard = 'allowed_domains'; } else { - this.props.state.wizard = "team_url"; + this.props.state.wizard = 'team_url'; } this.props.updateParent(this.props.state); @@ -428,69 +463,93 @@ SendInivtesPage = React.createClass({ e.preventDefault(); var valid = true; - var emails = []; - for (var i = 0; i < this.props.state.invites.length; i++) { - if (!this.refs['email_' + i].validate(this.props.state.team.email)) { - valid = false; - } else { - emails.push(this.refs['email_' + i].getValue()); + if (this.state.emailEnabled) { + var emails = []; + + for (var i = 0; i < this.props.state.invites.length; i++) { + if (!this.refs['email_' + i].validate(this.props.state.team.email)) { + valid = false; + } else { + emails.push(this.refs['email_' + i].getValue()); + } } - } - if (!valid) { - return; + if (valid) { + this.props.state.invites = emails; + } } - this.props.state.wizard = "username"; - this.props.state.invites = emails; - this.props.updateParent(this.props.state); + if (valid) { + this.props.state.wizard = 'username'; + this.props.updateParent(this.props.state); + } }, submitAddInvite: function (e) { e.preventDefault(); - this.props.state.wizard = "send_invites"; - if (this.props.state.invites == null || this.props.state.invites.length == 0) { + this.props.state.wizard = 'send_invites'; + if (!this.props.state.invites) { this.props.state.invites = []; } - this.props.state.invites.push(""); + this.props.state.invites.push(''); this.props.updateParent(this.props.state); }, submitSkip: function (e) { e.preventDefault(); - this.props.state.wizard = "username"; + this.props.state.wizard = 'username'; this.props.updateParent(this.props.state); }, getInitialState: function() { - return { }; + return { + emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false) + }; }, render: function() { - client.track('signup', 'signup_team_05_send_invites'); - var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null; + var content = null; + var bottomContent = null; - var emails = []; + 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]} />); + 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}>Add Invitation</a></div> + </div> + ); + + bottomContent = ( + <p className='color--light'>{'if you prefer, you can invite ' + strings.Team + ' members later'}<br /> and <a href='#' onClick={this.submitSkip}>skip this step</a> for now.</p> + ); + } else { + content = ( + <div className='form-group color--light'>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>{"Invite " + utils.toTitleCase(strings.Team) + " Members"}</h2> - { emails } - <div className="form-group text-right"><a href="#" onClick={this.submitAddInvite}>Add Invitation</a></div> - <div className="form-group"><button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button></div> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2> + {content} + <div className='form-group'> + <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + </div> </form> - <p className="color--light">{"if you prefer, you can invite " + strings.Team + " members later"}<br /> and <a href="#" onClick={this.submitSkip}>skip this step</a> for now.</p> - <div className="margin--extra"> - <a href="#" onClick={this.submitBack}>Back to previous step</a> + {bottomContent} + <div className='margin--extra'> + <a href='#' onClick={this.submitBack}>Back to previous step</a> </div> </div> ); @@ -508,12 +567,12 @@ UsernamePage = React.createClass({ var name = this.refs.name.getDOMNode().value.trim(); - var username_error = utils.isValidUsername(name); - if (username_error === 'Cannot use a reserved word as a username.') { - this.setState({name_error: 'This username is reserved, please choose a new one.'}); + var usernameError = utils.isValidUsername(name); + if (usernameError === 'Cannot use a reserved word as a username.') { + this.setState({nameError: 'This username is reserved, please choose a new one.'}); return; - } else if (username_error) { - this.setState({name_error: "Username must begin with a letter, and contain 3 to 15 characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'"}); + } else if (usernameError) { + this.setState({nameError: 'Username must begin with a letter, and contain 3 to 15 characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''}); return; } @@ -527,31 +586,36 @@ UsernamePage = React.createClass({ render: function() { client.track('signup', 'signup_team_06_username'); - var name_error = this.state.name_error ? <label className='control-label'>{this.state.name_error}</label> : null; + 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 className='margin--less'>Your username</h2> - <h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5> - <div className='inner__content margin--extra'> - <div className={name_error ? 'form-group has-error' : 'form-group'}> - <div className='row'> - <div className='col-sm-11'> - <h5><strong>Choose your username</strong></h5> - <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' /> - <div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h2 className='margin--less'>Your username</h2> + <h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5> + <div className='inner__content margin--extra'> + <div className={nameDivClass}> + <div className='row'> + <div className='col-sm-11'> + <h5><strong>Choose your username</strong></h5> + <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' /> + <div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div> + </div> + </div> + {nameError} </div> </div> - {name_error} + <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <div className='margin--extra'> + <a href='#' onClick={this.submitBack}>Back to previous step</a> </div> - </div> - <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> - <div className='margin--extra'> - <a href='#' onClick={this.submitBack}>Back to previous step</a> - </div> - </form> + </form> </div> ); } @@ -560,7 +624,7 @@ UsernamePage = React.createClass({ PasswordPage = React.createClass({ submitBack: function (e) { e.preventDefault(); - this.props.state.wizard = "username"; + this.props.state.wizard = 'username'; this.props.updateParent(this.props.state); }, submitNext: function (e) { @@ -568,11 +632,11 @@ PasswordPage = React.createClass({ var password = this.refs.password.getDOMNode().value.trim(); if (!password || password.length < 5) { - this.setState({password_error: "Please enter at least 5 characters"}); + this.setState({passwordError: 'Please enter at least 5 characters'}); return; } - this.setState({password_error: null, server_error: null}); + this.setState({passwordError: null, serverError: null}); $('#finish-button').button('loading'); var teamSignup = JSON.parse(JSON.stringify(this.props.state)); teamSignup.user.password = password; @@ -582,13 +646,12 @@ PasswordPage = React.createClass({ client.createTeamFromSignup(teamSignup, function(data) { - client.track('signup', 'signup_team_08_complete'); var props = this.props; $('#sign-up-button').button('reset'); - props.state.wizard = "finished"; + props.state.wizard = 'finished'; props.updateParent(props.state, true); window.location.href = utils.getWindowLocationOrigin() + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email); @@ -601,55 +664,63 @@ PasswordPage = React.createClass({ // window.location.href = '/channels/town-square'; // }.bind(ctl), // function(err) { - // this.setState({name_error: err.message}); + // this.setState({nameError: err.message}); // }.bind(ctl) // ); }.bind(this), function(err) { - this.setState({server_error: err.message}); + this.setState({serverError: err.message}); $('#sign-up-button').button('reset'); }.bind(this) ); }, getInitialState: function() { - return { }; + return {}; }, render: function() { - client.track('signup', 'signup_team_07_password'); - var password_error = this.state.password_error ? <div className="form-group has-error"><label className="control-label">{ this.state.password_error }</label></div> : 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 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">Your password</h2> - <h5 className="color--light">Select a password that you'll use to login with your email address:</h5> - <div className="inner__content margin--extra"> - <h5><strong>Email</strong></h5> - <div className="block--gray form-group">{this.props.state.team.email}</div> - <div className={ password_error ? "form-group has-error" : "form-group" }> - <div className="row"> - <div className="col-sm-11"> - <h5><strong>Choose your password</strong></h5> - <input autoFocus={true} type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> - <div className="color--light form__hint">Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div> + <img className='signup-team-logo' src='/static/images/logo.png' /> + <h2 className='margin--less'>Your password</h2> + <h5 className='color--light'>Select a password that you'll use to login with your email address:</h5> + <div className='inner__content margin--extra'> + <h5><strong>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>Choose your password</strong></h5> + <input autoFocus={true} type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> + <div className='color--light form__hint'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div> + </div> + </div> + {passwordError} + {serverError} </div> </div> - { password_error } - { server_error } + <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> Creating ' + strings.Team + '...'} onClick={this.submitNext}>Finish</button> </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> Creating "+strings.Team+"..."} onClick={this.submitNext}>Finish</button> - </div> - <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 className="margin--extra"> - <a href="#" onClick={this.submitBack}>Back to previous step</a> - </div> - </form> + <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 className='margin--extra'> + <a href='#' onClick={this.submitBack}>Back to previous step</a> + </div> + </form> </div> ); } @@ -668,14 +739,14 @@ module.exports = React.createClass({ if (!props) { props = {}; - props.wizard = "welcome"; + props.wizard = 'welcome'; props.team = {}; props.team.email = this.props.email; - props.team.allowed_domains = ""; + props.team.allowed_domains = ''; props.invites = []; - props.invites.push(""); - props.invites.push(""); - props.invites.push(""); + props.invites.push(''); + props.invites.push(''); + props.invites.push(''); props.user = {}; props.hash = this.props.hash; props.data = this.props.data; @@ -684,36 +755,34 @@ module.exports = React.createClass({ return props; }, render: function() { - if (this.state.wizard == "welcome") { - return <WelcomePage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'welcome') { + return <WelcomePage state={this.state} updateParent={this.updateParent} />; } - if (this.state.wizard == "team_display_name") { - return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'team_display_name') { + return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />; } - if (this.state.wizard == "team_url") { - return <TeamURLPage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'team_url') { + return <TeamURLPage state={this.state} updateParent={this.updateParent} />; } - if (this.state.wizard == "allowed_domains") { - return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'allowed_domains') { + return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />; } - if (this.state.wizard == "send_invites") { - return <SendInivtesPage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'send_invites') { + return <SendInivtesPage state={this.state} updateParent={this.updateParent} />; } - if (this.state.wizard == "username") { - return <UsernamePage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'username') { + return <UsernamePage state={this.state} updateParent={this.updateParent} />; } - if (this.state.wizard == "password") { - return <PasswordPage state={this.state} updateParent={this.updateParent} /> + if (this.state.wizard === 'password') { + return <PasswordPage state={this.state} updateParent={this.updateParent} />; } return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>); } }); - - diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 90d90b29f..929499715 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -1,7 +1,6 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Navbar = require('../components/navbar.jsx'); var Sidebar = require('../components/sidebar.jsx'); @@ -36,21 +35,23 @@ var AccessHistoryModal = require('../components/access_history_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx') +var AsyncClient = require('../utils/async_client.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; global.window.setup_channel_page = function(team_name, team_type, team_id, channel_name, channel_id) { + AsyncClient.getConfig(); AppDispatcher.handleViewAction({ - type: ActionTypes.CLICK_CHANNEL, - name: channel_name, - id: channel_id + type: ActionTypes.CLICK_CHANNEL, + name: channel_name, + id: channel_id }); AppDispatcher.handleViewAction({ - type: ActionTypes.CLICK_TEAM, - id: team_id + type: ActionTypes.CLICK_TEAM, + id: team_id }); React.render( @@ -99,7 +100,7 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - <MemberInviteModal />, + <MemberInviteModal teamType={team_type} />, document.getElementById('invite_member_modal') ); @@ -194,17 +195,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann ); React.render( - <MentionList id="post_textbox" />, + <MentionList id='post_textbox' />, document.getElementById('post_mention_tab') ); React.render( - <MentionList id="reply_textbox" />, + <MentionList id='reply_textbox' />, document.getElementById('reply_mention_tab') ); React.render( - <MentionList id="edit_textbox" />, + <MentionList id='edit_textbox' />, document.getElementById('edit_mention_tab') ); diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx index e982f5a79..37c441d4f 100644 --- a/web/react/pages/signup_team.jsx +++ b/web/react/pages/signup_team.jsx @@ -1,11 +1,15 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var SignupTeam =require('../components/signup_team.jsx'); +var SignupTeam = require('../components/signup_team.jsx'); + +var AsyncClient = require('../utils/async_client.jsx'); global.window.setup_signup_team_page = function() { + AsyncClient.getConfig(); + React.render( <SignupTeam />, document.getElementById('signup-team') ); -};
\ No newline at end of file +}; diff --git a/web/react/stores/config_store.jsx b/web/react/stores/config_store.jsx new file mode 100644 index 000000000..7ff177b35 --- /dev/null +++ b/web/react/stores/config_store.jsx @@ -0,0 +1,56 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); +var EventEmitter = require('events').EventEmitter; +var assign = require('object-assign'); + +var BrowserStore = require('../stores/browser_store.jsx'); + +var Constants = require('../utils/constants.jsx'); +var ActionTypes = Constants.ActionTypes; + +var CHANGE_EVENT = 'change'; + +var ConfigStore = assign({}, EventEmitter.prototype, { + emitChange: function emitChange() { + this.emit(CHANGE_EVENT); + }, + addChangeListener: function addChangeListener(callback) { + this.on(CHANGE_EVENT, callback); + }, + removeChangeListener: function removeChangeListener(callback) { + this.removeListener(CHANGE_EVENT, callback); + }, + getSetting: function getSetting(key, defaultValue) { + return BrowserStore.getItem('config_' + key, defaultValue); + }, + getSettingAsBoolean: function getSettingAsNumber(key, defaultValue) { + var value = ConfigStore.getSetting(key, defaultValue); + + if (typeof value !== 'string') { + return !!value; + } else { + return value === 'true'; + } + }, + updateStoredSettings: function updateStoredSettings(settings) { + for (var key in settings) { + BrowserStore.setItem('config_' + key, settings[key]); + } + } +}); + +ConfigStore.dispatchToken = AppDispatcher.register(function registry(payload) { + var action = payload.action; + + switch (action.type) { + case ActionTypes.RECIEVED_CONFIG: + ConfigStore.updateStoredSettings(action.settings); + ConfigStore.emitChange(); + break; + default: + } +}); + +module.exports = ConfigStore; diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 7e8a6116c..0b87bbd7b 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -4,6 +4,7 @@ var client = require('./client.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); +var ConfigStore = require('../stores/config_store.jsx'); var PostStore = require('../stores/post_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var utils = require('./utils.jsx'); @@ -457,3 +458,28 @@ module.exports.getMyTeam = function() { } ); } + +function getConfig() { + if (isCallInProgress('getConfig')) { + return; + } + + callTracker['getConfig'] = utils.getTimestamp(); + client.getConfig( + function(data, textStatus, xhr) { + callTracker['getConfig'] = 0; + + if (data && xhr.status !== 304) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECIEVED_CONFIG, + settings: data + }); + } + }, + function(err) { + callTracker['getConfig'] = 0; + dispatchError(err, 'getConfig'); + } + ); +} +module.exports.getConfig = getConfig; diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index f2b6619a5..5aab80d01 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -880,3 +880,18 @@ module.exports.updateValetFeature = function(data, success, error) { module.exports.track('api', 'api_teams_update_valet_feature'); }; + +function getConfig(success, error) { + $.ajax({ + url: '/api/v1/config/get_all', + dataType: 'json', + type: 'GET', + ifModified: true, + success: success, + error: function(xhr, status, err) { + var e = handleError('getConfig', xhr, status, err); + error(e); + } + }); +}; +module.exports.getConfig = getConfig; diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 0c714567a..508de9185 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -31,6 +31,8 @@ module.exports = { CLICK_TEAM: null, RECIEVED_TEAM: null, + + RECIEVED_CONFIG: null }), PayloadSources: keyMirror({ |