diff options
Diffstat (limited to 'web/react/components')
-rw-r--r-- | web/react/components/access_history_modal.jsx | 57 | ||||
-rw-r--r-- | web/react/components/activity_log_modal.jsx | 113 | ||||
-rw-r--r-- | web/react/components/edit_channel_modal.jsx | 2 | ||||
-rw-r--r-- | web/react/components/file_attachment.jsx | 10 | ||||
-rw-r--r-- | web/react/components/invite_member_modal.jsx | 66 | ||||
-rw-r--r-- | web/react/components/login.jsx | 2 | ||||
-rw-r--r-- | web/react/components/more_channels.jsx | 123 | ||||
-rw-r--r-- | web/react/components/new_channel.jsx | 12 | ||||
-rw-r--r-- | web/react/components/post.jsx | 6 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 7 | ||||
-rw-r--r-- | web/react/components/rename_channel_modal.jsx | 10 | ||||
-rw-r--r-- | web/react/components/setting_item_min.jsx | 19 | ||||
-rw-r--r-- | web/react/components/sidebar.jsx | 31 | ||||
-rw-r--r-- | web/react/components/sidebar_header.jsx | 2 | ||||
-rw-r--r-- | web/react/components/sidebar_right_menu.jsx | 2 | ||||
-rw-r--r-- | web/react/components/signup_team_complete.jsx | 609 | ||||
-rw-r--r-- | web/react/components/user_settings.jsx | 41 | ||||
-rw-r--r-- | web/react/components/user_settings_modal.jsx | 3 |
18 files changed, 647 insertions, 468 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 16768a119..a19e5c16e 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -13,21 +13,23 @@ function getStateFromStoresForAudits() { } module.exports = React.createClass({ + displayName: 'AccessHistoryModal', componentDidMount: function() { - UserStore.addAuditsChangeListener(this._onChange); - $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) { + UserStore.addAuditsChangeListener(this.onListenerChange); + $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() { AsyncClient.getAudits(); }); var self = this; - $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { + $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() { + $('#user_settings').modal('show'); self.setState({moreInfo: []}); }); }, componentWillUnmount: function() { - UserStore.removeAuditsChangeListener(this._onChange); + UserStore.removeAuditsChangeListener(this.onListenerChange); }, - _onChange: function() { + onListenerChange: function() { var newState = getStateFromStoresForAudits(); if (!utils.areStatesEqual(newState.audits, this.state.audits)) { this.setState(newState); @@ -61,6 +63,21 @@ module.exports = React.createClass({ currentAudit.session_id = 'N/A (Login attempt)'; } + var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>); + if (this.state.moreInfo[i]) { + moreInfo = ( + <div> + <div>{'Session ID: ' + currentAudit.session_id}</div> + <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div> + </div> + ); + } + + var divider = null; + if (i < this.state.audits.length - 1) { + divider = (<div className='divider-light'></div>) + } + accessList[i] = ( <div className='access-history__table'> <div className='access__date'>{newDate}</div> @@ -68,25 +85,21 @@ module.exports = React.createClass({ <div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div> <div className='report__info'> <div>{'IP: ' + currentAudit.ip_address}</div> - {this.state.moreInfo[i] ? - <div> - <div>{'Session ID: ' + currentAudit.session_id}</div> - <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div> - </div> - : - <a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a> - } + {moreInfo} </div> - {i < this.state.audits.length - 1 ? - <div className='divider-light'/> - : - null - } + {divider} </div> </div> ); } + var content; + if (this.state.audits.loading) { + content = (<LoadingScreen />); + } else { + content = (<form role='form'>{accessList}</form>); + } + return ( <div> <div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'> @@ -97,13 +110,7 @@ module.exports = React.createClass({ <h4 className='modal-title' id='myModalLabel'>Access History</h4> </div> <div ref='modalBody' className='modal-body'> - {!this.state.audits.loading ? - <form role='form'> - {accessList} - </form> - : - <LoadingScreen /> - } + {content} </div> </div> </div> diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index f28f0d5f1..1192a72bc 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -10,40 +10,41 @@ var utils = require('../utils/utils.jsx'); function getStateFromStoresForSessions() { return { sessions: UserStore.getSessions(), - server_error: null, - client_error: null + serverError: null, + clientError: null }; } module.exports = React.createClass({ + displayName: 'ActivityLogModal', submitRevoke: function(altId) { - var self = this; Client.revokeSession(altId, function(data) { AsyncClient.getSessions(); }.bind(this), function(err) { - state = getStateFromStoresForSessions(); - state.server_error = err; + var state = getStateFromStoresForSessions(); + state.serverError = err; this.setState(state); }.bind(this) ); }, componentDidMount: function() { - UserStore.addSessionsChangeListener(this._onChange); + UserStore.addSessionsChangeListener(this.onListenerChange); $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { AsyncClient.getSessions(); }); var self = this; $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({ moreInfo: [] }); + $('#user_settings').modal('show'); + self.setState({moreInfo: []}); }); }, componentWillUnmount: function() { - UserStore.removeSessionsChangeListener(this._onChange); + UserStore.removeSessionsChangeListener(this.onListenerChange); }, - _onChange: function() { + onListenerChange: function() { var newState = getStateFromStoresForSessions(); if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) { this.setState(newState); @@ -52,7 +53,7 @@ module.exports = React.createClass({ handleMoreInfo: function(index) { var newMoreInfo = this.state.moreInfo; newMoreInfo[index] = true; - this.setState({ moreInfo: newMoreInfo }); + this.setState({moreInfo: newMoreInfo}); }, getInitialState: function() { var initialState = getStateFromStoresForSessions(); @@ -61,68 +62,76 @@ module.exports = React.createClass({ }, render: function() { var activityList = []; - var server_error = this.state.server_error ? this.state.server_error : null; + var serverError = this.state.serverError; + + // Squash any false-y value for server error into null + if (!serverError) { + serverError = null; + } for (var i = 0; i < this.state.sessions.length; i++) { var currentSession = this.state.sessions[i]; var lastAccessTime = new Date(currentSession.last_activity_at); var firstAccessTime = new Date(currentSession.create_at); - var devicePicture = ""; + var devicePicture = ''; - if (currentSession.props.platform === "Windows") { - devicePicture = "fa fa-windows"; + if (currentSession.props.platform === 'Windows') { + devicePicture = 'fa fa-windows'; } - else if (currentSession.props.platform === "Macintosh" || currentSession.props.platform === "iPhone") { - devicePicture = "fa fa-apple"; + else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') { + devicePicture = 'fa fa-apple'; } - else if (currentSession.props.platform === "Linux") { - devicePicture = "fa fa-linux"; + else if (currentSession.props.platform === 'Linux') { + devicePicture = 'fa fa-linux'; + } + + var moreInfo; + if (this.state.moreInfo[i]) { + moreInfo = ( + <div> + <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div> + <div>{'OS: ' + currentSession.props.os}</div> + <div>{'Browser: ' + currentSession.props.browser}</div> + <div>{'Session ID: ' + currentSession.alt_id}</div> + </div> + ); + } else { + moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>); } activityList[i] = ( - <div className="activity-log__table"> - <div className="activity-log__report"> - <div className="report__platform"><i className={devicePicture} />{currentSession.props.platform}</div> - <div className="report__info"> - <div>{"Last activity: " + lastAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div> - { this.state.moreInfo[i] ? - <div> - <div>{"First time active: " + firstAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div> - <div>{"OS: " + currentSession.props.os}</div> - <div>{"Browser: " + currentSession.props.browser}</div> - <div>{"Session ID: " + currentSession.alt_id}</div> - </div> - : - <a className="theme" href="#" onClick={this.handleMoreInfo.bind(this, i)}>More info</a> - } + <div className='activity-log__table'> + <div className='activity-log__report'> + <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div> + <div className='report__info'> + <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div> + {moreInfo} </div> </div> - <div className="activity-log__action"><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className="btn btn-primary">Logout</button></div> + <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div> </div> ); } + var content; + if (this.state.sessions.loading) { + content = (<LoadingScreen />); + } else { + content = (<form role='form'>{activityList}</form>); + } + return ( <div> - <div className="modal fade" ref="modal" id="activity-log" tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog modal-lg"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title" id="myModalLabel">Active Sessions</h4> + <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'> + <div className='modal-dialog modal-lg'> + <div className='modal-content'> + <div className='modal-header'> + <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> + <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4> </div> - <p className="session-help-text">Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the "Logout" button below to end a session.</p> - <div ref="modalBody" className="modal-body"> - { !this.state.sessions.loading ? - <div> - <form role="form"> - { activityList } - </form> - { server_error } - </div> - : - <LoadingScreen /> - } + <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p> + <div ref='modalBody' className='modal-body'> + {content} </div> </div> </div> diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index 06d7fc3e8..dcff5b89d 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -14,7 +14,7 @@ module.exports = React.createClass({ Client.updateChannelDesc(data, function(data) { this.setState({ server_error: "" }); - AsyncClient.getChannels(true); + AsyncClient.getChannel(this.state.channel_id); $(this.refs.modal.getDOMNode()).modal('hide'); }.bind(this), function(err) { diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index b7ea5734f..c36c908d2 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -113,6 +113,14 @@ module.exports = React.createClass({ fileSizeString = utils.fileSizeToString(this.state.fileSize); } + var filenameString = decodeURIComponent(utils.getFileName(filename)); + var trimmedFilename; + if (filenameString.length > 35) { + trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "..."; + } else { + trimmedFilename = filenameString; + } + return ( <div className="post-image__column" key={filename}> <a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick} @@ -120,7 +128,7 @@ module.exports = React.createClass({ {thumbnail} </a> <div className="post-image__details"> - <div className="post-image__name">{decodeURIComponent(utils.getFileName(filename))}</div> + <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div> <div> <span className="post-image__type">{fileInfo.ext.toUpperCase()}</span> <span className="post-image__size">{fileSizeString}</span> 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/login.jsx b/web/react/components/login.jsx index fe0a47777..eba4f06f4 100644 --- a/web/react/components/login.jsx +++ b/web/react/components/login.jsx @@ -50,7 +50,7 @@ module.exports = React.createClass({ var redirect = utils.getUrlParameter("redirect"); if (redirect) { - window.location.pathname = decodeURI(redirect); + window.location.pathname = decodeURIComponent(redirect); } else { window.location.pathname = '/' + name + '/channels/town-square'; } diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx index 007476f9b..5261ed6a7 100644 --- a/web/react/components/more_channels.jsx +++ b/web/react/components/more_channels.jsx @@ -1,34 +1,32 @@ // 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 asyncClient = require('../utils/async_client.jsx'); -var UserStore = require('../stores/user_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var LoadingScreen = require('./loading_screen.jsx'); function getStateFromStores() { - return { - channels: ChannelStore.getMoreAll(), - server_error: null - }; + return { + channels: ChannelStore.getMoreAll(), + serverError: null + }; } module.exports = React.createClass({ - displayName: "MoreChannelsModal", + displayName: 'MoreChannelsModal', componentDidMount: function() { ChannelStore.addMoreChangeListener(this._onChange); - $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { + $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function shown() { asyncClient.getMoreChannels(true); }); var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { + $(this.refs.modal.getDOMNode()).on('show.bs.modal', function show(e) { var button = e.relatedTarget; - self.setState({ channel_type: $(button).attr('data-channeltype') }); + self.setState({channelType: $(button).attr('data-channeltype')}); }); }, componentWillUnmount: function() { @@ -42,18 +40,17 @@ module.exports = React.createClass({ }, getInitialState: function() { var initState = getStateFromStores(); - initState.channel_type = ""; + initState.channelType = ''; return initState; }, - handleJoin: function(e) { - var self = this; - client.joinChannel(e, - function(data) { - $(self.refs.modal.getDOMNode()).modal('hide'); - asyncClient.getChannels(true); + handleJoin: function(id) { + client.joinChannel(id, + function() { + $(this.refs.modal.getDOMNode()).modal('hide'); + asyncClient.getChannel(id); }.bind(this), function(err) { - this.state.server_error = err.message; + this.state.serverError = err.message; this.setState(this.state); }.bind(this) ); @@ -62,52 +59,66 @@ module.exports = React.createClass({ $(this.refs.modal.getDOMNode()).modal('hide'); }, render: function() { - 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 serverError; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + var outter = this; var moreChannels; - if (this.state.channels != null) - moreChannels = this.state.channels; + if (this.state.channels != null) { + var channels = this.state.channels; + if (!channels.loading) { + if (channels.length) { + moreChannels = ( + <table className='more-channel-table table'> + <tbody> + {channels.map(function cMap(channel) { + return ( + <tr key={channel.id}> + <td> + <p className='more-channel-name'>{channel.display_name}</p> + <p className='more-channel-description'>{channel.description}</p> + </td> + <td className='td--action'><button onClick={outter.handleJoin.bind(outter, channel.id)} className='btn btn-primary'>Join</button></td> + </tr> + ); + })} + </tbody> + </table> + ); + } else { + moreChannels = ( + <div className='no-channel-message'> + <p className='primary-message'>No more channels to join</p> + <p className='secondary-message'>Click 'Create New Channel' to make a new one</p> + </div> + ); + } + } else { + moreChannels = <LoadingScreen />; + } + } return ( - <div className="modal fade" id="more_channels" ref="modal" tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <button type="button" className="close" data-dismiss="modal"> - <span aria-hidden="true">×</span> - <span className="sr-only">Close</span> + <div className='modal fade' id='more_channels' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'> + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <button type='button' className='close' data-dismiss='modal'> + <span aria-hidden='true'>×</span> + <span className='sr-only'>Close</span> </button> - <h4 className="modal-title">More Channels</h4> - <button data-toggle="modal" data-target="#new_channel" data-channeltype={this.state.channel_type} type="button" className="btn btn-primary channel-create-btn" onClick={this.handleNewChannel}>Create New Channel</button> + <h4 className='modal-title'>More Channels</h4> + <button data-toggle='modal' data-target='#new_channel' data-channeltype={this.state.channelType} type='button' className='btn btn-primary channel-create-btn' onClick={this.handleNewChannel}>Create New Channel</button> </div> - <div className="modal-body"> - {!moreChannels.loading ? - (moreChannels.length ? - <table className="more-channel-table table"> - <tbody> - {moreChannels.map(function(channel) { - return ( - <tr key={channel.id}> - <td> - <p className="more-channel-name">{channel.display_name}</p> - <p className="more-channel-description">{channel.description}</p> - </td> - <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="btn btn-primary">Join</button></td> - </tr> - ) - })} - </tbody> - </table> - : <div className="no-channel-message"> - <p className="primary-message">No more channels to join</p> - <p className="secondary-message">Click 'Create New Channel' to make a new one</p> - </div>) - : <LoadingScreen /> } - { server_error } + <div className='modal-body'> + {moreChannels} + {serverError} </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <div className='modal-footer'> + <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button> </div> </div> </div> diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx index c22147022..b00376758 100644 --- a/web/react/components/new_channel.jsx +++ b/web/react/components/new_channel.jsx @@ -55,16 +55,16 @@ module.exports = React.createClass({ channel.description = this.refs.channel_desc.getDOMNode().value.trim(); channel.type = this.state.channelType; - var self = this; client.createChannel(channel, - function() { + function(data) { + $(this.refs.modal.getDOMNode()).modal('hide'); + + asyncClient.getChannel(data.id); + utils.switchChannel(data); + this.refs.display_name.getDOMNode().value = ''; this.refs.channel_name.getDOMNode().value = ''; this.refs.channel_desc.getDOMNode().value = ''; - - $(self.refs.modal.getDOMNode()).modal('hide'); - window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; - asyncClient.getChannels(true); }.bind(this), function(err) { state.serverError = err.message; diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx index e72a2d001..f099c67ab 100644 --- a/web/react/components/post.jsx +++ b/web/react/components/post.jsx @@ -11,12 +11,6 @@ var ActionTypes = Constants.ActionTypes; module.exports = React.createClass({ displayName: "Post", - componentDidMount: function() { - $('.modal').on('show.bs.modal', function () { - $('.modal-body').css('overflow-y', 'auto'); - $('.modal-body').css('max-height', $(window).height() * 0.7); - }); - }, handleCommentClick: function(e) { e.preventDefault(); diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 83f806b79..fa74d4295 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -69,6 +69,12 @@ module.exports = React.createClass({ this.oldScrollHeight = post_holder.scrollHeight; this.oldZoom = (window.outerWidth - 8) / window.innerWidth; + $('.modal').on('show.bs.modal', function () { + $('.modal-body').css('overflow-y', 'auto'); + $('.modal-body').css('max-height', $(window).height() * 0.7); + }); + + // Timeout exists for the DOM to fully render before making changes var self = this; $(window).resize(function(){ $(post_holder).perfectScrollbar('update'); @@ -140,6 +146,7 @@ module.exports = React.createClass({ UserStore.removeStatusesChangeListener(this._onTimeChange); SocketStore.removeChangeListener(this._onSocketChange); $('body').off('click.userpopover'); + $('.modal').off('show.bs.modal') }, resize: function() { var post_holder = $(".post-list-holder-by-time")[0]; diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 26593b7fa..93cb6ef21 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -63,12 +63,14 @@ module.exports = React.createClass({ Client.updateChannel(channel, function(data, text, req) { + $(this.refs.modal.getDOMNode()).modal('hide'); + + AsyncClient.getChannel(channel.id); + utils.updateTabTitle(channel.display_name); + utils.updateAddressBar(channel.name); + this.refs.display_name.getDOMNode().value = ""; this.refs.channel_name.getDOMNode().value = ""; - - $('#' + this.props.modalId).modal('hide'); - window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + this.state.channel_name; - AsyncClient.getChannels(true); }.bind(this), function(err) { state.server_error = err.message; diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx index 2209c74d1..3c87e416e 100644 --- a/web/react/components/setting_item_min.jsx +++ b/web/react/components/setting_item_min.jsx @@ -2,12 +2,23 @@ // See License.txt for license information. module.exports = React.createClass({ + displayName: 'SettingsItemMin', + propTypes: { + title: React.PropTypes.string, + disableOpen: React.PropTypes.bool, + updateSection: React.PropTypes.func, + describe: React.PropTypes.string + }, render: function() { + var editButton = ''; + if (!this.props.disableOpen) { + editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>; + } return ( - <ul className="section-min"> - <li className="col-sm-10 section-title">{this.props.title}</li> - <li className="col-sm-2 section-edit"><a className="section-edit theme" href="#" onClick={this.props.updateSection}>Edit</a></li> - <li className="col-sm-7 section-describe">{this.props.describe}</li> + <ul className='section-min'> + <li className='col-sm-10 section-title'>{this.props.title}</li> + {editButton} + <li className='col-sm-7 section-describe'>{this.props.describe}</li> </ul> ); } diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index fe73cbcf7..a8496b385 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -102,7 +102,7 @@ function getStateFromStores() { } readDirectChannels = readDirectChannels.slice(index); - showDirectChannels.sort(function(a, b) { + showDirectChannels.sort(function directSort(a, b) { if (a.display_name < b.display_name) { return -1; } @@ -114,7 +114,7 @@ function getStateFromStores() { } return { - active_id: currentId, + activeId: currentId, channels: ChannelStore.getAll(), members: members, showDirectChannels: showDirectChannels, @@ -128,6 +128,7 @@ module.exports = React.createClass({ ChannelStore.addChangeListener(this.onChange); UserStore.addChangeListener(this.onChange); UserStore.addStatusesChangeListener(this.onChange); + TeamStore.addChangeListener(this.onChange); SocketStore.addChangeListener(this.onSocketChange); $('.nav-pills__container').perfectScrollbar(); @@ -146,6 +147,7 @@ module.exports = React.createClass({ ChannelStore.removeChangeListener(this.onChange); UserStore.removeChangeListener(this.onChange); UserStore.removeStatusesChangeListener(this.onChange); + TeamStore.removeChangeListener(this.onChange); SocketStore.removeChangeListener(this.onSocketChange); }, onChange: function() { @@ -157,9 +159,11 @@ module.exports = React.createClass({ onSocketChange: function(msg) { if (msg.action === 'posted') { if (ChannelStore.getCurrentId() === msg.channel_id) { - AsyncClient.getChannels(true, window.isActive); + if (window.isActive) { + AsyncClient.updateLastViewedAt(); + } } else { - AsyncClient.getChannels(true); + AsyncClient.getChannels(); } if (UserStore.getCurrentId() !== msg.user_id) { @@ -214,12 +218,12 @@ module.exports = React.createClass({ } } } else if (msg.action === 'viewed') { - if (ChannelStore.getCurrentId() != msg.channel_id) { - AsyncClient.getChannels(true); + if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) { + AsyncClient.getChannel(msg.channel_id); } } else if (msg.action === 'user_added') { if (UserStore.getCurrentId() === msg.user_id) { - AsyncClient.getChannels(true); + AsyncClient.getChannel(msg.channel_id); } } else if (msg.action === 'user_removed') { if (msg.user_id === UserStore.getCurrentId()) { @@ -282,7 +286,7 @@ module.exports = React.createClass({ }, render: function() { var members = this.state.members; - var activeId = this.state.active_id; + var activeId = this.state.activeId; var badgesActive = false; // keep track of the first and last unread channels so we can use them to set the unread indicators @@ -294,7 +298,7 @@ module.exports = React.createClass({ var channelMember = members[channel.id]; var linkClass = ''; - if (channel.id === self.state.active_id) { + if (channel.id === activeId) { linkClass = 'active'; } @@ -346,15 +350,16 @@ module.exports = React.createClass({ // set up click handler to switch channels (or create a new channel for non-existant ones) var clickHandler = null; - var href; + var href = '#'; + var teamURL = TeamStore.getCurrentTeamUrl(); if (!channel.fake) { clickHandler = function(e) { e.preventDefault(); utils.switchChannel(channel); }; - href = '#'; - } else { - href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name; + } + if (channel.fake && teamURL){ + href = teamURL + '/channels/' + channel.name; } return ( diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index cc3f255ee..761c06e74 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -96,7 +96,7 @@ var NavbarDropdown = React.createClass({ <span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} /> </a> <ul className='dropdown-menu' role='menu'> - <li><a href='#' data-toggle='modal' data-target='#user_settings1'>Account Settings</a></li> + <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li> {teamSettings} {inviteLink} {teamLink} diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index 2439719a1..d221ca840 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -72,7 +72,7 @@ module.exports = React.createClass({ <div className='nav-pills__container'> <ul className='nav nav-pills nav-stacked'> - <li><a href='#' data-toggle='modal' data-target='#user_settings1'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li> + <li><a href='#' data-toggle='modal' data-target='#user_settings'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li> {teamSettingsLink} {inviteLink} {teamLink} 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/components/user_settings.jsx b/web/react/components/user_settings.jsx index 1a0c313d3..8f29bbe57 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -13,6 +13,7 @@ var assign = require('object-assign'); function getNotificationsStateFromStores() { var user = UserStore.getCurrentUser(); + var soundNeeded = !utils.isBrowserFirefox(); var sound = (!user.notify_props || user.notify_props.desktop_sound == undefined) ? "true" : user.notify_props.desktop_sound; var desktop = (!user.notify_props || user.notify_props.desktop == undefined) ? "all" : user.notify_props.desktop; var email = (!user.notify_props || user.notify_props.email == undefined) ? "true" : user.notify_props.email; @@ -58,7 +59,7 @@ function getNotificationsStateFromStores() { } } - return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key }; + return { notify_level: desktop, enable_email: email, soundNeeded: soundNeeded, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key }; } @@ -105,11 +106,11 @@ var NotificationsTab = React.createClass({ }, componentDidMount: function() { UserStore.addChangeListener(this._onChange); - $('#user_settings1').on('hidden.bs.modal', this.handleClose); + $('#user_settings').on('hidden.bs.modal', this.handleClose); }, componentWillUnmount: function() { UserStore.removeChangeListener(this._onChange); - $('#user_settings1').off('hidden.bs.modal', this.handleClose); + $('#user_settings').off('hidden.bs.modal', this.handleClose); this.props.updateSection(''); }, _onChange: function() { @@ -235,7 +236,7 @@ var NotificationsTab = React.createClass({ } var soundSection; - if (this.props.activeSection === 'sound') { + if (this.props.activeSection === 'sound' && this.state.soundNeeded) { var soundActive = ["",""]; if (this.state.enable_sound === "false") { soundActive[1] = "active"; @@ -265,7 +266,9 @@ var NotificationsTab = React.createClass({ ); } else { var describe = ""; - if (this.state.enable_sound === "false") { + if (!this.state.soundNeeded) { + describe = "Please configure notification sounds in your browser settings" + } else if (this.state.enable_sound === "false") { describe = "Off"; } else { describe = "On"; @@ -276,6 +279,7 @@ var NotificationsTab = React.createClass({ title="Desktop notification sounds" describe={describe} updateSection={function(){self.props.updateSection("sound");}} + disableOpen = {!this.state.soundNeeded} /> ); } @@ -513,27 +517,34 @@ var SecurityTab = React.createClass({ this.setState({confirmPassword: e.target.value}); }, handleHistoryOpen: function() { - $('#user_settings1').modal('hide'); + this.setState({willReturn: true}); + $("#user_settings").modal('hide'); }, handleDevicesOpen: function() { - $('#user_settings1').modal('hide'); + this.setState({willReturn: true}); + $("#user_settings").modal('hide'); }, handleClose: function() { $(this.getDOMNode()).find('.form-control').each(function() { this.value = ''; }); this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null}); - this.props.updateTab('general'); + + if (!this.state.willReturn) { + this.props.updateTab('general'); + } else { + this.setState({willReturn: false}); + } }, componentDidMount: function() { - $('#user_settings1').on('hidden.bs.modal', this.handleClose); + $('#user_settings').on('hidden.bs.modal', this.handleClose); }, componentWillUnmount: function() { - $('#user_settings1').off('hidden.bs.modal', this.handleClose); + $('#user_settings').off('hidden.bs.modal', this.handleClose); this.props.updateSection(''); }, getInitialState: function() { - return {currentPassword: '', newPassword: '', confirmPassword: ''}; + return {currentPassword: '', newPassword: '', confirmPassword: '', willReturn: false}; }, render: function() { var serverError = this.state.serverError ? this.state.serverError : null; @@ -811,10 +822,10 @@ var GeneralTab = React.createClass({ this.props.updateSection(''); }, componentDidMount: function() { - $('#user_settings1').on('hidden.bs.modal', this.handleClose); + $('#user_settings').on('hidden.bs.modal', this.handleClose); }, componentWillUnmount: function() { - $('#user_settings1').off('hidden.bs.modal', this.handleClose); + $('#user_settings').off('hidden.bs.modal', this.handleClose); }, getInitialState: function() { var user = this.props.user; @@ -1093,7 +1104,7 @@ var AppearanceTab = React.createClass({ if (this.props.activeSection === "theme") { $(this.refs[this.state.theme].getDOMNode()).addClass('active-border'); } - $('#user_settings1').on('hidden.bs.modal', this.handleClose); + $('#user_settings').on('hidden.bs.modal', this.handleClose); }, componentDidUpdate: function() { if (this.props.activeSection === "theme") { @@ -1102,7 +1113,7 @@ var AppearanceTab = React.createClass({ } }, componentWillUnmount: function() { - $('#user_settings1').off('hidden.bs.modal', this.handleClose); + $('#user_settings').off('hidden.bs.modal', this.handleClose); this.props.updateSection(''); }, getInitialState: function() { diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx index 702e7ad7a..7181c4020 100644 --- a/web/react/components/user_settings_modal.jsx +++ b/web/react/components/user_settings_modal.jsx @@ -32,7 +32,7 @@ module.exports = React.createClass({ tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"}); return ( - <div className="modal fade" ref="modal" id="user_settings1" role="dialog" tabIndex="-1" aria-hidden="true"> + <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true"> <div className="modal-dialog settings-modal"> <div className="modal-content"> <div className="modal-header"> @@ -64,4 +64,3 @@ module.exports = React.createClass({ ); } }); - |