diff options
Diffstat (limited to 'web')
26 files changed, 285 insertions, 170 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx index 462f046f6..16768a119 100644 --- a/web/react/components/access_history_modal.jsx +++ b/web/react/components/access_history_modal.jsx @@ -3,7 +3,8 @@ var UserStore = require('../stores/user_store.jsx'); var AsyncClient = require('../utils/async_client.jsx'); -var Utils = require('../utils/utils.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); +var utils = require('../utils/utils.jsx'); function getStateFromStoresForAudits() { return { @@ -14,23 +15,28 @@ function getStateFromStoresForAudits() { module.exports = React.createClass({ componentDidMount: function() { UserStore.addAuditsChangeListener(this._onChange); - AsyncClient.getAudits(); + $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) { + AsyncClient.getAudits(); + }); var self = this; $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) { - self.setState({ moreInfo: [] }); + self.setState({moreInfo: []}); }); }, componentWillUnmount: function() { UserStore.removeAuditsChangeListener(this._onChange); }, _onChange: function() { - this.setState(getStateFromStoresForAudits()); + var newState = getStateFromStoresForAudits(); + if (!utils.areStatesEqual(newState.audits, this.state.audits)) { + this.setState(newState); + } }, handleMoreInfo: function(index) { var newMoreInfo = this.state.moreInfo; newMoreInfo[index] = true; - this.setState({ moreInfo: newMoreInfo }); + this.setState({moreInfo: newMoreInfo}); }, getInitialState: function() { var initialState = getStateFromStoresForAudits(); @@ -51,24 +57,28 @@ module.exports = React.createClass({ newDate = (<div> {currentHistoryDate.toDateString()} </div>); } + if (!currentAudit.session_id && currentAudit.action.search('/users/login') !== -1) { + currentAudit.session_id = 'N/A (Login attempt)'; + } + accessList[i] = ( - <div className="access-history__table"> - <div className="access__date">{newDate}</div> - <div className="access__report"> - <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 className='access-history__table'> + <div className='access__date'>{newDate}</div> + <div className='access__report'> + <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/v1", "")}</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> + <a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a> } </div> {i < this.state.audits.length - 1 ? - <div className="divider-light"/> + <div className='divider-light'/> : null } @@ -79,17 +89,21 @@ module.exports = React.createClass({ return ( <div> - <div className="modal fade" ref="modal" id="access-history" 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">Access History</h4> + <div className='modal fade' ref='modal' id='access-history' 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'>Access History</h4> </div> - <div ref="modalBody" className="modal-body"> - <form role="form"> - { accessList } + <div ref='modalBody' className='modal-body'> + {!this.state.audits.loading ? + <form role='form'> + {accessList} </form> + : + <LoadingScreen /> + } </div> </div> </div> diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx index 90f139e8b..f28f0d5f1 100644 --- a/web/react/components/activity_log_modal.jsx +++ b/web/react/components/activity_log_modal.jsx @@ -4,6 +4,8 @@ var UserStore = require('../stores/user_store.jsx'); var Client = require('../utils/client.jsx'); var AsyncClient = require('../utils/async_client.jsx'); +var LoadingScreen = require('./loading_screen.jsx'); +var utils = require('../utils/utils.jsx'); function getStateFromStoresForSessions() { return { @@ -29,7 +31,9 @@ module.exports = React.createClass({ }, componentDidMount: function() { UserStore.addSessionsChangeListener(this._onChange); - AsyncClient.getSessions(); + $(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) { @@ -40,7 +44,10 @@ module.exports = React.createClass({ UserStore.removeSessionsChangeListener(this._onChange); }, _onChange: function() { - this.setState(getStateFromStoresForSessions()); + var newState = getStateFromStoresForSessions(); + if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) { + this.setState(newState); + } }, handleMoreInfo: function(index) { var newMoreInfo = this.state.moreInfo; @@ -106,10 +113,16 @@ module.exports = React.createClass({ </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 /> + } </div> </div> </div> diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 7a129f200..76dbe370b 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -153,7 +153,7 @@ module.exports = React.createClass({ if (isDirect) { if (this.state.users.length > 1) { var contact = this.state.users[((this.state.users[0].id === currentId) ? 1 : 0)]; - channelTitle = <UserProfile userId={contact.id} overwriteName={contact.nickname || contact.username} />; + channelTitle = contact.nickname || contact.username; } } @@ -161,13 +161,13 @@ module.exports = React.createClass({ <table className="channel-header alt"> <tr> <th> - { !isDirect ? <div className="channel-header__info"> <div className="dropdown"> <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true"> <strong className="heading">{channelTitle} </strong> <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span> </a> + { !isDirect ? <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={channel.id} href="#">View Info</a></li> { !ChannelStore.isDefault(channel) ? @@ -193,12 +193,14 @@ module.exports = React.createClass({ : null } </ul> + : + <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown"> + <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li> + </ul> + } </div> <div data-toggle="popover" data-content={popoverContent} className="description">{description}</div> </div> - : - <a href="#"><strong className="heading">{channelTitle}</strong></a> - } </th> <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th> <th className="search-bar__container"><NavbarSearchBox /></th> diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index e23a37740..64ceec450 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -47,7 +47,7 @@ module.exports = React.createClass({ </p> </div> <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <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> diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx index a35a531b5..1b0cc185f 100644 --- a/web/react/components/edit_channel_modal.jsx +++ b/web/react/components/edit_channel_modal.jsx @@ -63,7 +63,7 @@ module.exports = React.createClass({ { server_error } </div> <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Save</button> </div> </div> diff --git a/web/react/components/email_verify.jsx b/web/react/components/email_verify.jsx index 168608274..678eb9928 100644 --- a/web/react/components/email_verify.jsx +++ b/web/react/components/email_verify.jsx @@ -11,7 +11,7 @@ module.exports = React.createClass({ var resend = ""; if (this.props.isVerified === "true") { title = config.SiteName + " Email Verified"; - body = <p>Your email has been verified! Click <a href="/">here</a> to log in.</p>; + 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"; body = <p>Please verify your email address. Check your inbox for an email.</p>; diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 94be2acd6..fed96b50a 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -212,7 +212,7 @@ module.exports = React.createClass({ <span>People invited automatically join Town Square channel.</span> </div> <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <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> </div> </div> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 8dc5013ca..46f77660d 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -309,12 +309,15 @@ module.exports = React.createClass({ var more_messages = <p className="beginning-messages-text">Beginning of Channel</p>; + var userStyle = { color: UserStore.getCurrentUser().props.theme } + if (channel != null) { if (order.length > 0 && order.length % Constants.POST_CHUNK_SIZE === 0) { more_messages = <a ref="loadmore" className="more-messages-text theme" href="#" onClick={this.getMorePosts}>Load more messages</a>; } else if (channel.type === 'D') { var teammate = utils.getDirectTeammate(channel.id) + if (teammate) { var teammate_name = teammate.nickname.length > 0 ? teammate.nickname : teammate.username; more_messages = ( @@ -329,6 +332,7 @@ module.exports = React.createClass({ {"This is the start of your private message history with " + teammate_name + "." }<br/> {"Private messages and files shared here are not shown to people outside this area."} </p> + <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a> </div> ); } else { @@ -342,7 +346,6 @@ module.exports = React.createClass({ var ui_name = channel.display_name var members = ChannelStore.getCurrentExtraInfo().members; var creator_name = ""; - var userStyle = { color: UserStore.getCurrentUser().props.theme } for (var i = 0; i < members.length; i++) { if (members[i].roles.indexOf('admin') > -1) { @@ -374,7 +377,7 @@ module.exports = React.createClass({ <div className="channel-intro"> <h4 className="channel-intro__title">Beginning of {ui_name}</h4> <p className="channel-intro__content"> - {"This is the start of " + ui_name + ", a channel for conversations you’d prefer out of more focused channels."} + {"This is the start of " + ui_name + ", a channel for non-work-related conversations."} <br/> </p> <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={ui_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a> diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx index 9e4a25f85..26593b7fa 100644 --- a/web/react/components/rename_channel_modal.jsx +++ b/web/react/components/rename_channel_modal.jsx @@ -96,7 +96,7 @@ module.exports = React.createClass({ var self = this; $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { var button = $(e.relatedTarget); - self.setState({ display_name: button.attr('data-display'), title: button.attr('data-name'), channel_id: button.attr('data-channelid') }); + self.setState({ display_name: button.attr('data-display'), channel_name: button.attr('data-name'), channel_id: button.attr('data-channelid') }); }); $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', this.handleClose); }, @@ -139,7 +139,7 @@ module.exports = React.createClass({ </form> </div> <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> <button onClick={this.handleSubmit} type="button" className="btn btn-primary">Save</button> </div> </div> diff --git a/web/react/components/rename_team_modal.jsx b/web/react/components/rename_team_modal.jsx index dfd775a3b..bebdd6662 100644 --- a/web/react/components/rename_team_modal.jsx +++ b/web/react/components/rename_team_modal.jsx @@ -83,7 +83,7 @@ module.exports = React.createClass({ </form> </div> <div className="modal-footer"> - <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> + <button type="button" className="btn btn-default" data-dismiss="modal">Cancel</button> <button onClick={this.handleSubmit} type="button" className="btn btn-primary">Save</button> </div> </div> diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx index f21f0cd58..e39cf5d46 100644 --- a/web/react/components/search_bar.jsx +++ b/web/react/components/search_bar.jsx @@ -36,6 +36,9 @@ module.exports = React.createClass({ } } }, + clearFocus: function(e) { + $('.search-bar__container').removeClass('focused'); + }, handleClose: function(e) { e.preventDefault(); @@ -57,6 +60,7 @@ module.exports = React.createClass({ }, handleUserFocus: function(e) { e.target.select(); + $('.search-bar__container').addClass('focused'); }, performSearch: function(terms, isMentionSearch) { if (terms.length) { @@ -92,13 +96,14 @@ module.exports = React.createClass({ render: function() { return ( <div> - <div className="sidebar__collapse" onClick={this.handleClose}>Cancel</div> - <span className="glyphicon glyphicon-search sidebar__search-icon"></span> + <div className="sidebar__collapse" onClick={this.handleClose}><span className="fa fa-angle-left"></span></div> + <span onClick={this.clearFocus} className="search__clear">Cancel</span> <form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}> + <span className="glyphicon glyphicon-search sidebar__search-icon"></span> <input type="text" ref="search" - className="form-control search-bar-box" + className="form-control search-bar" placeholder="Search" value={this.state.search_term} onFocus={this.handleUserFocus} diff --git a/web/react/components/setting_picture.jsx b/web/react/components/setting_picture.jsx index 6cfb74d60..fa4c8bb62 100644 --- a/web/react/components/setting_picture.jsx +++ b/web/react/components/setting_picture.jsx @@ -7,8 +7,8 @@ module.exports = React.createClass({ var reader = new FileReader(); var img = this.refs.image.getDOMNode(); - reader.onload = function (e) { - $(img).attr('src', e.target.result) + reader.onload = function(e) { + $(img).attr('src', e.target.result); }; reader.readAsDataURL(file); @@ -25,27 +25,27 @@ module.exports = React.createClass({ var img = null; if (this.props.picture) { - img = (<img ref="image" className="profile-img" src=""/>); + img = (<img ref='image' className='profile-img' src=''/>); } else { - img = (<img ref="image" className="profile-img" src={this.props.src}/>); + img = (<img ref='image' className='profile-img' src={this.props.src}/>); } var self = this; return ( - <ul className="section-max"> - <li className="col-xs-12 section-title">{this.props.title}</li> - <li className="col-xs-offset-3 col-xs-8"> - <ul className="setting-list"> - <li className="setting-list-item"> + <ul className='section-max'> + <li className='col-xs-12 section-title'>{this.props.title}</li> + <li className='col-xs-offset-3 col-xs-8'> + <ul className='setting-list'> + <li className='setting-list-item'> {img} </li> - <li className="setting-list-item"> - { server_error } - { client_error } - <span className="btn btn-sm btn-primary btn-file sel-btn">Upload<input ref="input" accept=".jpg,.png,.bmp" type="file" onChange={this.props.pictureChange}/></span> - <a className={this.props.submitActive ? "btn btn-sm btn-primary" : "btn btn-sm btn-inactive disabled"} onClick={this.props.submit}>Save</a> - <a className="btn btn-sm theme" href="#" onClick={self.props.updateSection}>Cancel</a> + <li className='setting-list-item'> + {server_error} + {client_error} + <span className='btn btn-sm btn-primary btn-file sel-btn'>Select<input ref='input' accept='.jpg,.png,.bmp' type='file' onChange={this.props.pictureChange}/></span> + <a className={this.props.submitActive ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-inactive disabled'} onClick={this.props.submit}>Save</a> + <a className='btn btn-sm theme' href='#' onClick={self.props.updateSection}>Cancel</a> </li> </ul> </li> diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index e7512934a..e01ddcd05 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -17,11 +17,20 @@ var NavbarDropdown = React.createClass({ e.preventDefault(); client.logout(); }, + blockToggle: false, componentDidMount: function() { UserStore.addTeamsChangeListener(this._onChange); + + var self = this; + $(this.refs.dropdown.getDOMNode()).on('hide.bs.dropdown', function(e) { + self.blockToggle = true; + setTimeout(function(){self.blockToggle = false;}, 100); + }); }, componentWillUnmount: function() { UserStore.removeTeamsChangeListener(this._onChange); + + $(this.refs.dropdown.getDOMNode()).off('hide.bs.dropdown'); }, _onChange: function() { if (this.isMounted()) { @@ -75,7 +84,7 @@ var NavbarDropdown = React.createClass({ return ( <ul className="nav navbar-nav navbar-right"> - <li className="dropdown"> + <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> @@ -107,6 +116,14 @@ module.exports = React.createClass({ }; }, + toggleDropdown: function(e) { + if (this.refs.dropdown.blockToggle) { + this.refs.dropdown.blockToggle = false; + return; + } + $('.team__header').find('.dropdown-toggle').dropdown('toggle'); + }, + render: function() { var me = UserStore.getCurrentUser(); @@ -116,7 +133,7 @@ module.exports = React.createClass({ return ( <div className="team__header theme"> - <a className="settings_link" href="#" data-toggle="modal" data-target="#user_settings1"> + <a href="#" onClick={this.toggleDropdown}> { me.last_picture_update ? <img className="user__picture" src={"/api/v1/users/" + me.id + "/image?time=" + me.update_at} /> : @@ -127,7 +144,7 @@ module.exports = React.createClass({ <div className="team__name">{ this.props.teamDisplayName }</div> </div> </a> - <NavbarDropdown teamType={this.props.teamType} /> + <NavbarDropdown ref="dropdown" teamType={this.props.teamType} /> </div> ); } diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx index 21f9edef1..83daa3b1f 100644 --- a/web/react/components/signup_team_complete.jsx +++ b/web/react/components/signup_team_complete.jsx @@ -87,7 +87,7 @@ WelcomePage = React.createClass({ <h3 className="sub-heading">Welcome to:</h3> <h1 className="margin--top-none">{config.SiteName}</h1> </p> - <p className="margin--less">Let's setup 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"> @@ -271,7 +271,7 @@ TeamURLPage = React.createClass({ <p>{"Choose the web address of your new " + strings.Team + ":"}</p> <ul className="color--light"> <li>Short and memorable is best</li> - <li>Use lower case letters, numbers and dashes</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> @@ -587,25 +587,23 @@ PasswordPage = React.createClass({ var props = this.props; - setTimeout(function() { - $('#sign-up-button').button('reset'); - props.state.wizard = "finished"; - props.updateParent(props.state, true); - - window.location.href = utils.getWindowLocationOrigin() + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email); - - // client.loginByEmail(teamSignup.team.domain, teamSignup.team.email, teamSignup.user.password, - // function(data) { - // TeamStore.setLastName(teamSignup.team.domain); - // UserStore.setLastEmail(teamSignup.team.email); - // UserStore.setCurrentUser(data); - // window.location.href = '/channels/town-square'; - // }.bind(ctl), - // function(err) { - // this.setState({name_error: err.message}); - // }.bind(ctl) - // ); - }, 5000); + $('#sign-up-button').button('reset'); + props.state.wizard = "finished"; + props.updateParent(props.state, true); + + window.location.href = utils.getWindowLocationOrigin() + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email); + + // client.loginByEmail(teamSignup.team.domain, teamSignup.team.email, teamSignup.user.password, + // function(data) { + // TeamStore.setLastName(teamSignup.team.domain); + // UserStore.setLastEmail(teamSignup.team.email); + // UserStore.setCurrentUser(data); + // window.location.href = '/channels/town-square'; + // }.bind(ctl), + // function(err) { + // this.setState({name_error: err.message}); + // }.bind(ctl) + // ); }.bind(this), function(err) { this.setState({server_error: err.message}); @@ -620,8 +618,8 @@ PasswordPage = React.createClass({ client.track('signup', 'signup_team_07_password'); - var password_error = this.state.password_error ? <label className="control-label">{ this.state.password_error }</label> : null; - var server_error = this.state.server_error ? <label className="control-label">{ this.state.server_error }</label> : null; + 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; return ( <div> diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 670aab943..03808e821 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -58,7 +58,7 @@ module.exports = React.createClass({ }.bind(this), function(err) { if (err.message == "Login failed because email address has not been verified") { - window.location.href = "/verify_email?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.teamName); + window.location.href = "/verify_email?email="+ encodeURIComponent(this.state.user.email) + "&teamname=" + encodeURIComponent(this.props.teamName); } else { this.state.server_error = err.message; this.setState(this.state); @@ -107,7 +107,7 @@ module.exports = React.createClass({ <div className={ this.state.original_email == "" ? "margin--extra" : "hidden"} > <h5><strong>What's your email address?</strong></h5> <div className={ email_error ? "form-group has-error" : "form-group" }> - <input type="email" ref="email" className="form-control" defaultValue={ this.state.user.email } placeholder="" maxLength="128" /> + <input type="email" ref="email" className="form-control" defaultValue={ this.state.user.email } placeholder="" maxLength="128" autoFocus={true} /> { email_error } </div> </div> @@ -123,6 +123,7 @@ module.exports = React.createClass({ return ( <div> + <form> <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> @@ -148,9 +149,10 @@ module.exports = React.createClass({ </div> </div> </div> - <p className="margin--extra"><button 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> { server_error } <p>By creating an account and using Mattermost you are agreeing to our <a href={ config.TermsLink }>Terms of Service</a>. If you do not agree, you cannot use this service.</p> + </form> </div> ); } diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index e1ae6da52..902989b7b 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -465,17 +465,17 @@ var SecurityTab = React.createClass({ var confirmPassword = this.state.confirm_password; if (currentPassword === '') { - this.setState({ password_error: "Please enter your current password" }); + this.setState({password_error: 'Please enter your current password', server_error: ''}); return; } if (newPassword.length < 5) { - this.setState({ password_error: "New passwords must be at least 5 characters" }); + this.setState({password_error: 'New passwords must be at least 5 characters', server_error: ''}); return; } - if (newPassword != confirmPassword) { - this.setState({ password_error: "The new passwords you entered do not match" }); + if (newPassword !== confirmPassword) { + this.setState({password_error: 'The new passwords you entered do not match', server_error: ''}); return; } @@ -488,11 +488,16 @@ var SecurityTab = React.createClass({ function(data) { this.props.updateSection(""); AsyncClient.getMe(); - this.setState({ current_password: '', new_password: '', confirm_password: '' }); + this.setState({current_password: '', new_password: '', confirm_password: ''}); }.bind(this), function(err) { - state = this.getInitialState(); - state.server_error = err; + var state = this.getInitialState(); + if (err.message) { + state.server_error = err.message; + } else { + state.server_error = err; + } + state.password_error = ''; this.setState(state); }.bind(this) ); @@ -709,7 +714,11 @@ var GeneralTab = React.createClass({ }.bind(this), function(err) { state = this.getInitialState(); - state.server_error = err; + if(err.message) { + state.server_error = err.message; + } else { + state.server_error = err + } this.setState(state); }.bind(this) ); diff --git a/web/react/pages/verify.jsx b/web/react/pages/verify.jsx index 69850849f..96b556983 100644 --- a/web/react/pages/verify.jsx +++ b/web/react/pages/verify.jsx @@ -3,11 +3,9 @@ var EmailVerify = require('../components/email_verify.jsx'); -global.window.setup_verify_page = function(is_verified) { - +global.window.setupVerifyPage = function setupVerifyPage(isVerified, teamURL, userEmail) { React.render( - <EmailVerify isVerified={is_verified} />, + <EmailVerify isVerified={isVerified} teamURL={teamURL} userEmail={userEmail} />, document.getElementById('verify') ); - }; diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx index 001162f47..aff5a0bed 100644 --- a/web/react/stores/user_store.jsx +++ b/web/react/stores/user_store.jsx @@ -164,13 +164,13 @@ var UserStore = assign({}, EventEmitter.prototype, { BrowserStore.setItem("sessions", sessions); }, getSessions: function() { - return BrowserStore.getItem("sessions", []); + return BrowserStore.getItem("sessions", {loading: true}); }, setAudits: function(audits) { BrowserStore.setItem("audits", audits); }, getAudits: function() { - return BrowserStore.getItem("audits", []); + return BrowserStore.getItem("audits", {loading: true}); }, setTeams: function(teams) { BrowserStore.setItem("teams", teams); diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 09240bf06..e51f7f3f4 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -416,7 +416,7 @@ module.exports.textToJsx = function(text, options) { var lines = text.split("\n"); for (var i = 0; i < lines.length; i++) { var line = lines[i]; - var words = line.split(" "); + var words = line.split(/(?=\W)/); var highlightSearchClass = ""; for (var z = 0; z < words.length; z++) { var word = words[z]; @@ -444,7 +444,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>); + inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix}</span>); } else if (testUrlMatch(word).length) { var match = testUrlMatch(word)[0]; var link = match.link; @@ -452,7 +452,7 @@ module.exports.textToJsx = function(text, options) { var prefix = word.substring(0,word.indexOf(match.text)); var suffix = word.substring(word.indexOf(match.text)+match.text.length); - inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>); + inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix}</span>); } else if (trimWord.match(hashRegex)) { var suffix = word.match(puncEndRegex); @@ -463,7 +463,7 @@ module.exports.textToJsx = function(text, options) { highlightSearchClass = " search-highlight"; } - inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>); + inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix}</span>); } else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) { var suffix = word.match(puncEndRegex); @@ -473,15 +473,15 @@ module.exports.textToJsx = function(text, options) { if (searchTerm === trimWord.substring(1).toLowerCase()) { highlightSearchClass = " search-highlight"; } - inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix} </span>); + inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix}</span>); } else { - inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>); + inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix}</span>); } } else if (word === "") { // if word is empty dont include a span } else { - inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>); + inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span></span>); } highlightSearchClass = ""; } diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss index 687e330a6..fb37c43eb 100644 --- a/web/sass-files/sass/partials/_headers.scss +++ b/web/sass-files/sass/partials/_headers.scss @@ -84,8 +84,32 @@ // Team Header in Sidebar .sidebar--left, .sidebar--menu { .team__header { + position: relative; padding: 10px; @include legacy-pie-clearfix; + &:before { + @include single-transition(all, 0.05s, linear); + content: ""; + background: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + position: absolute; + } + &:hover { + &:before { + background: rgba(black, 0.1); + } + .user__name { + color: #fff; + } + .navbar-right { + .dropdown-toggle { + @include opacity(1); + } + } + } a { color: #fff; } @@ -94,12 +118,11 @@ position: absolute; top: 10px; right: 22px; + z-index: 5; .dropdown-toggle { + @include single-transition(all, 0.1s, linear); padding: 10px; @include opacity(0.8); - &:hover { - @include opacity(1); - } } .dropdown-menu { li a { @@ -124,6 +147,8 @@ .header__info { color: #fff; padding-left: 4px; + z-index: 1; + position: relative; } .team__name, .user__name { display: block; @@ -137,9 +162,11 @@ text-decoration: none; } .user__name { + @include single-transition(all, 0.1s, linear); font-size: 14px; font-weight: 400; color: #eee; + color: rgba(#fff, 0.8); } > .nav { > li { @@ -250,10 +277,10 @@ vertical-align: top; display: inline-block; width: 15px; - margin: 9px 4px 3px 0; + margin: 9px 6px 3px 0; &:hover { svg { - fill: #888; + fill: #777; } } a { @@ -263,6 +290,6 @@ svg { vertical-align: top; margin-top: 8px; - fill: #AAA; + fill: #aaa; } } diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss index e5e67a9e0..905907d84 100644 --- a/web/sass-files/sass/partials/_navbar.scss +++ b/web/sass-files/sass/partials/_navbar.scss @@ -5,15 +5,15 @@ .navbar-default { position: absolute; border: none; - min-height: 50px; + min-height: 45px; background: $primary-color; .navbar-nav { > li { > a { - height: 50px; + height: 45px; padding: 0 1.3em; i { - line-height: 50px; + line-height: 45px; } } } @@ -24,8 +24,8 @@ border-radius: 0; margin: 0; padding: 0 10px; - line-height: 53px; - height: 50px; + line-height: 48px; + height: 44px; z-index: 5; fill: #fff; .icon-bar { @@ -38,8 +38,8 @@ } .navbar-brand { padding: 0 0.5em; - height: 50px; - line-height: 50px; + height: 45px; + line-height: 45px; float: none; font-size: 16px; .heading { diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 719934638..e3f140413 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -427,9 +427,9 @@ body { &.white { .inner__wrap { - >.row.content { - margin-bottom: -185px; - } + >.row.content { + margin-bottom: -185px; + } } } } @@ -447,29 +447,45 @@ } } } + .search__clear { + display: block; + } .search-bar__container { - padding: 10px 8px 13px; + padding: 0; + height: 45px; background: $primary-color; color: #fff; + &.focused { + .sidebar__collapse { + @include translateX(-45px); + } + .search__form { + @include translateX(-45px); + padding-left: 55px; + padding-right: 24px; + } + .search__clear { + @include translateX(0px); + } + } .search__form { border: none; - padding: 0 60px 0 35px; + @include translateX(0px); + padding: 7px 20px 0 49px; + height: 45px; + position: relative; + @include single-transition(all, 0.2s, linear); .form-control { - line-height: normal; - background: none; + border: none; + padding: 0 10px 0 31px; + background: rgba(black, 0.2); + @include border-radius(3px); color: #fff; - border-radius: 0; - padding: 0; - border-bottom: 1px solid #FFF; - border-bottom: 1px solid rgba(#fff, 0.4); - &:focus { - border-bottom: 1px solid rgba(#fff, 0.8); - } } input[type=text] { - @include input-placeholder { + @include input-placeholder { color: #fff; - color: rgba(#fff, 0.6); + color: rgba(#fff, 0.5); } } } @@ -550,11 +566,6 @@ .sidebar--right__close { display: none; } - .search__form { - .glyphicon { - color: #fff; - } - } } .inner__wrap { &.move--right { @@ -650,7 +661,7 @@ display: block; } .access__report { - margin: 0 0 15px 15px; + margin: 0 0 15px 15px; } .access__date { div { diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss index 794358320..9ae41ebb0 100644 --- a/web/sass-files/sass/partials/_search.scss +++ b/web/sass-files/sass/partials/_search.scss @@ -1,23 +1,39 @@ .search-bar__container { padding: 8px 8px 8px 0; } -.sidebar__collapse { - width: auto; - height: auto; +.search__clear { + display: none; position: absolute; - top: 1px; - right: 15px; + right: 0; + line-height: 45px; + margin-right: 13px; + @include single-transition(all, 0.2s, linear); + @include translateX(60px); + z-index: 5; + cursor: pointer; +} +.sidebar__collapse { cursor: pointer; - padding: 1em 0; z-index: 5; + fill: #FFF; + position: absolute; + left: 0; + font-size: 35px; + width: 49px; + @include single-transition(all, 0.2s, linear); + @include translateX(0px); + text-align: center; + padding-left: 1px; + line-height: 40px; display: none; } .sidebar__search-icon { position: absolute; - left: 15px; - top: 18px; - font-size: 16px; - @include opacity(0.8); + top: 15px; + margin-left: 10px; + font-size: 14px; + color: #fff; + color: rgba(#fff, 0.5); display: none; } .search__form { @@ -30,9 +46,8 @@ .sidebar--right & { width: 100%; } - .search-bar-box { + .search-bar { height: 40px; - border: 1px solid #ddd; padding-right: 30px; box-shadow: none; .search-bar__container & { diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss index 1a0c55f39..4b6ee79a1 100644 --- a/web/sass-files/sass/partials/_signup.scss +++ b/web/sass-files/sass/partials/_signup.scss @@ -74,7 +74,7 @@ .form__hint { font-size: 0.95em; color: #999; - margin: 10px 0; + margin: 10px 0 0; } .signup-team-confirm__container { diff --git a/web/templates/verify.html b/web/templates/verify.html index a61964bb3..de839db68 100644 --- a/web/templates/verify.html +++ b/web/templates/verify.html @@ -9,7 +9,7 @@ </div> </div> <script> - window.setup_verify_page('{{ .Props.IsVerified }}'); + window.setupVerifyPage('{{.Props.IsVerified}}', '{{.Props.TeamURL}}', '{{.Props.UserEmail}}'); </script> </body> </html> diff --git a/web/web.go b/web/web.go index 68e2a5226..8b329c149 100644 --- a/web/web.go +++ b/web/web.go @@ -352,27 +352,26 @@ func getChannel(c *api.Context, w http.ResponseWriter, r *http.Request) { func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { resend := r.URL.Query().Get("resend") - name := r.URL.Query().Get("name") + name := r.URL.Query().Get("teamname") email := r.URL.Query().Get("email") hashedId := r.URL.Query().Get("hid") userId := r.URL.Query().Get("uid") - if resend == "true" { - - teamId := "" - if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil { - c.Err = result.Err - return - } else { - teamId = result.Data.(*model.Team).Id - } + var team *model.Team + if result := <-api.Srv.Store.Team().GetByName(name); result.Err != nil { + c.Err = result.Err + return + } else { + team = result.Data.(*model.Team) + } - if result := <-api.Srv.Store.User().GetByEmail(teamId, email); result.Err != nil { + if resend == "true" { + if result := <-api.Srv.Store.User().GetByEmail(team.Id, email); result.Err != nil { c.Err = result.Err return } else { user := result.Data.(*model.User) - api.FireAndForgetVerifyEmail(user.Id, strings.Split(user.Nickname, " ")[0], user.Email, name, c.GetTeamURL()) + api.FireAndForgetVerifyEmail(user.Id, user.Email, team.Name, team.DisplayName, c.GetSiteURL(), c.GetTeamURLFromTeam(team)) http.Redirect(w, r, "/", http.StatusFound) return } @@ -396,6 +395,8 @@ func verifyEmail(c *api.Context, w http.ResponseWriter, r *http.Request) { page := NewHtmlTemplatePage("verify", "Email Verified") page.Props["IsVerified"] = isVerified + page.Props["TeamURL"] = c.GetTeamURLFromTeam(team) + page.Props["UserEmail"] = email page.Render(c, w) } |