diff options
Diffstat (limited to 'web')
24 files changed, 1369 insertions, 746 deletions
diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 0fa433383..8e8ed3f73 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -10,13 +10,18 @@ var SocketStore = require('../stores/socket_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var PostStore = require('../stores/post_store.jsx'); var UserStore = require('../stores/user_store.jsx'); -var Constants = require('../utils/constants.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - componentDidMount: function() { +export default class ChannelLoader extends React.Component { + constructor(props) { + super(props); + this.onSocketChange = this.onSocketChange.bind(this); + + this.state = {}; + } + componentDidMount() { /* Initial aysnc loads */ AsyncClient.getMe(); AsyncClient.getPosts(ChannelStore.getCurrentId()); @@ -60,32 +65,32 @@ module.exports = React.createClass({ var user = UserStore.getCurrentUser(); if (user.props && user.props.theme) { - utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';'); - utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';'); - utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';'); - utils.changeCss('.mention', 'background: ' + user.props.theme + ';'); - utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';'); - utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}'); - utils.changeCss('.search-item-container:hover', 'background: ' + utils.changeOpacity(user.props.theme, 0.05) + ';'); + Utils.changeCss('div.theme', 'background-color:' + user.props.theme + ';'); + Utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme + ';'); + Utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme + ';'); + Utils.changeCss('.mention', 'background: ' + user.props.theme + ';'); + Utils.changeCss('.mention-link', 'color: ' + user.props.theme + ';'); + Utils.changeCss('@media(max-width: 768px){.search-bar__container', 'background: ' + user.props.theme + ';}'); + Utils.changeCss('.search-item-container:hover', 'background: ' + Utils.changeOpacity(user.props.theme, 0.05) + ';'); } if (user.props.theme !== '#000000' && user.props.theme !== '#585858') { - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) + ';'); - utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;'); + Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, -10) + ';'); + Utils.changeCss('a.theme', 'color:' + user.props.theme + '; fill:' + user.props.theme + '!important;'); } else if (user.props.theme === '#000000') { - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +50) + ';'); + Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +50) + ';'); $('.team__header').addClass('theme--black'); } else if (user.props.theme === '#585858') { - utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, +10) + ';'); + Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';'); $('.team__header').addClass('theme--gray'); } - }, - onSocketChange: function(msg) { + } + onSocketChange(msg) { if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) { UserStore.setStatus(msg.user_id, 'online'); } - }, - render: function() { + } + render() { return <div/>; } -}); +} diff --git a/web/react/components/confirm_modal.jsx b/web/react/components/confirm_modal.jsx index 3be13cf9b..cb3b9c5e3 100644 --- a/web/react/components/confirm_modal.jsx +++ b/web/react/components/confirm_modal.jsx @@ -1,31 +1,70 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - handleConfirm: function() { - $('#'+this.props.parent_id).attr('data-confirm', 'true'); - $('#'+this.props.parent_id).modal('hide'); - $('#'+this.props.id).modal('hide'); - }, - render: function() { +export default class ConfirmModal extends React.Component { + constructor(props) { + super(props); + + this.handleConfirm = this.handleConfirm.bind(this); + + this.state = {}; + } + handleConfirm() { + $('#' + this.props.parent_id).attr('data-confirm', 'true'); + $('#' + this.props.parent_id).modal('hide'); + $('#' + this.props.id).modal('hide'); + } + render() { return ( - <div className="modal fade" id={this.props.id} tabIndex="-1" role="dialog" aria-hidden="true"> - <div className="modal-dialog"> - <div className="modal-content"> - <div className="modal-header"> - <h4 className="modal-title">{this.props.title}</h4> - </div> - <div className="modal-body"> - {this.props.message} - </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button onClick={this.handleConfirm} type="button" className="btn btn-primary">{this.props.confirm_button}</button> + <div + className='modal fade' + id={this.props.id} + tabIndex='-1' + role='dialog' + aria-hidden='true' + > + <div className='modal-dialog'> + <div className='modal-content'> + <div className='modal-header'> + <h4 className='modal-title'>{this.props.title}</h4> + </div> + <div className='modal-body'> + {this.props.message} + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + onClick={this.handleConfirm} + type='button' + className='btn btn-primary' + > + {this.props.confirm_button} + </button> + </div> </div> - </div> - </div> + </div> </div> ); } -}); +} +ConfirmModal.defaultProps = { + parent_id: '', + id: '', + title: '', + message: '', + confirm_button: '' +}; +ConfirmModal.propTypes = { + parent_id: React.PropTypes.string, + id: React.PropTypes.string, + title: React.PropTypes.string, + message: React.PropTypes.string, + confirm_button: React.PropTypes.string +}; diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx index 55d6f509c..075f9c742 100644 --- a/web/react/components/delete_post_modal.jsx +++ b/web/react/components/delete_post_modal.jsx @@ -4,20 +4,28 @@ var Client = require('../utils/client.jsx'); var PostStore = require('../stores/post_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; -module.exports = React.createClass({ - handleDelete: function(e) { - Client.deletePost(this.state.channel_id, this.state.post_id, - function(data) { - var selected_list = this.state.selectedList; - if (selected_list && selected_list.order && selected_list.order.length > 0) { - var selected_post = selected_list.posts[selected_list.order[0]]; - if ((selected_post.id === this.state.post_id && this.state.title === "Post") || selected_post.root_id === this.state.post_id) { +export default class DeletePostModal extends React.Component { + constructor(props) { + super(props); + + this.handleDelete = this.handleDelete.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + + this.state = {title: '', postId: '', channelId: '', selectedList: PostStore.getSelectedPost(), comments: 0}; + } + handleDelete() { + Client.deletePost(this.state.channelId, this.state.postId, + function deleteSuccess() { + var selectedList = this.state.selectedList; + if (selectedList && selectedList.order && selectedList.order.length > 0) { + var selectedPost = selectedList.posts[selectedList.order[0]]; + if ((selectedPost.id === this.state.postId && this.state.title === 'Post') || selectedPost.root_id === this.state.postId) { AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_SEARCH, results: null @@ -27,14 +35,14 @@ module.exports = React.createClass({ type: ActionTypes.RECIEVED_POST_SELECTED, results: null }); - } else if (selected_post.id === this.state.post_id && this.state.title === "Comment") { - if (selected_post.root_id && selected_post.root_id.length > 0 && selected_list.posts[selected_post.root_id]) { - selected_list.order = [selected_post.root_id]; - delete selected_list.posts[selected_post.id]; + } else if (selectedPost.id === this.state.postId && this.state.title === 'Comment') { + if (selectedPost.root_id && selectedPost.root_id.length > 0 && selectedList.posts[selectedPost.root_id]) { + selectedList.order = [selectedPost.root_id]; + delete selectedList.posts[selectedPost.id]; AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_POST_SELECTED, - post_list: selected_list + post_list: selectedList }); AppDispatcher.handleServerAction({ @@ -44,67 +52,97 @@ module.exports = React.createClass({ } } } - PostStore.removePost(this.state.post_id, this.state.channel_id); - AsyncClient.getPosts(this.state.channel_id); + PostStore.removePost(this.state.postId, this.state.channelId); + AsyncClient.getPosts(this.state.channelId); }.bind(this), - function(err) { - AsyncClient.dispatchError(err, "deletePost"); - }.bind(this) + function deleteFailed(err) { + AsyncClient.dispatchError(err, 'deletePost'); + } ); - }, - componentDidMount: function() { - var self = this; - $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { + } + componentDidMount() { + $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function freshOpen(e) { var newState = {}; - if(BrowserStore.getItem('edit_state_transfer')) { + if (BrowserStore.getItem('edit_state_transfer')) { newState = BrowserStore.getItem('edit_state_transfer'); BrowserStore.removeItem('edit_state_transfer'); } else { var button = e.relatedTarget; - newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') }; + newState = {title: $(button).attr('data-title'), channelId: $(button).attr('data-channelid'), postId: $(button).attr('data-postid'), comments: $(button).attr('data-comments')}; } - self.setState(newState); - }); - PostStore.addSelectedPostChangeListener(this._onChange); - }, - componentWillUnmount: function() { - PostStore.removeSelectedPostChangeListener(this._onChange); - }, - _onChange: function() { + this.setState(newState); + }.bind(this)); + PostStore.addSelectedPostChangeListener(this.onListenerChange); + } + componentWillUnmount() { + PostStore.removeSelectedPostChangeListener(this.onListenerChange); + } + onListenerChange() { var newList = PostStore.getSelectedPost(); - if (!utils.areStatesEqual(this.state.selectedList, newList)) { - this.setState({ selectedList: newList }); + if (!Utils.areStatesEqual(this.state.selectedList, newList)) { + this.setState({selectedList: newList}); + } + } + render() { + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var commentWarning = ''; + if (this.state.comments > 0) { + commentWarning = 'This post has ' + this.state.comments + ' comment(s) on it.'; } - }, - getInitialState: function() { - return { title: "", post_id: "", channel_id: "", selectedList: PostStore.getSelectedPost(), comments: 0 }; - }, - render: function() { - var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null; return ( - <div className="modal fade" id="delete_post" ref="modal" role="dialog" tabIndex="-1" aria-hidden="true"> - <div className="modal-dialog modal-push-down"> - <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">Confirm {this.state.title} Delete</h4> + <div + className='modal fade' + id='delete_post' + ref='modal' + role='dialog' + tabIndex='-1' + aria-hidden='true' + > + <div className='modal-dialog modal-push-down'> + <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'>Confirm {this.state.title} Delete</h4> </div> - <div className="modal-body"> + <div className='modal-body'> Are you sure you want to delete the {this.state.title.toLowerCase()}? <br/> <br/> - { this.state.comments > 0 ? - "This post has " + this.state.comments + " comment(s) on it." - : "" } + {commentWarning} </div> - <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> - <button type="button" className="btn btn-danger" data-dismiss="modal" onClick={this.handleDelete}>Delete</button> + {error} + <div className='modal-footer'> + <button + type='button' + className='btn btn-default' + data-dismiss='modal' + > + Cancel + </button> + <button + type='button' + className='btn btn-danger' + data-dismiss='modal' + onClick={this.handleDelete} + > + Delete + </button> </div> </div> </div> </div> ); } -}); +} diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 678eb9928..f2e91dd98 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -1,35 +1,58 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -module.exports = React.createClass({ - handleResend: function() { - window.location.href = window.location.href + "&resend=true" - }, - render: function() { - var title = ""; - var body = ""; - var resend = ""; - if (this.props.isVerified === "true") { - title = config.SiteName + " Email Verified"; - body = <p>Your email has been verified! Click <a href={this.props.teamURL + "?email=" + this.props.userEmail}>here</a> to log in.</p>; +export default class EmailVerify extends React.Component { + constructor(props) { + super(props); + + this.handleResend = this.handleResend.bind(this); + + this.state = {}; + } + handleResend() { + window.location.href = window.location.href + '&resend=true'; + } + render() { + var title = ''; + var body = ''; + var resend = ''; + if (this.props.isVerified === 'true') { + title = config.SiteName + ' Email Verified'; + body = <p>Your email has been verified! Click <a href={this.props.teamURL + '?email=' + this.props.userEmail}>here</a> to log in.</p>; } else { - title = config.SiteName + " Email Not Verified"; + title = config.SiteName + ' Email Not Verified'; body = <p>Please verify your email address. Check your inbox for an email.</p>; - resend = <button onClick={this.handleResend} className="btn btn-primary">Resend Email</button> + resend = (<button + onClick={this.handleResend} + className='btn btn-primary' + > + Resend Email + </button>); } return ( - <div className="col-sm-offset-4 col-sm-4"> - <div className="panel panel-default"> - <div className="panel-heading"> - <h3 className="panel-title">{ title }</h3> + <div className='col-sm-offset-4 col-sm-4'> + <div className='panel panel-default'> + <div className='panel-heading'> + <h3 className='panel-title'>{title}</h3> </div> - <div className="panel-body"> - { body } - { resend } + <div className='panel-body'> + {body} + {resend} </div> </div> </div> ); } -}); +} + +EmailVerify.defaultProps = { + isVerified: 'false', + teamURL: '', + userEmail: '' +}; +EmailVerify.propTypes = { + isVerified: React.PropTypes.string, + teamURL: React.PropTypes.string, + userEmail: React.PropTypes.string +}; diff --git a/web/react/components/file_preview.jsx b/web/react/components/file_preview.jsx index d1b2f734a..33382a439 100644 --- a/web/react/components/file_preview.jsx +++ b/web/react/components/file_preview.jsx @@ -1,14 +1,17 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require('../stores/user_store.jsx'); -var ChannelStore = require('../stores/channel_store.jsx'); -var client = require('../utils/client.jsx'); -var utils = require('../utils/utils.jsx'); -var Constants = require('../utils/constants.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - handleRemove: function(e) { +export default class FilePreview extends React.Component { + constructor(props) { + super(props); + + this.handleRemove = this.handleRemove.bind(this); + + this.state = {}; + } + handleRemove(e) { var previewDiv = e.target.parentNode.parentNode; if (previewDiv.hasAttribute('data-filename')) { @@ -16,51 +19,96 @@ module.exports = React.createClass({ } else if (previewDiv.hasAttribute('data-client-id')) { this.props.onRemove(previewDiv.getAttribute('data-client-id')); } - }, - render: function() { + } + render() { var previews = []; - this.props.files.forEach(function(filename) { - + this.props.files.forEach(function setupPreview(fullFilename) { + var filename = fullFilename; var originalFilename = filename; var filenameSplit = filename.split('.'); - var ext = filenameSplit[filenameSplit.length-1]; - var type = utils.getFileType(ext); + var ext = filenameSplit[filenameSplit.length - 1]; + var type = Utils.getFileType(ext); + // This is a temporary patch to fix issue with old files using absolute paths - if (filename.indexOf("/api/v1/files/get") != -1) { - filename = filename.split("/api/v1/files/get")[1]; + + if (filename.indexOf('/api/v1/files/get') !== -1) { + filename = filename.split('/api/v1/files/get')[1]; } - filename = utils.getWindowLocationOrigin() + "/api/v1/files/get" + filename; + filename = Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename; - if (type === "image") { + if (type === 'image') { previews.push( - <div key={filename} className="preview-div" data-filename={originalFilename}> - <img className="preview-img" src={filename}/> - <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> + <div + key={filename} + className='preview-div' + data-filename={originalFilename} + > + <img + className='preview-img' + src={filename} + /> + <a + className='remove-preview' + onClick={this.handleRemove} + > + <i className='glyphicon glyphicon-remove'/> + </a> </div> ); } else { previews.push( - <div key={filename} className="preview-div custom-file" data-filename={originalFilename}> - <div className={"file-icon "+utils.getIconClassName(type)}/> - <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> + <div + key={filename} + className='preview-div custom-file' + data-filename={originalFilename} + > + <div className={'file-icon ' + Utils.getIconClassName(type)}/> + <a + className='remove-preview' + onClick={this.handleRemove} + > + <i className='glyphicon glyphicon-remove'/> + </a> </div> ); } }.bind(this)); - this.props.uploadsInProgress.forEach(function(clientId) { + this.props.uploadsInProgress.forEach(function addUploadsInProgress(clientId) { previews.push( - <div className="preview-div" data-client-id={clientId}> - <img className="spinner" src="/static/images/load.gif"/> - <a className="remove-preview" onClick={this.handleRemove}><i className="glyphicon glyphicon-remove"/></a> + <div + key={clientId} + className='preview-div' + data-client-id={clientId} + > + <img + className='spinner' + src='/static/images/load.gif' + /> + <a + className='remove-preview' + onClick={this.handleRemove} + > + <i className='glyphicon glyphicon-remove'/> + </a> </div> ); }.bind(this)); return ( - <div className="preview-container"> + <div className='preview-container'> {previews} </div> ); } -}); +} + +FilePreview.defaultProps = { + files: null, + uploadsInProgress: null +}; +FilePreview.propTypes = { + onRemove: React.PropTypes.func.isRequired, + files: React.PropTypes.array, + uploadsInProgress: React.PropTypes.array +}; diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx index 114dc183f..72a2a6251 100644 --- a/web/react/components/mention.jsx +++ b/web/react/components/mention.jsx @@ -1,30 +1,57 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var UserStore = require("../stores/user_store.jsx"); +var UserStore = require('../stores/user_store.jsx'); -module.exports = React.createClass({ - handleClick: function() { +export default class Mention extends React.Component { + constructor(props) { + super(props); + + this.handleClick = this.handleClick.bind(this); + + this.state = null; + } + handleClick() { this.props.handleClick(this.props.username); - }, - getInitialState: function() { - return null; - }, - render: function() { - var self = this; + } + render() { var icon; var timestamp = UserStore.getCurrentUser().update_at; - if (this.props.id === "allmention" || this.props.id === "channelmention") { - icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>; + if (this.props.id === 'allmention' || this.props.id === 'channelmention') { + icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; } else if (this.props.id != null) { - icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>; + icon = (<span><img + className='mention-img' + src={'/api/v1/users/' + this.props.id + '/image?time=' + timestamp} + /> + </span>); } else { - icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>; + icon = <span><i className='mention-img fa fa-users fa-2x'></i></span>; } return ( - <div className={"mentions-name " + this.props.isFocused} id={this.props.id + "_mentions"} onClick={this.handleClick} onMouseEnter={this.props.handleMouseEnter}> - <div className="pull-left">{icon}</div> - <div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div> + <div + className={'mentions-name ' + this.props.isFocused} + id={this.props.id + '_mentions'} + onClick={this.handleClick} + onMouseEnter={this.props.handleMouseEnter} + > + <div className='pull-left'>{icon}</div> + <div className='pull-left mention-align'><span>@{this.props.username}</span><span className='mention-fullname'>{this.props.secondary_text}</span></div> </div> ); } -}); +} + +Mention.defaultProps = { + username: '', + id: '', + isFocused: '', + secondary_text: '' +}; +Mention.propTypes = { + handleClick: React.PropTypes.func.isRequired, + handleMouseEnter: React.PropTypes.func.isRequired, + username: React.PropTypes.string, + id: React.PropTypes.string, + isFocused: React.PropTypes.string, + secondary_text: React.PropTypes.string +}; diff --git a/web/react/components/message_wrapper.jsx b/web/react/components/message_wrapper.jsx index 5fc88a61b..bce305853 100644 --- a/web/react/components/message_wrapper.jsx +++ b/web/react/components/message_wrapper.jsx @@ -1,17 +1,30 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -module.exports = React.createClass({ - render: function() { +export default class MessageWrapper extends React.Component { + constructor(props) { + super(props); + this.state = {}; + } + render() { if (this.props.message) { - var inner = utils.textToJsx(this.props.message, this.props.options); + var inner = Utils.textToJsx(this.props.message, this.props.options); return ( <div>{inner}</div> ); - } else { - return <div/> } + + return <div/>; } -}); +} + +MessageWrapper.defaultProps = { + message: null, + options: null +}; +MessageWrapper.propTypes = { + message: React.PropTypes.string, + options: React.PropTypes.object +}; diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx new file mode 100644 index 000000000..e818a5c92 --- /dev/null +++ b/web/react/components/navbar_dropdown.jsx @@ -0,0 +1,209 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Utils = require('../utils/utils.jsx'); +var client = require('../utils/client.jsx'); +var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); + +var Constants = require('../utils/constants.jsx'); + +function getStateFromStores() { + return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; +} + +export default class NavbarDropdown extends React.Component { + constructor(props) { + super(props); + this.blockToggle = false; + + this.handleLogoutClick = this.handleLogoutClick.bind(this); + this.onListenerChange = this.onListenerChange.bind(this); + + this.state = getStateFromStores(); + } + handleLogoutClick(e) { + e.preventDefault(); + client.logout(); + } + componentDidMount() { + UserStore.addTeamsChangeListener(this.onListenerChange); + TeamStore.addChangeListener(this.onListenerChange); + + $(React.findDOMNode(this.refs.dropdown)).on('hide.bs.dropdown', function resetDropdown() { + this.blockToggle = true; + setTimeout(function blockTimeout() { + this.blockToggle = false; + }.bind(this), 100); + }.bind(this)); + } + componentWillUnmount() { + UserStore.removeTeamsChangeListener(this.onListenerChange); + TeamStore.removeChangeListener(this.onListenerChange); + + $(React.findDOMNode(this.refs.dropdown)).off('hide.bs.dropdown'); + } + onListenerChange() { + var newState = getStateFromStores(); + if (!Utils.areStatesEqual(newState, this.state)) { + this.setState(newState); + } + } + render() { + var teamLink = ''; + var inviteLink = ''; + var manageLink = ''; + var currentUser = UserStore.getCurrentUser(); + var isAdmin = false; + var teamSettings = null; + + if (currentUser != null) { + isAdmin = currentUser.roles.indexOf('admin') > -1; + + inviteLink = (<li> + <a + href='#' + data-toggle='modal' + data-target='#invite_member' + > + Invite New Member + </a> + </li>); + + if (this.props.teamType === 'O') { + teamLink = ( + <li> + <a + href='#' + data-toggle='modal' + data-target='#get_link' + data-title='Team Invite' + data-value={Utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id} + > + Get Team Invite Link + </a> + </li> + ); + } + } + + if (isAdmin) { + manageLink = (<li> + <a + href='#' + data-toggle='modal' + data-target='#team_members' + > + Manage Team + </a> + </li>); + teamSettings = (<li> + <a + href='#' + data-toggle='modal' + data-target='#team_settings' + > + Team Settings + </a> + </li>); + } + + var teams = []; + + teams.push(<li + className='divider' + key='div' + > + </li>); + if (this.state.teams.length > 1 && this.state.currentTeam) { + var curTeamName = this.state.currentTeam.name; + this.state.teams.forEach(function listTeams(teamName) { + if (teamName !== curTeamName) { + teams.push(<li key={teamName}><a href={Utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>); + } + }); + } + teams.push(<li key='newTeam_li'> + <a + key='newTeam_a' + target='_blank' + href={Utils.getWindowLocationOrigin() + '/signup_team'} + > + Create a New Team + </a> + </li>); + + return ( + <ul className='nav navbar-nav navbar-right'> + <li + ref='dropdown' + className='dropdown' + > + <a + href='#' + className='dropdown-toggle' + data-toggle='dropdown' + role='button' + aria-expanded='false' + > + <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_settings' + > + Account Settings + </a> + </li> + {teamSettings} + {inviteLink} + {teamLink} + {manageLink} + <li> + <a + href='#' + onClick={this.handleLogoutClick} + > + Logout + </a> + </li> + {teams} + <li className='divider'></li> + <li> + <a + target='_blank' + href={config.HelpLink} + > + Help + </a> + </li> + <li> + <a + target='_blank' + href={config.ReportProblemLink} + > + Report a Problem + </a> + </li> + </ul> + </li> + </ul> + ); + } +} + +NavbarDropdown.defaultProps = { + teamType: '' +}; +NavbarDropdown.propTypes = { + teamType: React.PropTypes.string +}; diff --git a/web/react/components/notify_counts.jsx b/web/react/components/notify_counts.jsx index ebc49882b..0b7c41b62 100644 --- a/web/react/components/notify_counts.jsx +++ b/web/react/components/notify_counts.jsx @@ -23,27 +23,30 @@ function getCountsStateFromStores() { return {count: count}; } -module.exports = React.createClass({ - displayName: 'NotifyCounts', - componentDidMount: function() { +export default class NotifyCounts extends React.Component { + constructor(props) { + super(props); + + this.onListenerChange = this.onListenerChange.bind(this); + + this.state = getCountsStateFromStores(); + } + componentDidMount() { ChannelStore.addChangeListener(this.onListenerChange); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { ChannelStore.removeChangeListener(this.onListenerChange); - }, - onListenerChange: function() { + } + onListenerChange() { var newState = getCountsStateFromStores(); if (!utils.areStatesEqual(newState, this.state)) { this.setState(newState); } - }, - getInitialState: function() { - return getCountsStateFromStores(); - }, - render: function() { + } + render() { if (this.state.count) { return <span className='badge badge-notify'>{this.state.count}</span>; } return null; } -}); +} diff --git a/web/react/components/password_reset.jsx b/web/react/components/password_reset.jsx index b2edea620..399d3b7b9 100644 --- a/web/react/components/password_reset.jsx +++ b/web/react/components/password_reset.jsx @@ -1,143 +1,47 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var UserStore = require('../stores/user_store.jsx'); +var PasswordResetSendLink = require('./password_reset_send_link.jsx'); +var PasswordResetForm = require('./password_reset_form.jsx'); -SendResetPasswordLink = React.createClass({ - handleSendLink: function(e) { - e.preventDefault(); - var state = {}; +export default class PasswordReset extends React.Component { + constructor(props) { + super(props); - var email = this.refs.email.getDOMNode().value.trim(); - if (!email) { - state.error = "Please enter a valid email address." - this.setState(state); - return; - } - - state.error = null; - this.setState(state); - - data = {}; - data['email'] = email; - data['name'] = this.props.teamName; - - client.sendPasswordReset(data, - function(data) { - this.setState({ error: null, update_text: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, more_update_text: "Please check your inbox." }); - $(this.refs.reset_form.getDOMNode()).hide(); - }.bind(this), - function(err) { - this.setState({ error: err.message, update_text: null, more_update_text: null }); - }.bind(this) - ); - }, - getInitialState: function() { - return {}; - }, - render: function() { - var update_text = this.state.update_text ? <div className="reset-form alert alert-success">{this.state.update_text}{this.state.more_update_text}</div> : null; - var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null; - - return ( - <div className="col-sm-12"> - <div className="signup-team__container"> - <h3>Password Reset</h3> - { update_text } - <form onSubmit={this.handleSendLink} ref="reset_form"> - <p>{"To reset your password, enter the email address you used to sign up for " + this.props.teamDisplayName + "."}</p> - <div className={error ? 'form-group has-error' : 'form-group'}> - <input type="text" className="form-control" name="email" ref="email" placeholder="Email" /> - </div> - { error } - <button type="submit" className="btn btn-primary">Reset my password</button> - </form> - </div> - </div> - ); + this.state = {}; } -}); - -ResetPassword = React.createClass({ - handlePasswordReset: function(e) { - e.preventDefault(); - var state = {}; - - var password = this.refs.password.getDOMNode().value.trim(); - if (!password || password.length < 5) { - state.error = "Please enter at least 5 characters." - this.setState(state); - return; - } - - state.error = null; - this.setState(state); - - data = {}; - data['new_password'] = password; - data['hash'] = this.props.hash; - data['data'] = this.props.data; - data['name'] = this.props.teamName; - - client.resetPassword(data, - function(data) { - this.setState({ error: null, update_text: "Your password has been updated successfully." }); - }.bind(this), - function(err) { - this.setState({ error: err.message, update_text: null }); - }.bind(this) - ); - }, - getInitialState: function() { - return {}; - }, - render: function() { - var update_text = this.state.update_text ? <div className="form-group"><br/><label className="control-label reset-form">{this.state.update_text} Click <a href={"/" + this.props.teamName + "/login"}>here</a> to log in.</label></div> : null; - var error = this.state.error ? <div className="form-group has-error"><label className="control-label">{this.state.error}</label></div> : null; - - return ( - <div className="col-sm-12"> - <div className="signup-team__container"> - <h3>Password Reset</h3> - <form onSubmit={this.handlePasswordReset}> - <p>{"Enter a new password for your " + this.props.teamDisplayName + " " + config.SiteName + " account."}</p> - <div className={error ? 'form-group has-error' : 'form-group'}> - <input type="password" className="form-control" name="password" ref="password" placeholder="Password" /> - </div> - { error } - <button type="submit" className="btn btn-primary">Change my password</button> - { update_text } - </form> - </div> - </div> - ); - } -}); - -module.exports = React.createClass({ - getInitialState: function() { - return {}; - }, - render: function() { - - if (this.props.isReset === "false") { - return ( - <SendResetPasswordLink - teamDisplayName={this.props.teamDisplayName} - teamName={this.props.teamName} - /> - ); - } else { + render() { + if (this.props.isReset === 'false') { return ( - <ResetPassword + <PasswordResetSendLink teamDisplayName={this.props.teamDisplayName} teamName={this.props.teamName} - hash={this.props.hash} - data={this.props.data} /> ); } + + return ( + <PasswordResetForm + teamDisplayName={this.props.teamDisplayName} + teamName={this.props.teamName} + hash={this.props.hash} + data={this.props.data} + /> + ); } -}); +} + +PasswordReset.defaultProps = { + isReset: '', + teamName: '', + teamDisplayName: '', + hash: '', + data: '' +}; +PasswordReset.propTypes = { + isReset: React.PropTypes.string, + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + hash: React.PropTypes.string, + data: React.PropTypes.string +}; diff --git a/web/react/components/password_reset_form.jsx b/web/react/components/password_reset_form.jsx new file mode 100644 index 000000000..7acd2d1f7 --- /dev/null +++ b/web/react/components/password_reset_form.jsx @@ -0,0 +1,100 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var client = require('../utils/client.jsx'); + +export default class PasswordResetForm extends React.Component { + constructor(props) { + super(props); + + this.handlePasswordReset = this.handlePasswordReset.bind(this); + + this.state = {}; + } + handlePasswordReset(e) { + e.preventDefault(); + var state = {}; + + var password = React.findDOMNode(this.refs.password).value.trim(); + if (!password || password.length < 5) { + state.error = 'Please enter at least 5 characters.'; + this.setState(state); + return; + } + + state.error = null; + this.setState(state); + + var data = {}; + data.new_password = password; + data.hash = this.props.hash; + data.data = this.props.data; + data.name = this.props.teamName; + + client.resetPassword(data, + function resetSuccess() { + this.setState({error: null, updateText: 'Your password has been updated successfully.'}); + }.bind(this), + function resetFailure(err) { + this.setState({error: err.message, updateText: null}); + }.bind(this) + ); + } + render() { + var updateText = null; + if (this.state.updateText) { + updateText = <div className='form-group'><br/><label className='control-label reset-form'>{this.state.updateText} Click <a href={'/' + this.props.teamName + '/login'}>here</a> to log in.</label></div>; + } + + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var formClass = 'form-group'; + if (error) { + formClass += ' has-error'; + } + + return ( + <div className='col-sm-12'> + <div className='signup-team__container'> + <h3>Password Reset</h3> + <form onSubmit={this.handlePasswordReset}> + <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + config.SiteName + ' account.'}</p> + <div className={formClass}> + <input + type='password' + className='form-control' + name='password' + ref='password' + placeholder='Password' + /> + </div> + {error} + <button + type='submit' + className='btn btn-primary' + > + Change my password + </button> + {updateText} + </form> + </div> + </div> + ); + } +} + +PasswordResetForm.defaultProps = { + teamName: '', + teamDisplayName: '', + hash: '', + data: '' +}; +PasswordResetForm.propTypes = { + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string, + hash: React.PropTypes.string, + data: React.PropTypes.string +}; diff --git a/web/react/components/password_reset_send_link.jsx b/web/react/components/password_reset_send_link.jsx new file mode 100644 index 000000000..1e6cc3607 --- /dev/null +++ b/web/react/components/password_reset_send_link.jsx @@ -0,0 +1,98 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var client = require('../utils/client.jsx'); + +export default class PasswordResetSendLink extends React.Component { + constructor(props) { + super(props); + + this.handleSendLink = this.handleSendLink.bind(this); + + this.state = {}; + } + handleSendLink(e) { + e.preventDefault(); + var state = {}; + + var email = React.findDOMNode(this.refs.email).value.trim(); + if (!email) { + state.error = 'Please enter a valid email address.'; + this.setState(state); + return; + } + + state.error = null; + this.setState(state); + + var data = {}; + data.email = email; + data.name = this.props.teamName; + + client.sendPasswordReset(data, + function passwordResetSent() { + this.setState({error: null, updateText: <p>A password reset link has been sent to <b>{email}</b> for your <b>{this.props.teamDisplayName}</b> team on {window.location.hostname}.</p>, moreUpdateText: 'Please check your inbox.'}); + $(React.findDOMNode(this.refs.reset_form)).hide(); + }.bind(this), + function passwordResetFailedToSend(err) { + this.setState({error: err.message, update_text: null, moreUpdateText: null}); + }.bind(this) + ); + } + render() { + var updateText = null; + if (this.state.updateText) { + updateText = <div className='reset-form alert alert-success'>{this.state.updateText}{this.state.moreUpdateText}</div>; + } + + var error = null; + if (this.state.error) { + error = <div className='form-group has-error'><label className='control-label'>{this.state.error}</label></div>; + } + + var formClass = 'form-group'; + if (error) { + formClass += ' has-error'; + } + + return ( + <div className='col-sm-12'> + <div className='signup-team__container'> + <h3>Password Reset</h3> + {updateText} + <form + onSubmit={this.handleSendLink} + ref='reset_form' + > + <p>{'To reset your password, enter the email address you used to sign up for ' + this.props.teamDisplayName + '.'}</p> + <div className={formClass}> + <input + type='text' + className='form-control' + name='email' + ref='email' + placeholder='Email' + /> + </div> + {error} + <button + type='submit' + className='btn btn-primary' + > + Reset my password + </button> + </form> + </div> + </div> + ); + } +} + +PasswordResetSendLink.defaultProps = { + teamName: '', + teamDisplayName: '' +}; +PasswordResetSendLink.propTypes = { + teamName: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/web/react/components/post_deleted_modal.jsx b/web/react/components/post_deleted_modal.jsx index 83b007bad..d284a9d1b 100644 --- a/web/react/components/post_deleted_modal.jsx +++ b/web/react/components/post_deleted_modal.jsx @@ -3,34 +3,61 @@ var UserStore = require('../stores/user_store.jsx'); -module.exports = React.createClass({ - getInitialState: function() { - return { }; - }, - render: function() { - var currentUser = UserStore.getCurrentUser() +export default class PostDeletedModal extends React.Component { + constructor(props) { + super(props); + + this.state = {}; + } + render() { + var currentUser = UserStore.getCurrentUser(); if (currentUser != null) { return ( - <div className="modal fade" ref="modal" id="post_deleted" 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" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title" id="myModalLabel">Comment could not be posted</h4> - </div> - <div className="modal-body"> - <p>Someone deleted the message on which you tried to post a comment.</p> + <div + className='modal fade' + ref='modal' + id='post_deleted' + 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' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + id='myModalLabel' + > + Comment could not be posted + </h4> + </div> + <div className='modal-body'> + <p>Someone deleted the message on which you tried to post a comment.</p> + </div> + <div className='modal-footer'> + <button + type='button' + className='btn btn-primary' + data-dismiss='modal' + > + Okay + </button> + </div> </div> - <div className="modal-footer"> - <button type="button" className="btn btn-primary" data-dismiss="modal">Okay</button> - </div> - </div> - </div> + </div> </div> ); - } else { - return <div/>; } + + return <div/>; } -}); +} diff --git a/web/react/components/rhs_comment.jsx b/web/react/components/rhs_comment.jsx index 7df2fed9e..e74ab7f13 100644 --- a/web/react/components/rhs_comment.jsx +++ b/web/react/components/rhs_comment.jsx @@ -6,10 +6,10 @@ var ChannelStore = require('../stores/channel_store.jsx'); var UserProfile = require('./user_profile.jsx'); var UserStore = require('../stores/user_store.jsx'); var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var Constants = require('../utils/constants.jsx'); var FileAttachmentList = require('./file_attachment_list.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); var ActionTypes = Constants.ActionTypes; @@ -25,7 +25,7 @@ export default class RhsComment extends React.Component { e.preventDefault(); var post = this.props.post; - client.createPost(post, post.channel_id, + Client.createPost(post, post.channel_id, function success(data) { AsyncClient.getPosts(post.channel_id); @@ -52,7 +52,7 @@ export default class RhsComment extends React.Component { this.forceUpdate(); } shouldComponentUpdate(nextProps) { - if (!utils.areStatesEqual(nextProps.post, this.props.post)) { + if (!Utils.areStatesEqual(nextProps.post, this.props.post)) { return true; } @@ -73,7 +73,7 @@ export default class RhsComment extends React.Component { type = 'Comment'; } - var message = utils.textToJsx(post.message); + var message = Utils.textToJsx(post.message); var timestamp = UserStore.getCurrentUser().update_at; var loading; @@ -182,7 +182,7 @@ export default class RhsComment extends React.Component { </li> <li className='post-header-col'> <time className='post-right-comment-time'> - {utils.displayCommentDateTime(post.create_at)} + {Utils.displayCommentDateTime(post.create_at)} </time> </li> <li className='post-header-col post-header__reply'> diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index af65b7e1d..6e219cc6c 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -1,133 +1,25 @@ // 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 NavbarDropdown = require('./navbar_dropdown.jsx'); var UserStore = require('../stores/user_store.jsx'); -var TeamStore = require('../stores/team_store.jsx'); -var Constants = require('../utils/constants.jsx'); +export default class SidebarHeader extends React.Component { + constructor(props) { + super(props); -function getStateFromStores() { - return {teams: UserStore.getTeams(), currentTeam: TeamStore.getCurrent()}; -} - -var NavbarDropdown = React.createClass({ - handleLogoutClick: function(e) { - e.preventDefault(); - client.logout(); - }, - blockToggle: false, - componentDidMount: function() { - UserStore.addTeamsChangeListener(this.onListenerChange); - TeamStore.addChangeListener(this.onListenerChange); - - var self = this; - $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function() { - self.blockToggle = true; - setTimeout(function() { - self.blockToggle = false; - }, 100); - }); - }, - componentWillUnmount: function() { - UserStore.removeTeamsChangeListener(this.onListenerChange); - TeamStore.removeChangeListener(this.onListenerChange); - - $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown'); - }, - onListenerChange: function() { - if (this.isMounted()) { - var newState = getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } - } - }, - getInitialState: function() { - return getStateFromStores(); - }, - render: function() { - var teamLink = ''; - var inviteLink = ''; - var manageLink = ''; - var currentUser = UserStore.getCurrentUser(); - var isAdmin = false; - var teamSettings = null; - - if (currentUser != null) { - isAdmin = currentUser.roles.indexOf('admin') > -1; - - inviteLink = (<li> <a href='#' data-toggle='modal' data-target='#invite_member'>Invite New Member</a> </li>); - - if (this.props.teamType === 'O') { - teamLink = ( - <li> - <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id}>Get Team Invite Link</a> - </li> - ); - } - } - - if (isAdmin) { - manageLink = (<li> <a href='#' data-toggle='modal' data-target='#team_members'>Manage Team</a> </li>); - teamSettings = (<li> <a href='#' data-toggle='modal' data-target='#team_settings'>Team Settings</a> </li>); - } + this.toggleDropdown = this.toggleDropdown.bind(this); - var teams = []; - - teams.push(<li className='divider' key='div'></li>); - if (this.state.teams.length > 1 && this.state.currentTeam) { - var curTeamName = this.state.currentTeam.name; - this.state.teams.forEach(function(teamName) { - if (teamName !== curTeamName) { - teams.push(<li key={teamName}><a href={utils.getWindowLocationOrigin() + '/' + teamName}>Switch to {teamName}</a></li>); - } - }); - } - teams.push(<li key='newTeam_li'><a key='newTeam_a' target="_blank" href={utils.getWindowLocationOrigin() + '/signup_team' }>Create a New Team</a></li>); - - return ( - <ul className='nav navbar-nav navbar-right'> - <li ref='dropdown' className='dropdown'> - <a href='#' className='dropdown-toggle' data-toggle='dropdown' role='button' aria-expanded='false'> - <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_settings'>Account Settings</a></li> - {teamSettings} - {inviteLink} - {teamLink} - {manageLink} - <li><a href='#' onClick={this.handleLogoutClick}>Logout</a></li> - {teams} - <li className='divider'></li> - <li><a target='_blank' href={config.HelpLink}>Help</a></li> - <li><a target='_blank' href={config.ReportProblemLink}>Report a Problem</a></li> - </ul> - </li> - </ul> - ); + this.state = {}; } -}); - -module.exports = React.createClass({ - displayName: 'SidebarHeader', - getDefaultProps: function() { - return { - teamDisplayName: config.SiteName - }; - }, - - toggleDropdown: function() { + toggleDropdown() { if (this.refs.dropdown.blockToggle) { this.refs.dropdown.blockToggle = false; return; } $('.team__header').find('.dropdown-toggle').dropdown('toggle'); - }, - - render: function() { + } + render() { var me = UserStore.getCurrentUser(); var profilePicture = null; @@ -136,20 +28,38 @@ module.exports = React.createClass({ } if (me.last_picture_update) { - profilePicture = (<img className='user__picture' src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} />); + profilePicture = (<img + className='user__picture' + src={'/api/v1/users/' + me.id + '/image?time=' + me.update_at} + />); } return ( <div className='team__header theme'> - <a href='#' onClick={this.toggleDropdown}> + <a + href='#' + onClick={this.toggleDropdown} + > {profilePicture} <div className='header__info'> <div className='user__name'>{'@' + me.username}</div> - <div className='team__name'>{this.props.teamDisplayName }</div> + <div className='team__name'>{this.props.teamDisplayName}</div> </div> </a> - <NavbarDropdown ref='dropdown' teamType={this.props.teamType} /> + <NavbarDropdown + ref='dropdown' + teamType={this.props.teamType} + /> </div> ); } -}); +} + +SidebarHeader.defaultProps = { + teamDisplayName: config.SiteName, + teamType: '' +}; +SidebarHeader.propTypes = { + teamDisplayName: React.PropTypes.string, + teamType: React.PropTypes.string +}; diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index df75e3adf..9aeda6626 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -4,47 +4,49 @@ var SearchResults = require('./search_results.jsx'); var RhsThread = require('./rhs_thread.jsx'); var PostStore = require('../stores/post_store.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); -function getStateFromStores(from_search) { - return { search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch() }; +function getStateFromStores() { + return {search_visible: PostStore.getSearchResults() != null, post_right_visible: PostStore.getSelectedPost() != null, is_mention_search: PostStore.getIsMentionSearch()}; } -module.exports = React.createClass({ - componentDidMount: function() { - PostStore.addSearchChangeListener(this._onSearchChange); - PostStore.addSelectedPostChangeListener(this._onSelectedChange); - }, - componentWillUnmount: function() { - PostStore.removeSearchChangeListener(this._onSearchChange); - PostStore.removeSelectedPostChangeListener(this._onSelectedChange); - }, - _onSelectedChange: function(from_search) { - if (this.isMounted()) { - var newState = getStateFromStores(from_search); - newState.from_search = from_search; - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } +export default class SidebarRight extends React.Component { + constructor(props) { + super(props); + + this.onSelectedChange = this.onSelectedChange.bind(this); + this.onSearchChange = this.onSearchChange.bind(this); + this.resize = this.resize.bind(this); + + this.state = getStateFromStores(); + } + componentDidMount() { + PostStore.addSearchChangeListener(this.onSearchChange); + PostStore.addSelectedPostChangeListener(this.onSelectedChange); + } + componentWillUnmount() { + PostStore.removeSearchChangeListener(this.onSearchChange); + PostStore.removeSelectedPostChangeListener(this.onSelectedChange); + } + onSelectedChange(fromSearch) { + var newState = getStateFromStores(fromSearch); + newState.from_search = fromSearch; + if (!Utils.areStatesEqual(newState, this.state)) { + this.setState(newState); } - }, - _onSearchChange: function() { - if (this.isMounted()) { - var newState = getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - this.setState(newState); - } + } + onSearchChange() { + var newState = getStateFromStores(); + if (!Utils.areStatesEqual(newState, this.state)) { + this.setState(newState); } - }, - resize: function() { + } + resize() { var postHolder = $('.post-list-holder-by-time'); postHolder[0].scrollTop = postHolder[0].scrollHeight - 224; - }, - getInitialState: function() { - return getStateFromStores(); - }, - render: function() { - if (! (this.state.search_visible || this.state.post_right_visible)) { + } + render() { + if (!(this.state.search_visible || this.state.post_right_visible)) { $('.inner__wrap').removeClass('move--left').removeClass('move--right'); $('.sidebar--right').removeClass('move--left'); this.resize(); @@ -58,25 +60,27 @@ module.exports = React.createClass({ $('.sidebar--right').addClass('move--left'); $('.sidebar--right').prepend('<div class="sidebar__overlay"></div>'); this.resize(); - setTimeout(function(){ - $('.sidebar__overlay').fadeOut("200", function(){ + setTimeout(function overlayTimer() { + $('.sidebar__overlay').fadeOut('200', function fadeOverlay() { $(this).remove(); }); - },500) + }, 500); - var content = ""; + var content = ''; if (this.state.search_visible) { content = <SearchResults isMentionSearch={this.state.is_mention_search} />; - } - else if (this.state.post_right_visible) { - content = <RhsThread fromSearch={this.state.from_search} isMentionSearch={this.state.is_mention_search} />; + } else if (this.state.post_right_visible) { + content = (<RhsThread + fromSearch={this.state.from_search} + isMentionSearch={this.state.is_mention_search} + />); } return ( - <div className="sidebar-right-container"> - { content } + <div className='sidebar-right-container'> + {content} </div> ); } -}); +} diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 756aae638..1d45548da 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -10,69 +10,99 @@ var UsernamePage = require('./team_signup_username_page.jsx'); var PasswordPage = require('./team_signup_password_page.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -module.exports = React.createClass({ - displayName: 'SignupTeamComplete', - propTypes: { - hash: React.PropTypes.string, - email: React.PropTypes.string, - data: React.PropTypes.string - }, - updateParent: function(state, skipSet) { +export default class SignupTeamComplete extends React.Component { + constructor(props) { + super(props); + + this.updateParent = this.updateParent.bind(this); + + var initialState = BrowserStore.getGlobalItem(props.hash); + + if (!initialState) { + initialState = {}; + initialState.wizard = 'welcome'; + initialState.team = {}; + initialState.team.email = this.props.email; + initialState.team.allowed_domains = ''; + initialState.invites = []; + initialState.invites.push(''); + initialState.invites.push(''); + initialState.invites.push(''); + initialState.user = {}; + initialState.hash = this.props.hash; + initialState.data = this.props.data; + } + + this.state = initialState; + } + updateParent(state, skipSet) { BrowserStore.setGlobalItem(this.props.hash, state); if (!skipSet) { this.setState(state); } - }, - getInitialState: function() { - var props = BrowserStore.getGlobalItem(this.props.hash); - - if (!props) { - props = {}; - props.wizard = 'welcome'; - props.team = {}; - props.team.email = this.props.email; - props.team.allowed_domains = ''; - props.invites = []; - props.invites.push(''); - props.invites.push(''); - props.invites.push(''); - props.user = {}; - props.hash = this.props.hash; - props.data = this.props.data; - } - - return props; - }, - render: function() { + } + render() { if (this.state.wizard === 'welcome') { - return <WelcomePage state={this.state} updateParent={this.updateParent} />; + return (<WelcomePage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'team_display_name') { - return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />; + return (<TeamDisplayNamePage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'team_url') { - return <TeamURLPage state={this.state} updateParent={this.updateParent} />; + return (<TeamURLPage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'allowed_domains') { - return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />; + return (<AllowedDomainsPage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'send_invites') { - return <SendInivtesPage state={this.state} updateParent={this.updateParent} />; + return (<SendInivtesPage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'username') { - return <UsernamePage state={this.state} updateParent={this.updateParent} />; + return (<UsernamePage + state={this.state} + updateParent={this.updateParent} + />); } if (this.state.wizard === 'password') { - return <PasswordPage state={this.state} updateParent={this.updateParent} />; + 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>); } -}); +} + +SignupTeamComplete.defaultProps = { + hash: '', + email: '', + data: '' +}; +SignupTeamComplete.propTypes = { + hash: React.PropTypes.string, + email: React.PropTypes.string, + data: React.PropTypes.string +}; diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 2080cc191..67e85d4de 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -7,11 +7,31 @@ var UserStore = require('../stores/user_store.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); var Constants = require('../utils/constants.jsx'); -module.exports = React.createClass({ - handleSubmit: function(e) { +export default class SignupUserComplete extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + + var initialState = BrowserStore.getGlobalItem(this.props.hash); + + if (!initialState) { + initialState = {}; + initialState.wizard = 'welcome'; + initialState.user = {}; + initialState.user.team_id = this.props.teamId; + initialState.user.email = this.props.email; + initialState.hash = this.props.hash; + initialState.data = this.props.data; + initialState.original_email = this.props.email; + } + + this.state = initialState; + } + handleSubmit(e) { e.preventDefault(); - this.state.user.username = this.refs.name.getDOMNode().value.trim(); + this.state.user.username = React.findDOMNode(this.refs.name).value.trim(); if (!this.state.user.username) { this.setState({nameError: 'This field is required', emailError: '', passwordError: '', serverError: ''}); return; @@ -31,14 +51,14 @@ module.exports = React.createClass({ return; } - this.state.user.email = this.refs.email.getDOMNode().value.trim(); + this.state.user.email = React.findDOMNode(this.refs.email).value.trim(); if (!this.state.user.email) { this.setState({nameError: '', emailError: 'This field is required', passwordError: ''}); return; } - this.state.user.password = this.refs.password.getDOMNode().value.trim(); - if (!this.state.user.password || this.state.user.password .length < 5) { + this.state.user.password = React.findDOMNode(this.refs.password).value.trim(); + if (!this.state.user.password || this.state.user.password .length < 5) { this.setState({nameError: '', emailError: '', passwordError: 'Please enter at least 5 characters', serverError: ''}); return; } @@ -48,11 +68,11 @@ module.exports = React.createClass({ this.state.user.allow_marketing = true; client.createUser(this.state.user, this.state.data, this.state.hash, - function(data) { + function createUserSuccess() { client.track('signup', 'signup_user_02_complete'); client.loginByEmail(this.props.teamName, this.state.user.email, this.state.user.password, - function(data) { + function emailLoginSuccess(data) { UserStore.setLastEmail(this.state.user.email); UserStore.setCurrentUser(data); if (this.props.hash > 0) { @@ -60,7 +80,7 @@ module.exports = React.createClass({ } window.location.href = '/'; }.bind(this), - function(err) { + function emailLoginFailure(err) { if (err.message === 'Login failed because email address has not been verified') { window.location.href = '/verify_email?email=' + encodeURIComponent(this.state.user.email) + '&teamname=' + encodeURIComponent(this.props.teamName); } else { @@ -69,28 +89,12 @@ module.exports = React.createClass({ }.bind(this) ); }.bind(this), - function(err) { + function createUserFailure(err) { this.setState({serverError: err.message}); }.bind(this) ); - }, - getInitialState: function() { - var state = BrowserStore.getGlobalItem(this.props.hash); - - if (!state) { - state = {}; - state.wizard = 'welcome'; - state.user = {}; - state.user.team_id = this.props.teamId; - state.user.email = this.props.email; - state.hash = this.props.hash; - state.data = this.props.data; - state.original_email = this.props.email; - } - - return state; - }, - render: function() { + } + render() { client.track('signup', 'signup_user_01_welcome'); if (this.state.wizard === 'finished') { @@ -134,16 +138,24 @@ module.exports = React.createClass({ yourEmailIs = <span>Your email address is {this.state.user.email}. You'll use this address to sign in to {config.SiteName}.</span>; } - var emailContainerStyle = "margin--extra"; + var emailContainerStyle = 'margin--extra'; if (this.state.original_email) { - emailContainerStyle = "hidden"; + emailContainerStyle = 'hidden'; } var email = ( <div className={emailContainerStyle}> <h5><strong>What's your email address?</strong></h5> <div className={emailDivStyle}> - <input type='email' ref='email' className='form-control' defaultValue={this.state.user.email} placeholder='' maxLength='128' autoFocus={true} /> + <input + type='email' + ref='email' + className='form-control' + defaultValue={this.state.user.email} + placeholder='' + maxLength='128' + autoFocus={true} + /> {emailError} </div> </div> @@ -155,7 +167,10 @@ module.exports = React.createClass({ var signupMessage = []; if (authServices.indexOf(Constants.GITLAB_SERVICE) >= 0) { signupMessage.push( - <a className='btn btn-custom-login gitlab' href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search}> + <a + className='btn btn-custom-login gitlab' + href={'/' + this.props.teamName + '/signup/gitlab' + window.location.search} + > <span className='icon' /> <span>with GitLab</span> </a> @@ -172,7 +187,13 @@ module.exports = React.createClass({ <div className='margin--extra'> <h5><strong>Choose your username</strong></h5> <div className={nameDivStyle}> - <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' /> + <input + type='text' + ref='name' + className='form-control' + placeholder='' + maxLength='128' + /> {nameError} <p className='form__hint'>Username must begin with a letter, and contain between 3 to 15 lowercase characters made up of numbers, letters, and the symbols '.', '-' and '_'</p> </div> @@ -180,12 +201,26 @@ module.exports = React.createClass({ <div className='margin--extra'> <h5><strong>Choose your password</strong></h5> <div className={passwordDivStyle}> - <input type='password' ref='password' className='form-control' placeholder='' maxLength='128' /> + <input + type='password' + ref='password' + className='form-control' + placeholder='' + maxLength='128' + /> {passwordError} </div> </div> </div> - <p className='margin--extra'><button type='submit' onClick={this.handleSubmit} className='btn-primary btn'>Create Account</button></p> + <p className='margin--extra'> + <button + type='submit' + onClick={this.handleSubmit} + className='btn-primary btn' + > + Create Account + </button> + </p> </div> ); } @@ -209,7 +244,10 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <img + className='signup-team-logo' + src='/static/images/logo.png' + /> <h5 className='margin--less'>Welcome to:</h5> <h2 className='signup-team__name'>{this.props.teamDisplayName}</h2> <h2 className='signup-team__subdomain'>on {config.SiteName}</h2> @@ -222,6 +260,23 @@ module.exports = React.createClass({ </div> ); } -}); - +} +SignupUserComplete.defaultProps = { + teamName: '', + hash: '', + teamId: '', + email: '', + data: null, + authServices: '', + teamDisplayName: '' +}; +SignupUserComplete.propTypes = { + teamName: React.PropTypes.string, + hash: React.PropTypes.string, + teamId: React.PropTypes.string, + email: React.PropTypes.string, + data: React.PropTypes.string, + authServices: React.PropTypes.string, + teamDisplayName: React.PropTypes.string +}; diff --git a/web/react/components/team_signup_send_invites_page.jsx b/web/react/components/team_signup_send_invites_page.jsx index 646a742ba..6d9b0ec03 100644 --- a/web/react/components/team_signup_send_invites_page.jsx +++ b/web/react/components/team_signup_send_invites_page.jsx @@ -2,9 +2,9 @@ // See License.txt for license information. var EmailItem = require('./team_signup_email_item.jsx'); -var utils = require('../utils/utils.jsx'); +var Utils = require('../utils/utils.jsx'); var ConfigStore = require('../stores/config_store.jsx'); -var client = require('../utils/client.jsx'); +var Client = require('../utils/client.jsx'); export default class TeamSignupSendInvitesPage extends React.Component { constructor(props) { @@ -71,7 +71,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { } keySubmit(e) { if (e && e.keyCode === 13) { - this.submitNext(e) + this.submitNext(e); } } componentWillMount() { @@ -92,7 +92,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { } } render() { - client.track('signup', 'signup_team_05_send_invites'); + Client.track('signup', 'signup_team_05_send_invites'); var content = null; var bottomContent = null; @@ -165,7 +165,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { className='signup-team-logo' src='/static/images/logo.png' /> - <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2> + <h2>{'Invite ' + Utils.toTitleCase(strings.Team) + ' Members'}</h2> {content} <div className='form-group'> <button @@ -190,6 +190,7 @@ export default class TeamSignupSendInvitesPage extends React.Component { ); } } + TeamSignupSendInvitesPage.propTypes = { state: React.PropTypes.object.isRequired, updateParent: React.PropTypes.func.isRequired diff --git a/web/react/components/team_signup_username_page.jsx b/web/react/components/team_signup_username_page.jsx index 56882e6a1..8efcd87e1 100644 --- a/web/react/components/team_signup_username_page.jsx +++ b/web/react/components/team_signup_username_page.jsx @@ -1,26 +1,29 @@ // 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 Utils = require('../utils/utils.jsx'); +var Client = require('../utils/client.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupUsernamePage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitBack: function(e) { +export default class TeamSignupUsernamePage extends React.Component { + constructor(props) { + super(props); + + this.submitBack = this.submitBack.bind(this); + this.submitNext = this.submitNext.bind(this); + + this.state = {}; + } + submitBack(e) { e.preventDefault(); this.props.state.wizard = 'send_invites'; this.props.updateParent(this.props.state); - }, - submitNext: function(e) { + } + submitNext(e) { e.preventDefault(); - var name = this.refs.name.getDOMNode().value.trim(); + var name = React.findDOMNode(this.refs.name).value.trim(); - var usernameError = utils.isValidUsername(name); + 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; @@ -32,12 +35,9 @@ module.exports = React.createClass({ this.props.state.wizard = 'password'; this.props.state.user.username = name; this.props.updateParent(this.props.state); - }, - getInitialState: function() { - return {}; - }, - render: function() { - client.track('signup', 'signup_team_06_username'); + } + render() { + Client.track('signup', 'signup_team_06_username'); var nameError = null; var nameDivClass = 'form-group'; @@ -49,7 +49,10 @@ module.exports = React.createClass({ return ( <div> <form> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <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'> @@ -57,19 +60,47 @@ module.exports = React.createClass({ <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' /> + <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> - <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button> + <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> + <a + href='#' + onClick={this.submitBack} + > + Back to previous step + </a> </div> </form> </div> ); } -}); +} + +TeamSignupUsernamePage.defaultProps = { + state: null +}; +TeamSignupUsernamePage.propTypes = { + state: React.PropTypes.object, + updateParent: React.PropTypes.func +}; diff --git a/web/react/components/team_signup_welcome_page.jsx b/web/react/components/team_signup_welcome_page.jsx index f0c680bd8..e742cba2f 100644 --- a/web/react/components/team_signup_welcome_page.jsx +++ b/web/react/components/team_signup_welcome_page.jsx @@ -1,17 +1,22 @@ // 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 Utils = require('../utils/utils.jsx'); +var Client = require('../utils/client.jsx'); var BrowserStore = require('../stores/browser_store.jsx'); -module.exports = React.createClass({ - displayName: 'TeamSignupWelcomePage', - propTypes: { - state: React.PropTypes.object, - updateParent: React.PropTypes.func - }, - submitNext: function(e) { +export default class TeamSignupWelcomePage extends React.Component { + constructor(props) { + super(props); + + this.submitNext = this.submitNext.bind(this); + this.handleDiffEmail = this.handleDiffEmail.bind(this); + this.handleDiffSubmit = this.handleDiffSubmit.bind(this); + this.handleKeyPress = this.handleKeyPress.bind(this); + + this.state = {useDiff: false}; + } + submitNext(e) { if (!BrowserStore.isLocalStorageSupported()) { this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'}); return; @@ -19,18 +24,18 @@ module.exports = React.createClass({ e.preventDefault(); this.props.state.wizard = 'team_display_name'; this.props.updateParent(this.props.state); - }, - handleDiffEmail: function(e) { + } + handleDiffEmail(e) { e.preventDefault(); this.setState({useDiff: true}); - }, - handleDiffSubmit: function(e) { + } + handleDiffSubmit(e) { e.preventDefault(); var state = {useDiff: true, serverError: ''}; - var email = this.refs.email.getDOMNode().value.trim().toLowerCase(); - if (!email || !utils.isEmail(email)) { + var email = React.findDOMNode(this.refs.email).value.trim().toLowerCase(); + if (!email || !Utils.isEmail(email)) { state.emailError = 'Please enter a valid email address'; this.setState(state); return; @@ -41,7 +46,7 @@ module.exports = React.createClass({ } state.emailError = ''; - client.signupTeam(email, + Client.signupTeam(email, function success(data) { if (data.follow_link) { window.location.href = data.follow_link; @@ -56,23 +61,20 @@ module.exports = React.createClass({ this.setState(this.state); }.bind(this) ); - }, - getInitialState: function() { - return {useDiff: false}; - }, - handleKeyPress: function(event) { + } + handleKeyPress(event) { if (event.keyCode === 13) { this.submitNext(event); } - }, - componentWillMount: function() { + } + componentWillMount() { document.addEventListener('keyup', this.handleKeyPress, false); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { document.removeEventListener('keyup', this.handleKeyPress, false); - }, - render: function() { - client.track('signup', 'signup_team_01_welcome'); + } + render() { + Client.track('signup', 'signup_team_01_welcome'); var storageError = null; if (this.state.storageError) { @@ -105,7 +107,10 @@ module.exports = React.createClass({ return ( <div> <p> - <img className='signup-team-logo' src='/static/images/logo.png' /> + <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> @@ -121,7 +126,14 @@ module.exports = React.createClass({ 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> + <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 /> @@ -129,16 +141,42 @@ module.exports = React.createClass({ <div className={emailDivClass}> <div className='row'> <div className='col-sm-9'> - <input type='email' ref='email' className='form-control' placeholder='Email Address' maxLength='128' /> + <input + type='email' + ref='email' + className='form-control' + placeholder='Email Address' + maxLength='128' + /> </div> </div> {emailError} </div> {serverError} - <button className='btn btn-md btn-primary' type='button' onClick={this.handleDiffSubmit}>Use this instead</button> + <button + className='btn btn-md btn-primary' + type='button' + onClick={this.handleDiffSubmit} + > + Use this instead + </button> </div> - <a href='#' onClick={this.handleDiffEmail} className={differentEmailLinkClass}>Use a different email</a> + <a + href='#' + onClick={this.handleDiffEmail} + className={differentEmailLinkClass} + > + Use a different email + </a> </div> ); } -}); +} + +TeamSignupWelcomePage.defaultProps = { + state: {} +}; +TeamSignupWelcomePage.propTypes = { + updateParent: React.PropTypes.func.isRequired, + state: React.PropTypes.object +}; diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx index f5a555951..7ec75e000 100644 --- a/web/react/components/user_settings_modal.jsx +++ b/web/react/components/user_settings_modal.jsx @@ -4,51 +4,75 @@ var SettingsSidebar = require('./settings_sidebar.jsx'); var UserSettings = require('./user_settings.jsx'); -module.exports = React.createClass({ - componentDidMount: function() { - $('body').on('click', '.modal-back', function(){ +export default class UserSettingsModal extends React.Component { + constructor(props) { + super(props); + + this.updateTab = this.updateTab.bind(this); + this.updateSection = this.updateSection.bind(this); + + this.state = {active_tab: 'general', active_section: ''}; + } + componentDidMount() { + $('body').on('click', '.modal-back', function changeDisplay() { $(this).closest('.modal-dialog').removeClass('display--content'); }); - $('body').on('click', '.modal-header .close', function(){ - setTimeout(function() { + $('body').on('click', '.modal-header .close', function closeModal() { + setTimeout(function finishClose() { $('.modal-dialog.display--content').removeClass('display--content'); }, 500); }); - }, - updateTab: function(tab) { - this.setState({ active_tab: tab }); - }, - updateSection: function(section) { - this.setState({ active_section: section }); - }, - getInitialState: function() { - return { active_tab: "general", active_section: "" }; - }, - render: function() { + } + updateTab(tab) { + this.setState({active_tab: tab}); + } + updateSection(section) { + this.setState({active_section: section}); + } + render() { var tabs = []; - tabs.push({name: "general", uiName: "General", icon: "glyphicon glyphicon-cog"}); - tabs.push({name: "security", uiName: "Security", icon: "glyphicon glyphicon-lock"}); - tabs.push({name: "notifications", uiName: "Notifications", icon: "glyphicon glyphicon-exclamation-sign"}); - tabs.push({name: "appearance", uiName: "Appearance", icon: "glyphicon glyphicon-wrench"}); + tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); + tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'}); + tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'}); + tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'}); return ( - <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"> - <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> - <h4 className="modal-title" ref="title">Account Settings</h4> + <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'> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + Account Settings + </h4> </div> - <div className="modal-body"> - <div className="settings-table"> - <div className="settings-links"> + <div className='modal-body'> + <div className='settings-table'> + <div className='settings-links'> <SettingsSidebar tabs={tabs} activeTab={this.state.active_tab} updateTab={this.updateTab} /> </div> - <div className="settings-content minimize-settings"> + <div className='settings-content minimize-settings'> <UserSettings activeTab={this.state.active_tab} activeSection={this.state.active_section} @@ -63,4 +87,4 @@ module.exports = React.createClass({ </div> ); } -}); +} diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx index ba0bda78e..b97f29e29 100644 --- a/web/react/components/user_settings_notifications.jsx +++ b/web/react/components/user_settings_notifications.jsx @@ -163,15 +163,15 @@ export default class NotificationsTab extends React.Component { } handleNotifyRadio(notifyLevel) { this.setState({notifyLevel: notifyLevel}); - this.refs.wrapper.getDOMNode().focus(); + React.findDOMNode(this.refs.wrapper).focus(); } handleEmailRadio(enableEmail) { this.setState({enableEmail: enableEmail}); - this.refs.wrapper.getDOMNode().focus(); + React.findDOMNode(this.refs.wrapper).focus(); } handleSoundRadio(enableSound) { this.setState({enableSound: enableSound}); - this.refs.wrapper.getDOMNode().focus(); + React.findDOMNode(this.refs.wrapper).focus(); } updateUsernameKey(val) { this.setState({usernameKey: val}); @@ -189,10 +189,10 @@ export default class NotificationsTab extends React.Component { this.setState({channelKey: val}); } updateCustomMentionKeys() { - var checked = this.refs.customcheck.getDOMNode().checked; + var checked = React.findDOMNode(this.refs.customcheck).checked; if (checked) { - var text = this.refs.custommentions.getDOMNode().value; + var text = React.findDOMNode(this.refs.custommentions).value; // remove all spaces and split string into individual keys this.setState({customKeys: text.replace(/ /g, ''), customKeysChecked: true}); @@ -201,7 +201,7 @@ export default class NotificationsTab extends React.Component { } } onCustomChange() { - this.refs.customcheck.getDOMNode().checked = true; + React.findDOMNode(this.refs.customcheck).checked = true; this.updateCustomMentionKeys(); } render() { @@ -210,8 +210,6 @@ export default class NotificationsTab extends React.Component { serverError = this.state.serverError; } - var self = this; - var user = this.props.user; var desktopSection; @@ -229,12 +227,12 @@ export default class NotificationsTab extends React.Component { let inputs = []; inputs.push( - <div> + <div key='userNotificationLevelOption'> <div className='radio'> <label> <input type='radio' checked={notifyActive[0]} - onChange={self.handleNotifyRadio.bind(this, 'all')} + onChange={this.handleNotifyRadio.bind(this, 'all')} > For all activity </input> @@ -246,7 +244,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={notifyActive[1]} - onChange={self.handleNotifyRadio.bind(this, 'mention')} + onChange={this.handleNotifyRadio.bind(this, 'mention')} > Only for mentions and private messages </input> @@ -258,7 +256,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={notifyActive[2]} - onChange={self.handleNotifyRadio.bind(this, 'none')} + onChange={this.handleNotifyRadio.bind(this, 'none')} > Never </input> @@ -268,15 +266,15 @@ export default class NotificationsTab extends React.Component { ); handleUpdateDesktopSection = function updateDesktopSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); let extraInfo = ( <div className='setting-list__hint'> These settings will override the global notification settings for the <b>{this.state.curChannel}</b> channel </div> - ) + ); desktopSection = ( <SettingItemMax @@ -299,8 +297,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateDesktopSection = function updateDesktopSection() { - self.props.updateSection('desktop'); - }; + this.props.updateSection('desktop'); + }.bind(this); desktopSection = ( <SettingItemMin @@ -324,13 +322,13 @@ export default class NotificationsTab extends React.Component { let inputs = []; inputs.push( - <div> + <div key='userNotificationSoundOptions'> <div className='radio'> <label> <input type='radio' checked={soundActive[0]} - onChange={self.handleSoundRadio.bind(this, 'true')} + onChange={this.handleSoundRadio.bind(this, 'true')} > On </input> @@ -342,7 +340,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={soundActive[1]} - onChange={self.handleSoundRadio.bind(this, 'false')} + onChange={this.handleSoundRadio.bind(this, 'false')} > Off </input> @@ -353,9 +351,9 @@ export default class NotificationsTab extends React.Component { ); handleUpdateSoundSection = function updateSoundSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); soundSection = ( <SettingItemMax @@ -377,8 +375,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateSoundSection = function updateSoundSection() { - self.props.updateSection('sound'); - }; + this.props.updateSection('sound'); + }.bind(this); soundSection = ( <SettingItemMin @@ -403,13 +401,13 @@ export default class NotificationsTab extends React.Component { let inputs = []; inputs.push( - <div> + <div key='userNotificationEmailOptions'> <div className='radio'> <label> <input type='radio' checked={emailActive[0]} - onChange={self.handleEmailRadio.bind(this, 'true')} + onChange={this.handleEmailRadio.bind(this, 'true')} > On </input> @@ -421,7 +419,7 @@ export default class NotificationsTab extends React.Component { <input type='radio' checked={emailActive[1]} - onChange={self.handleEmailRadio.bind(this, 'false')} + onChange={this.handleEmailRadio.bind(this, 'false')} > Off </input> @@ -433,9 +431,9 @@ export default class NotificationsTab extends React.Component { ); handleUpdateEmailSection = function updateEmailSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); emailSection = ( <SettingItemMax @@ -455,8 +453,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateEmailSection = function updateEmailSection() { - self.props.updateSection('email'); - }; + this.props.updateSection('email'); + }.bind(this); emailSection = ( <SettingItemMin @@ -480,10 +478,10 @@ export default class NotificationsTab extends React.Component { if (user.first_name) { handleUpdateFirstNameKey = function handleFirstNameKeyChange(e) { - self.updateFirstNameKey(e.target.checked); - }; + this.updateFirstNameKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationFirstNameOption'> <div className='checkbox'> <label> <input @@ -500,10 +498,10 @@ export default class NotificationsTab extends React.Component { } handleUpdateUsernameKey = function handleUsernameKeyChange(e) { - self.updateUsernameKey(e.target.checked); - }; + this.updateUsernameKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationUsernameOption'> <div className='checkbox'> <label> <input @@ -519,10 +517,10 @@ export default class NotificationsTab extends React.Component { ); handleUpdateMentionKey = function handleMentionKeyChange(e) { - self.updateMentionKey(e.target.checked); - }; + this.updateMentionKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationMentionOption'> <div className='checkbox'> <label> <input @@ -538,10 +536,10 @@ export default class NotificationsTab extends React.Component { ); handleUpdateAllKey = function handleAllKeyChange(e) { - self.updateAllKey(e.target.checked); - }; + this.updateAllKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationAllOption'> <div className='checkbox'> <label> <input @@ -557,10 +555,10 @@ export default class NotificationsTab extends React.Component { ); handleUpdateChannelKey = function handleChannelKeyChange(e) { - self.updateChannelKey(e.target.checked); - }; + this.updateChannelKey(e.target.checked); + }.bind(this); inputs.push( - <div> + <div key='userNotificationChannelOption'> <div className='checkbox'> <label> <input @@ -576,7 +574,7 @@ export default class NotificationsTab extends React.Component { ); inputs.push( - <div> + <div key='userNotificationCustomOption'> <div className='checkbox'> <label> <input @@ -600,9 +598,9 @@ export default class NotificationsTab extends React.Component { ); handleUpdateKeysSection = function updateKeysSection(e) { - self.props.updateSection(''); + this.props.updateSection(''); e.preventDefault(); - }; + }.bind(this); keysSection = ( <SettingItemMax title='Words that trigger mentions' @@ -645,8 +643,8 @@ export default class NotificationsTab extends React.Component { } handleUpdateKeysSection = function updateKeysSection() { - self.props.updateSection('keys'); - }; + this.props.updateSection('keys'); + }.bind(this); keysSection = ( <SettingItemMin diff --git a/web/react/dispatcher/app_dispatcher.jsx b/web/react/dispatcher/app_dispatcher.jsx index 4ae28e8eb..04e026f46 100644 --- a/web/react/dispatcher/app_dispatcher.jsx +++ b/web/react/dispatcher/app_dispatcher.jsx @@ -8,23 +8,21 @@ var Constants = require('../utils/constants.jsx'); var PayloadSources = Constants.PayloadSources; var AppDispatcher = assign(new Dispatcher(), { + handleServerAction: function performServerAction(action) { + var payload = { + source: PayloadSources.SERVER_ACTION, + action: action + }; + this.dispatch(payload); + }, - handleServerAction: function(action) { - var payload = { - source: PayloadSources.SERVER_ACTION, - action: action - }; - this.dispatch(payload); - }, - - handleViewAction: function(action) { - var payload = { - source: PayloadSources.VIEW_ACTION, - action: action - }; - this.dispatch(payload); - } - + handleViewAction: function performViewAction(action) { + var payload = { + source: PayloadSources.VIEW_ACTION, + action: action + }; + this.dispatch(payload); + } }); module.exports = AppDispatcher; |