diff options
Diffstat (limited to 'web/react/components')
16 files changed, 420 insertions, 68 deletions
diff --git a/web/react/components/admin_console/admin_controller.jsx b/web/react/components/admin_console/admin_controller.jsx index 4f144b0dd..e587c4f84 100644 --- a/web/react/components/admin_console/admin_controller.jsx +++ b/web/react/components/admin_console/admin_controller.jsx @@ -18,6 +18,7 @@ import GitLabSettingsTab from './gitlab_settings.jsx'; import SqlSettingsTab from './sql_settings.jsx'; import TeamSettingsTab from './team_settings.jsx'; import ServiceSettingsTab from './service_settings.jsx'; +import LegalAndSupportSettingsTab from './legal_and_support_settings.jsx'; import TeamUsersTab from './team_users.jsx'; import TeamAnalyticsTab from './team_analytics.jsx'; @@ -148,6 +149,8 @@ export default class AdminController extends React.Component { tab = <TeamSettingsTab config={this.state.config} />; } else if (this.state.selected === 'service_settings') { tab = <ServiceSettingsTab config={this.state.config} />; + } else if (this.state.selected === 'legal_and_support_settings') { + tab = <LegalAndSupportSettingsTab config={this.state.config} />; } else if (this.state.selected === 'team_users') { if (this.state.teams) { tab = <TeamUsersTab team={this.state.teams[this.state.selectedTeam]} />; diff --git a/web/react/components/admin_console/admin_sidebar.jsx b/web/react/components/admin_console/admin_sidebar.jsx index cc98c495e..da445da37 100644 --- a/web/react/components/admin_console/admin_sidebar.jsx +++ b/web/react/components/admin_console/admin_sidebar.jsx @@ -108,7 +108,7 @@ export default class AdminSidebar extends React.Component { <a href='#' onClick={this.handleClick.bind(this, 'team_users', team.id)} - className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id)} + className={'nav__sub-menu-item ' + this.isSelected('team_users', team.id) + ' ' + this.isSelected('team_analytics', team.id)} > {team.name} <OverlayTrigger @@ -252,6 +252,15 @@ export default class AdminSidebar extends React.Component { {'GitLab Settings'} </a> </li> + <li> + <a + href='#' + className={this.isSelected('legal_and_support_settings')} + onClick={this.handleClick.bind(this, 'legal_and_support_settings', null)} + > + {'Legal and Support Settings'} + </a> + </li> </ul> <ul className='nav nav__sub-menu'> <li> diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx index 238ace3da..42e3507d6 100644 --- a/web/react/components/admin_console/email_settings.jsx +++ b/web/react/components/admin_console/email_settings.jsx @@ -586,7 +586,7 @@ export default class EmailSettings extends React.Component { onChange={this.handleChange} disabled={!this.state.sendPushNotifications} /> - <p className='help-text'>{'Location of the push notification server.'}</p> + <p className='help-text'>{'Location of Mattermost push notification service you can set up behind your firewall using https://github.com/mattermost/push-proxy. For testing you can use https://push.mattermost.com, which connects to the sample Mattermost iOS app in the public Apple AppStore. Please do not use test service for production deployments.'}</p> </div> </div> diff --git a/web/react/components/admin_console/legal_and_support_settings.jsx b/web/react/components/admin_console/legal_and_support_settings.jsx new file mode 100644 index 000000000..b00e4b6bd --- /dev/null +++ b/web/react/components/admin_console/legal_and_support_settings.jsx @@ -0,0 +1,222 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import * as Client from '../../utils/client.jsx'; +import * as AsyncClient from '../../utils/async_client.jsx'; + +export default class LegalAndSupportSettings extends React.Component { + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + + this.state = { + saveNeeded: false, + serverError: null + }; + } + + handleChange() { + var s = {saveNeeded: true, serverError: this.state.serverError}; + this.setState(s); + } + + handleSubmit(e) { + e.preventDefault(); + $('#save-button').button('loading'); + + var config = this.props.config; + + config.SupportSettings.TermsOfServiceLink = ReactDOM.findDOMNode(this.refs.TermsOfServiceLink).value.trim(); + config.SupportSettings.PrivacyPolicyLink = ReactDOM.findDOMNode(this.refs.PrivacyPolicyLink).value.trim(); + config.SupportSettings.AboutLink = ReactDOM.findDOMNode(this.refs.AboutLink).value.trim(); + config.SupportSettings.HelpLink = ReactDOM.findDOMNode(this.refs.HelpLink).value.trim(); + config.SupportSettings.ReportAProblemLink = ReactDOM.findDOMNode(this.refs.ReportAProblemLink).value.trim(); + config.SupportSettings.SupportEmail = ReactDOM.findDOMNode(this.refs.SupportEmail).value.trim(); + + Client.saveConfig( + config, + () => { + AsyncClient.getConfig(); + this.setState({ + serverError: null, + saveNeeded: false + }); + $('#save-button').button('reset'); + }, + (err) => { + this.setState({ + serverError: err.message, + saveNeeded: true + }); + $('#save-button').button('reset'); + } + ); + } + + render() { + var serverError = ''; + if (this.state.serverError) { + serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; + } + + var saveClass = 'btn'; + if (this.state.saveNeeded) { + saveClass = 'btn btn-primary'; + } + + return ( + <div className='wrapper--fixed'> + + <h3>{'Legal and Support Settings'}</h3> + <form + className='form-horizontal' + role='form' + > + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='TermsOfServiceLink' + > + {'Terms of Service link:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='TermsOfServiceLink' + ref='TermsOfServiceLink' + defaultValue={this.props.config.SupportSettings.TermsOfServiceLink} + onChange={this.handleChange} + /> + <p className='help-text'>{'Link to Terms of Service available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='PrivacyPolicyLink' + > + {'Privacy Policy link:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='PrivacyPolicyLink' + ref='PrivacyPolicyLink' + defaultValue={this.props.config.SupportSettings.PrivacyPolicyLink} + onChange={this.handleChange} + /> + <p className='help-text'>{'Link to Privacy Policy available to users on desktop and on mobile. Leaving this blank will hide the option to display a notice.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='AboutLink' + > + {'About link:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='AboutLink' + ref='AboutLink' + defaultValue={this.props.config.SupportSettings.AboutLink} + onChange={this.handleChange} + /> + <p className='help-text'>{'Link to About page for more information on your Mattermost deployment, for example its purpose and audience within your organization. Defaults to Mattermost information page.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='HelpLink' + > + {'Help link:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='HelpLink' + ref='HelpLink' + defaultValue={this.props.config.SupportSettings.HelpLink} + onChange={this.handleChange} + /> + <p className='help-text'>{'Link to help documentation from team site main menu. Typically not changed unless your organization chooses to create custom documentation.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='ReportAProblemLink' + > + {'Report a Problem link:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='ReportAProblemLink' + ref='ReportAProblemLink' + defaultValue={this.props.config.SupportSettings.ReportAProblemLink} + onChange={this.handleChange} + /> + <p className='help-text'>{'Link to help documentation from team site main menu. By default this points to the peer-to-peer troubleshooting forum where users can search for, find and request help with technical issues.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='SupportEmail' + > + {'Support email:'} + </label> + <div className='col-sm-8'> + <input + type='text' + className='form-control' + id='SupportEmail' + ref='SupportEmail' + defaultValue={this.props.config.SupportSettings.SupportEmail} + onChange={this.handleChange} + /> + <p className='help-text'>{'Email shown during tutorial for end users to ask support questions.'}</p> + </div> + </div> + + <div className='form-group'> + <div className='col-sm-12'> + {serverError} + <button + disabled={!this.state.saveNeeded} + type='submit' + className={saveClass} + onClick={this.handleSubmit} + id='save-button' + data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Saving Config...'} + > + {'Save'} + </button> + </div> + </div> + + </form> + </div> + ); + } +} + +LegalAndSupportSettings.propTypes = { + config: React.PropTypes.object +}; diff --git a/web/react/components/admin_console/privacy_settings.jsx b/web/react/components/admin_console/privacy_settings.jsx index 61393f1c6..78747d9f2 100644 --- a/web/react/components/admin_console/privacy_settings.jsx +++ b/web/react/components/admin_console/privacy_settings.jsx @@ -132,7 +132,7 @@ export default class PrivacySettings extends React.Component { /> {'false'} </label> - <p className='help-text'>{'When false, hides full name of users from other users including team owner and team administrators.'}</p> + <p className='help-text'>{'When false, hides full name of users from other users, including team owners and team administrators. Username is shown in place of full name.'}</p> </div> </div> diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 908eb709a..d7582d682 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -36,10 +36,11 @@ export default class ServiceSettings extends React.Component { config.ServiceSettings.SegmentDeveloperKey = ReactDOM.findDOMNode(this.refs.SegmentDeveloperKey).value.trim(); config.ServiceSettings.GoogleDeveloperKey = ReactDOM.findDOMNode(this.refs.GoogleDeveloperKey).value.trim(); config.ServiceSettings.EnableIncomingWebhooks = ReactDOM.findDOMNode(this.refs.EnableIncomingWebhooks).checked; - config.ServiceSettings.EnableOutgoingWebhooks = React.findDOMNode(this.refs.EnableOutgoingWebhooks).checked; + config.ServiceSettings.EnableOutgoingWebhooks = ReactDOM.findDOMNode(this.refs.EnableOutgoingWebhooks).checked; config.ServiceSettings.EnablePostUsernameOverride = ReactDOM.findDOMNode(this.refs.EnablePostUsernameOverride).checked; config.ServiceSettings.EnablePostIconOverride = ReactDOM.findDOMNode(this.refs.EnablePostIconOverride).checked; config.ServiceSettings.EnableTesting = ReactDOM.findDOMNode(this.refs.EnableTesting).checked; + config.ServiceSettings.EnableDeveloper = ReactDOM.findDOMNode(this.refs.EnableDeveloper).checked; config.ServiceSettings.EnableSecurityFixAlert = ReactDOM.findDOMNode(this.refs.EnableSecurityFixAlert).checked; //config.ServiceSettings.EnableOAuthServiceProvider = ReactDOM.findDOMNode(this.refs.EnableOAuthServiceProvider).checked; @@ -343,6 +344,39 @@ export default class ServiceSettings extends React.Component { <div className='form-group'> <label className='control-label col-sm-4' + htmlFor='EnableDeveloper' + > + {'Enable Developer Mode: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='EnableDeveloper' + value='true' + ref='EnableDeveloper' + defaultChecked={this.props.config.ServiceSettings.EnableDeveloper} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='EnableDeveloper' + value='false' + defaultChecked={!this.props.config.ServiceSettings.EnableDeveloper} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'(Developer Option) When true, extra information around errors will be displayed in the UI.'}</p> + </div> + </div> + + <div className='form-group'> + <label + className='control-label col-sm-4' htmlFor='EnableSecurityFixAlert' > {'Enable Security Alerts: '} diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx index cf40af6ae..3661b19e6 100644 --- a/web/react/components/more_direct_channels.jsx +++ b/web/react/components/more_direct_channels.jsx @@ -47,6 +47,21 @@ export default class MoreDirectChannels extends React.Component { UserStore.addChangeListener(this.handleUserChange); } + componentDidUpdate(prevProps) { + if (!prevProps.show && this.props.show) { + this.onShow(); + } + } + + onShow() { + if (Utils.isMobile()) { + $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 250); + } else { + $(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar(); + $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 300); + } + } + handleFilterChange() { const filter = ReactDOM.findDOMNode(this.refs.filter).value; @@ -164,15 +179,6 @@ export default class MoreDirectChannels extends React.Component { ); } - componentDidUpdate(prevProps) { - if (!prevProps.show && this.props.show) { - $(ReactDOM.findDOMNode(this.refs.userList)).css('max-height', $(window).height() - 50); - if ($(window).width() > 768) { - $(ReactDOM.findDOMNode(this.refs.userList)).perfectScrollbar(); - } - } - } - render() { if (!this.props.show) { return null; @@ -217,8 +223,8 @@ export default class MoreDirectChannels extends React.Component { <Modal.Header closeButton={true}> <Modal.Title>{'Direct Messages'}</Modal.Title> </Modal.Header> - <Modal.Body> - <div className='row filter-row'> + <Modal.Body ref='modalBody'> + <div className='filter-row'> <div className='col-sm-6'> <input ref='filter' diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index c286ee6f9..d4ec5a5f5 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -184,6 +184,34 @@ export default class NavbarDropdown extends React.Component { ); } + let helpLink = null; + if (global.window.mm_config.HelpLink) { + helpLink = ( + <li> + <a + target='_blank' + href={global.window.mm_config.HelpLink} + > + {'Help'} + </a> + </li> + ); + } + + let reportLink = null; + if (global.window.mm_config.ReportAProblemLink) { + reportLink = ( + <li> + <a + target='_blank' + href={global.window.mm_config.ReportAProblemLink} + > + {'Report a Problem'} + </a> + </li> + ); + } + return ( <ul className='nav navbar-nav navbar-right'> <li @@ -230,22 +258,8 @@ export default class NavbarDropdown extends React.Component { {sysAdminLink} {teams} <li className='divider'></li> - <li> - <a - target='_blank' - href='/static/help/help.html' - > - {'Help'} - </a> - </li> - <li> - <a - target='_blank' - href='/static/help/report_problem.html' - > - {'Report a Problem'} - </a> - </li> + {helpLink} + {reportLink} <li> <a href='#' diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx index b7ac92672..cc4f5e138 100644 --- a/web/react/components/posts_view.jsx +++ b/web/react/components/posts_view.jsx @@ -233,7 +233,8 @@ export default class PostsView extends React.Component { window.requestAnimationFrame(() => { // If separator exists scroll to it. Otherwise scroll to bottom. if (this.refs.newMessageSeparator) { - this.refs.newMessageSeparator.scrollIntoView(); + var objDiv = this.refs.postlist; + objDiv.scrollTop = this.refs.newMessageSeparator.offsetTop; //scrolls node to top of Div } else { this.refs.postlist.scrollTop = this.refs.postlist.scrollHeight; } diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx index f71abf971..f235cac0a 100644 --- a/web/react/components/search_results_item.jsx +++ b/web/react/components/search_results_item.jsx @@ -31,8 +31,7 @@ export default class SearchResultsItem extends React.Component { handleFocusRHSClick(e) { e.preventDefault(); - - EventHelpers.emitPostFocusRightHandSideEvent(this.props.post); + EventHelpers.emitPostFocusRightHandSideFromSearch(this.props.post, this.props.isMentionSearch); } render() { diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 8393440cb..a1b93188a 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -378,6 +378,13 @@ export default class Sidebar extends React.Component { ); } + var icon = null; + if (channel.type === 'O') { + icon = <div className='status'><i className='fa fa-globe'></i></div>; + } else if (channel.type === 'P') { + icon = <div className='status'><i className='fa fa-lock'></i></div>; + } + // set up click handler to switch channels (or create a new channel for non-existant ones) var handleClick = null; var href = '#'; @@ -461,6 +468,7 @@ export default class Sidebar extends React.Component { href={href} onClick={handleClick} > + {icon} {status} {channel.display_name} {badge} diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx index d93d146d8..20c2bf696 100644 --- a/web/react/components/sidebar_right_menu.jsx +++ b/web/react/components/sidebar_right_menu.jsx @@ -49,7 +49,7 @@ export default class SidebarRightMenu extends React.Component { href='#' onClick={EventHelpers.showInviteMemberModal} > - <i className='fa fa-user'></i>Invite New Member + <i className='fa fa-user'></i>{'Invite New Member'} </a> </li> ); @@ -75,7 +75,7 @@ export default class SidebarRightMenu extends React.Component { href='#' data-toggle='modal' data-target='#team_settings' - ><i className='fa fa-globe'></i>Team Settings</a> + ><i className='fa fa-globe'></i>{'Team Settings'}</a> </li> ); manageLink = ( @@ -93,7 +93,7 @@ export default class SidebarRightMenu extends React.Component { <a href={'/admin_console?' + utils.getSessionIndex()} > - <i className='fa fa-wrench'></i>System Console</a> + <i className='fa fa-wrench'></i>{'System Console'}</a> </li> ); } @@ -107,6 +107,27 @@ export default class SidebarRightMenu extends React.Component { teamDisplayName = this.props.teamDisplayName; } + let helpLink = null; + if (global.window.mm_config.HelpLink) { + helpLink = ( + <li> + <a + target='_blank' + href={global.window.mm_config.HelpLink} + ><i className='fa fa-question'></i>{'Help'}</a></li> + ); + } + + let reportLink = null; + if (global.window.mm_config.ReportAProblemLink) { + reportLink = ( + <li> + <a + target='_blank' + href={global.window.mm_config.ReportAProblemLink} + ><i className='fa fa-phone'></i>{'Report a Problem'}</a></li> + ); + } return ( <div> <div className='team__header theme'> @@ -123,7 +144,7 @@ export default class SidebarRightMenu extends React.Component { href='#' onClick={() => this.setState({showUserSettingsModal: true})} > - <i className='fa fa-cog'></i>Account Settings + <i className='fa fa-cog'></i>{'Account Settings'} </a> </li> {teamSettingsLink} @@ -135,18 +156,10 @@ export default class SidebarRightMenu extends React.Component { <a href='#' onClick={this.handleLogoutClick} - ><i className='fa fa-sign-out'></i>Logout</a></li> + ><i className='fa fa-sign-out'></i>{'Logout'}</a></li> <li className='divider'></li> - <li> - <a - target='_blank' - href='/static/help/help.html' - ><i className='fa fa-question'></i>Help</a></li> - <li> - <a - target='_blank' - href='/static/help/report_problem.html' - ><i className='fa fa-phone'></i>Report a Problem</a></li> + {helpLink} + {reportLink} </ul> </div> <UserSettingsModal diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index 9360d31f8..7ab1e5512 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -112,23 +112,30 @@ export default class TutorialIntroScreens extends React.Component { const circles = this.createCircles(); - return ( - <div> - <h3>{'You’re all set'}</h3> - <p> - {inviteModalLink} - {' when you’re ready.'} - </p> + let supportInfo = null; + if (global.window.mm_config.SupportEmail) { + supportInfo = ( <p> {'Need anything, just email us at '} <a - href='mailto:feedback@mattermost.com' + href={'mailto:' + global.window.mm_config.SupportEmail} target='_blank' > - {'feedback@mattermost.com'} + {global.window.mm_config.SupportEmail} </a> {'.'} </p> + ); + } + + return ( + <div> + <h3>{'You’re all set'}</h3> + <p> + {inviteModalLink} + {' when you’re ready.'} + </p> + {supportInfo} {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up. Use it for posting updates everyone needs to know.'} {circles} </div> diff --git a/web/react/components/user_settings/custom_theme_chooser.jsx b/web/react/components/user_settings/custom_theme_chooser.jsx index 35f836adb..b7d90922a 100644 --- a/web/react/components/user_settings/custom_theme_chooser.jsx +++ b/web/react/components/user_settings/custom_theme_chooser.jsx @@ -3,6 +3,9 @@ import Constants from '../../utils/constants.jsx'; +const OverlayTrigger = ReactBootstrap.OverlayTrigger; +const Popover = ReactBootstrap.Popover; + export default class CustomThemeChooser extends React.Component { constructor(props) { super(props); @@ -19,6 +22,15 @@ export default class CustomThemeChooser extends React.Component { }); $('.color-picker').on('changeColor', this.onPickerChange); } + componentDidUpdate() { + const theme = this.props.theme; + Constants.THEME_ELEMENTS.forEach((element) => { + if (theme.hasOwnProperty(element.id) && element.id !== 'codeTheme') { + $('#' + element.id).data('colorpicker').color.setColor(theme[element.id]); + $('#' + element.id).colorpicker('update'); + } + }); + } onPickerChange(e) { const theme = this.props.theme; theme[e.target.id] = e.color.toHex(); @@ -72,6 +84,19 @@ export default class CustomThemeChooser extends React.Component { ); }); + var popoverContent = ( + <Popover + bsStyle='info' + id='code-popover' + className='code-popover' + > + <img + width='200' + src={'/static/images/themes/code_themes/' + theme[element.id] + '.png'} + /> + </Popover> + ); + elements.push( <div className='col-sm-4 form-group' @@ -85,16 +110,22 @@ export default class CustomThemeChooser extends React.Component { <select className='form-control' type='text' - defaultValue={theme[element.id]} + value={theme[element.id]} onChange={this.onInputChange} > {codeThemeOptions} </select> + <OverlayTrigger + placement='top' + overlay={popoverContent} + ref='headerOverlay' + > <span className='input-group-addon'> <img src={'/static/images/themes/code_themes/' + theme[element.id] + '.png'} /> </span> + </OverlayTrigger> </div> </div> ); @@ -112,7 +143,7 @@ export default class CustomThemeChooser extends React.Component { <input className='form-control' type='text' - defaultValue={theme[element.id]} + value={theme[element.id]} onChange={this.onInputChange} /> <span className='input-group-addon'><i></i></span> diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx index fdbac9831..ede639691 100644 --- a/web/react/components/user_settings/manage_outgoing_hooks.jsx +++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx @@ -36,7 +36,7 @@ export default class ManageOutgoingHooks extends React.Component { if (this.state.triggerWords.length !== 0) { hook.trigger_words = this.state.triggerWords.trim().split(','); } - hook.callback_urls = this.state.callbackURLs.split('\n'); + hook.callback_urls = this.state.callbackURLs.split('\n').map((url) => url.trim()); Client.addOutgoingHook( hook, diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx index 962efd7a2..014038dd4 100644 --- a/web/react/components/user_settings/user_settings_general.jsx +++ b/web/react/components/user_settings/user_settings_general.jsx @@ -9,6 +9,7 @@ import UserStore from '../../stores/user_store.jsx'; import ErrorStore from '../../stores/error_store.jsx'; import * as Client from '../../utils/client.jsx'; +import Constants from '../../utils/constants.jsx'; import * as AsyncClient from '../../utils/async_client.jsx'; import * as Utils from '../../utils/utils.jsx'; @@ -51,7 +52,7 @@ export default class UserSettingsGeneralTab extends React.Component { } if (user.username === username) { - this.setState({clientError: 'You must submit a new username.', emailError: '', serverError: ''}); + this.updateSection(''); return; } @@ -66,7 +67,7 @@ export default class UserSettingsGeneralTab extends React.Component { const nickname = this.state.nickname.trim(); if (user.nickname === nickname) { - this.setState({clientError: 'You must submit a new nickname.', emailError: '', serverError: ''}); + this.updateSection(''); return; } @@ -82,7 +83,7 @@ export default class UserSettingsGeneralTab extends React.Component { const lastName = this.state.lastName.trim(); if (user.first_name === firstName && user.last_name === lastName) { - this.setState({clientError: 'You must submit a new first or last name.', emailError: '', serverError: ''}); + this.updateSection(''); return; } @@ -98,10 +99,6 @@ export default class UserSettingsGeneralTab extends React.Component { const email = this.state.email.trim().toLowerCase(); const confirmEmail = this.state.confirmEmail.trim().toLowerCase(); - if (user.email === email) { - return; - } - if (email === '' || !Utils.isEmail(email)) { this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''}); return; @@ -112,6 +109,11 @@ export default class UserSettingsGeneralTab extends React.Component { return; } + if (user.email === email) { + this.updateSection(''); + return; + } + user.email = email; this.submitUser(user, true); } @@ -155,6 +157,9 @@ export default class UserSettingsGeneralTab extends React.Component { if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') { this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'}); return; + } else if (picture.size > Constants.MAX_FILE_SIZE) { + this.setState({clientError: 'Unable to upload profile image. File is too large.'}); + return; } var formData = new FormData(); |