summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/about_build_modal.jsx4
-rw-r--r--web/react/components/admin_console/service_settings.jsx11
-rw-r--r--web/react/components/claim/claim_account.jsx53
-rw-r--r--web/react/components/claim/email_to_sso.jsx97
-rw-r--r--web/react/components/claim/sso_to_email.jsx113
-rw-r--r--web/react/components/login.jsx33
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx9
-rw-r--r--web/react/components/user_settings/manage_outgoing_hooks.jsx9
-rw-r--r--web/react/components/user_settings/user_settings_display.jsx20
-rw-r--r--web/react/components/user_settings/user_settings_security.jsx323
-rw-r--r--web/react/components/view_image.jsx42
-rw-r--r--web/react/pages/claim_account.jsx19
-rw-r--r--web/react/utils/client.jsx34
-rw-r--r--web/react/utils/constants.jsx2
-rw-r--r--web/react/utils/utils.jsx10
15 files changed, 623 insertions, 156 deletions
diff --git a/web/react/components/about_build_modal.jsx b/web/react/components/about_build_modal.jsx
index f71e1c9ab..3143bec22 100644
--- a/web/react/components/about_build_modal.jsx
+++ b/web/react/components/about_build_modal.jsx
@@ -37,10 +37,6 @@ export default class AboutBuildModal extends React.Component {
<div className='col-sm-3 info__label'>{'Build Hash:'}</div>
<div className='col-sm-9'>{config.BuildHash}</div>
</div>
- <div className='row'>
- <div className='col-sm-3 info__label'>{'Enterprise Ready:'}</div>
- <div className='col-sm-9'>{config.BuildEnterpriseReady}</div>
- </div>
</Modal.Body>
<Modal.Footer>
<button
diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx
index d7582d682..e235819fe 100644
--- a/web/react/components/admin_console/service_settings.jsx
+++ b/web/react/components/admin_console/service_settings.jsx
@@ -172,7 +172,16 @@ export default class ServiceSettings extends React.Component {
defaultValue={this.props.config.ServiceSettings.GoogleDeveloperKey}
onChange={this.handleChange}
/>
- <p className='help-text'>{'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}<a href='https://www.youtube.com/watch?v=Im69kzhpR3I'>{'https://www.youtube.com/watch?v=Im69kzhpR3I'}</a>{'. Leaving field blank disables the automatic generation of YouTube video previews from links.'}</p>
+ <p className='help-text'>
+ {'Set this key to enable embedding of YouTube video previews based on hyperlinks appearing in messages or comments. Instructions to obtain a key available at '}
+ <a
+ href='https://www.youtube.com/watch?v=Im69kzhpR3I'
+ target='_blank'
+ >
+ {'https://www.youtube.com/watch?v=Im69kzhpR3I'}
+ </a>
+ {'. Leaving the field blank disables the automatic generation of YouTube video previews from links.'}
+ </p>
</div>
</div>
diff --git a/web/react/components/claim/claim_account.jsx b/web/react/components/claim/claim_account.jsx
new file mode 100644
index 000000000..f38f558db
--- /dev/null
+++ b/web/react/components/claim/claim_account.jsx
@@ -0,0 +1,53 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import EmailToSSO from './email_to_sso.jsx';
+import SSOToEmail from './sso_to_email.jsx';
+
+export default class ClaimAccount extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+ render() {
+ let content;
+ if (this.props.email === '') {
+ content = <p>{'No email specified.'}</p>;
+ } else if (this.props.currentType === '' && this.props.newType !== '') {
+ content = (
+ <EmailToSSO
+ email={this.props.email}
+ type={this.props.newType}
+ teamName={this.props.teamName}
+ teamDisplayName={this.props.teamDisplayName}
+ />
+ );
+ } else {
+ content = (
+ <SSOToEmail
+ email={this.props.email}
+ currentType={this.props.currentType}
+ teamName={this.props.teamName}
+ teamDisplayName={this.props.teamDisplayName}
+ />
+ );
+ }
+
+ return (
+ <div>
+ {content}
+ </div>
+ );
+ }
+}
+
+ClaimAccount.defaultProps = {
+};
+ClaimAccount.propTypes = {
+ currentType: React.PropTypes.string.isRequired,
+ newType: React.PropTypes.string.isRequired,
+ email: React.PropTypes.string.isRequired,
+ teamName: React.PropTypes.string.isRequired,
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/claim/email_to_sso.jsx b/web/react/components/claim/email_to_sso.jsx
new file mode 100644
index 000000000..ac0cf876b
--- /dev/null
+++ b/web/react/components/claim/email_to_sso.jsx
@@ -0,0 +1,97 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../utils/utils.jsx';
+import * as Client from '../../utils/client.jsx';
+
+export default class EmailToSSO extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submit = this.submit.bind(this);
+
+ this.state = {};
+ }
+ submit(e) {
+ e.preventDefault();
+ var state = {};
+
+ var password = ReactDOM.findDOMNode(this.refs.password).value.trim();
+ if (!password) {
+ state.error = 'Please enter your password.';
+ this.setState(state);
+ return;
+ }
+
+ state.error = null;
+ this.setState(state);
+
+ var postData = {};
+ postData.password = password;
+ postData.email = this.props.email;
+ postData.team_name = this.props.teamName;
+ postData.service = this.props.type;
+
+ Client.switchToSSO(postData,
+ (data) => {
+ if (data.follow_link) {
+ window.location.href = data.follow_link;
+ }
+ },
+ (error) => {
+ this.setState({error});
+ }
+ );
+ }
+ 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 formClass = 'form-group';
+ if (error) {
+ formClass += ' has-error';
+ }
+
+ const uiType = Utils.toTitleCase(this.props.type) + ' SSO';
+
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>{'Switch Email/Password Account to ' + uiType}</h3>
+ <form onSubmit={this.submit}>
+ <p>{'Upon claiming your account, you will only be able to login with ' + Utils.toTitleCase(this.props.type) + ' SSO.'}</p>
+ <p>{'Enter the password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder='Password'
+ spellCheck='false'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ {'Switch account to ' + uiType}
+ </button>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
+
+EmailToSSO.defaultProps = {
+};
+EmailToSSO.propTypes = {
+ type: React.PropTypes.string.isRequired,
+ email: React.PropTypes.string.isRequired,
+ teamName: React.PropTypes.string.isRequired,
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/claim/sso_to_email.jsx b/web/react/components/claim/sso_to_email.jsx
new file mode 100644
index 000000000..0868b7f2f
--- /dev/null
+++ b/web/react/components/claim/sso_to_email.jsx
@@ -0,0 +1,113 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import * as Utils from '../../utils/utils.jsx';
+import * as Client from '../../utils/client.jsx';
+
+export default class SSOToEmail extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.submit = this.submit.bind(this);
+
+ this.state = {};
+ }
+ submit(e) {
+ e.preventDefault();
+ const state = {};
+
+ const password = ReactDOM.findDOMNode(this.refs.password).value.trim();
+ if (!password) {
+ state.error = 'Please enter a password.';
+ this.setState(state);
+ return;
+ }
+
+ const confirmPassword = ReactDOM.findDOMNode(this.refs.passwordconfirm).value.trim();
+ if (!confirmPassword || password !== confirmPassword) {
+ state.error = 'Passwords do not match.';
+ this.setState(state);
+ return;
+ }
+
+ state.error = null;
+ this.setState(state);
+
+ var postData = {};
+ postData.password = password;
+ postData.email = this.props.email;
+ postData.team_name = this.props.teamName;
+
+ Client.switchToEmail(postData,
+ (data) => {
+ if (data.follow_link) {
+ window.location.href = data.follow_link;
+ }
+ },
+ (error) => {
+ this.setState({error});
+ }
+ );
+ }
+ 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 formClass = 'form-group';
+ if (error) {
+ formClass += ' has-error';
+ }
+
+ const uiType = Utils.toTitleCase(this.props.currentType) + ' SSO';
+
+ return (
+ <div className='col-sm-12'>
+ <div className='signup-team__container'>
+ <h3>{'Switch ' + uiType + ' Account to Email'}</h3>
+ <form onSubmit={this.submit}>
+ <p>{'Upon changing your account type, you will only be able to login with your email and password.'}</p>
+ <p>{'Enter a new password for your ' + this.props.teamDisplayName + ' ' + global.window.mm_config.SiteName + ' account.'}</p>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='password'
+ ref='password'
+ placeholder='New Password'
+ spellCheck='false'
+ />
+ </div>
+ <div className={formClass}>
+ <input
+ type='password'
+ className='form-control'
+ name='passwordconfirm'
+ ref='passwordconfirm'
+ placeholder='Confirm Password'
+ spellCheck='false'
+ />
+ </div>
+ {error}
+ <button
+ type='submit'
+ className='btn btn-primary'
+ >
+ {'Switch ' + uiType + ' account to email and password'}
+ </button>
+ </form>
+ </div>
+ </div>
+ );
+ }
+}
+
+SSOToEmail.defaultProps = {
+};
+SSOToEmail.propTypes = {
+ currentType: React.PropTypes.string.isRequired,
+ email: React.PropTypes.string.isRequired,
+ teamName: React.PropTypes.string.isRequired,
+ teamDisplayName: React.PropTypes.string.isRequired
+};
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index 9afaa8b0d..1d9b3e906 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -1,10 +1,12 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import * as Utils from '../utils/utils.jsx';
import LoginEmail from './login_email.jsx';
import LoginLdap from './login_ldap.jsx';
+import * as Utils from '../utils/utils.jsx';
+import Constants from '../utils/constants.jsx';
+
export default class Login extends React.Component {
constructor(props) {
super(props);
@@ -40,15 +42,24 @@ export default class Login extends React.Component {
);
}
- const verifiedParam = Utils.getUrlParameter('verified');
- let verifiedBox = '';
- if (verifiedParam) {
- verifiedBox = (
- <div className='alert alert-success'>
- <i className='fa fa-check' />
- {' Email Verified'}
- </div>
- );
+ const extraParam = Utils.getUrlParameter('extra');
+ let extraBox = '';
+ if (extraParam) {
+ let msg;
+ if (extraParam === Constants.SIGNIN_CHANGE) {
+ msg = ' Sign-in method changed successfully';
+ } else if (extraParam === Constants.SIGNIN_VERIFIED) {
+ msg = ' Email Verified';
+ }
+
+ if (msg != null) {
+ extraBox = (
+ <div className='alert alert-success'>
+ <i className='fa fa-check' />
+ {msg}
+ </div>
+ );
+ }
}
let emailSignup;
@@ -124,7 +135,7 @@ export default class Login extends React.Component {
<h5 className='margin--less'>{'Sign in to:'}</h5>
<h2 className='signup-team__name'>{teamDisplayName}</h2>
<h2 className='signup-team__subdomain'>{'on '}{global.window.mm_config.SiteName}</h2>
- {verifiedBox}
+ {extraBox}
{loginMessage}
{emailSignup}
{ldapLogin}
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 9ebb55646..1506e3c98 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -162,7 +162,14 @@ export default class ManageIncomingHooks extends React.Component {
return (
<div key='addIncomingHook'>
- {'Create webhook URLs for use in external integrations. Please see '}<a href='http://mattermost.org/webhooks'>{'http://mattermost.org/webhooks'}</a> {' to learn more.'}
+ {'Create webhook URLs for use in external integrations. Please see '}
+ <a
+ href='http://mattermost.org/webhooks'
+ target='_blank'
+ >
+ {'http://mattermost.org/webhooks'}
+ </a>
+ {' to learn more.'}
<div><label className='control-label padding-top x2'>{'Add a new incoming webhook'}</label></div>
<div className='row padding-top'>
<div className='col-sm-10 padding-bottom'>
diff --git a/web/react/components/user_settings/manage_outgoing_hooks.jsx b/web/react/components/user_settings/manage_outgoing_hooks.jsx
index ede639691..17acf0f10 100644
--- a/web/react/components/user_settings/manage_outgoing_hooks.jsx
+++ b/web/react/components/user_settings/manage_outgoing_hooks.jsx
@@ -240,7 +240,14 @@ export default class ManageOutgoingHooks extends React.Component {
return (
<div key='addOutgoingHook'>
- {'Create webhooks to send new message events to an external integration. Please see '}<a href='http://mattermost.org/webhooks'>{'http://mattermost.org/webhooks'}</a> {' to learn more.'}
+ {'Create webhooks to send new message events to an external integration. Please see '}
+ <a
+ href='http://mattermost.org/webhooks'
+ target='_blank'
+ >
+ {'http://mattermost.org/webhooks'}
+ </a>
+ {' to learn more.'}
<div><label className='control-label padding-top x2'>{'Add a new outgoing webhook'}</label></div>
<div className='padding-top divider-light'></div>
<div className='padding-top'>
diff --git a/web/react/components/user_settings/user_settings_display.jsx b/web/react/components/user_settings/user_settings_display.jsx
index 96c3985d0..1ff0a2913 100644
--- a/web/react/components/user_settings/user_settings_display.jsx
+++ b/web/react/components/user_settings/user_settings_display.jsx
@@ -29,17 +29,16 @@ export default class UserSettingsDisplay extends React.Component {
this.handleNameRadio = this.handleNameRadio.bind(this);
this.handleFont = this.handleFont.bind(this);
this.updateSection = this.updateSection.bind(this);
+ this.updateState = this.updateState.bind(this);
+ this.deactivate = this.deactivate.bind(this);
this.state = getDisplayStateFromStores();
- this.selectedFont = this.state.selectedFont;
}
handleSubmit() {
const timePreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time', this.state.militaryTime);
const namePreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'name_format', this.state.nameFormat);
const fontPreference = PreferenceStore.setPreference(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'selected_font', this.state.selectedFont);
- this.selectedFont = this.state.selectedFont;
-
savePreferences([timePreference, namePreference, fontPreference],
() => {
PreferenceStore.emitChange();
@@ -61,9 +60,19 @@ export default class UserSettingsDisplay extends React.Component {
this.setState({selectedFont});
}
updateSection(section) {
- this.setState(getDisplayStateFromStores());
+ this.updateState();
this.props.updateSection(section);
}
+ updateState() {
+ const newState = getDisplayStateFromStores();
+ if (!Utils.areObjectsEqual(newState, this.state)) {
+ this.handleFont(newState.selectedFont);
+ this.setState(newState);
+ }
+ }
+ deactivate() {
+ this.updateState();
+ }
render() {
const serverError = this.state.serverError || null;
let clockSection;
@@ -266,9 +275,6 @@ export default class UserSettingsDisplay extends React.Component {
submit={this.handleSubmit}
server_error={serverError}
updateSection={(e) => {
- if (this.selectedFont !== this.state.selectedFont) {
- this.handleFont(this.selectedFont);
- }
this.updateSection('');
e.preventDefault();
}}
diff --git a/web/react/components/user_settings/user_settings_security.jsx b/web/react/components/user_settings/user_settings_security.jsx
index fa2fecf07..d9c5f58a9 100644
--- a/web/react/components/user_settings/user_settings_security.jsx
+++ b/web/react/components/user_settings/user_settings_security.jsx
@@ -6,6 +6,9 @@ import SettingItemMax from '../setting_item_max.jsx';
import AccessHistoryModal from '../access_history_modal.jsx';
import ActivityLogModal from '../activity_log_modal.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
+
+import TeamStore from '../../stores/team_store.jsx';
+
import * as Client from '../../utils/client.jsx';
import * as AsyncClient from '../../utils/async_client.jsx';
import Constants from '../../utils/constants.jsx';
@@ -18,9 +21,19 @@ export default class SecurityTab extends React.Component {
this.updateCurrentPassword = this.updateCurrentPassword.bind(this);
this.updateNewPassword = this.updateNewPassword.bind(this);
this.updateConfirmPassword = this.updateConfirmPassword.bind(this);
- this.setupInitialState = this.setupInitialState.bind(this);
+ this.getDefaultState = this.getDefaultState.bind(this);
+ this.createPasswordSection = this.createPasswordSection.bind(this);
+ this.createSignInSection = this.createSignInSection.bind(this);
- this.state = this.setupInitialState();
+ this.state = this.getDefaultState();
+ }
+ getDefaultState() {
+ return {
+ currentPassword: '',
+ newPassword: '',
+ confirmPassword: '',
+ authService: this.props.user.auth_service
+ };
}
submitPassword(e) {
e.preventDefault();
@@ -51,13 +64,13 @@ export default class SecurityTab extends React.Component {
data.new_password = newPassword;
Client.updatePassword(data,
- function success() {
+ () => {
this.props.updateSection('');
AsyncClient.getMe();
- this.setState(this.setupInitialState());
- }.bind(this),
- function fail(err) {
- var state = this.setupInitialState();
+ this.setState(this.getDefaultState());
+ },
+ (err) => {
+ var state = this.getDefaultState();
if (err.message) {
state.serverError = err.message;
} else {
@@ -65,7 +78,7 @@ export default class SecurityTab extends React.Component {
}
state.passwordError = '';
this.setState(state);
- }.bind(this)
+ }
);
}
updateCurrentPassword(e) {
@@ -77,86 +90,60 @@ export default class SecurityTab extends React.Component {
updateConfirmPassword(e) {
this.setState({confirmPassword: e.target.value});
}
- setupInitialState() {
- return {currentPassword: '', newPassword: '', confirmPassword: ''};
- }
- render() {
- var serverError;
- if (this.state.serverError) {
- serverError = this.state.serverError;
- }
- var passwordError;
- if (this.state.passwordError) {
- passwordError = this.state.passwordError;
- }
+ createPasswordSection() {
+ let updateSectionStatus;
- var updateSectionStatus;
- var passwordSection;
- if (this.props.activeSection === 'password') {
- var inputs = [];
- var submit = null;
-
- if (this.props.user.auth_service === '') {
- inputs.push(
- <div
- key='currentPasswordUpdateForm'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>Current Password</label>
- <div className='col-sm-7'>
- <input
- className='form-control'
- type='password'
- onChange={this.updateCurrentPassword}
- value={this.state.currentPassword}
- />
- </div>
- </div>
- );
- inputs.push(
- <div
- key='newPasswordUpdateForm'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>New Password</label>
- <div className='col-sm-7'>
- <input
- className='form-control'
- type='password'
- onChange={this.updateNewPassword}
- value={this.state.newPassword}
- />
- </div>
+ if (this.props.activeSection === 'password' && this.props.user.auth_service === '') {
+ const inputs = [];
+
+ inputs.push(
+ <div
+ key='currentPasswordUpdateForm'
+ className='form-group'
+ >
+ <label className='col-sm-5 control-label'>{'Current Password'}</label>
+ <div className='col-sm-7'>
+ <input
+ className='form-control'
+ type='password'
+ onChange={this.updateCurrentPassword}
+ value={this.state.currentPassword}
+ />
</div>
- );
- inputs.push(
- <div
- key='retypeNewPasswordUpdateForm'
- className='form-group'
- >
- <label className='col-sm-5 control-label'>Retype New Password</label>
- <div className='col-sm-7'>
- <input
- className='form-control'
- type='password'
- onChange={this.updateConfirmPassword}
- value={this.state.confirmPassword}
- />
- </div>
+ </div>
+ );
+ inputs.push(
+ <div
+ key='newPasswordUpdateForm'
+ className='form-group'
+ >
+ <label className='col-sm-5 control-label'>{'New Password'}</label>
+ <div className='col-sm-7'>
+ <input
+ className='form-control'
+ type='password'
+ onChange={this.updateNewPassword}
+ value={this.state.newPassword}
+ />
</div>
- );
-
- submit = this.submitPassword;
- } else {
- inputs.push(
- <div
- key='oauthPasswordInfo'
- className='form-group'
- >
- <label className='col-sm-12'>Log in occurs through GitLab. Please see your GitLab account settings page to update your password.</label>
+ </div>
+ );
+ inputs.push(
+ <div
+ key='retypeNewPasswordUpdateForm'
+ className='form-group'
+ >
+ <label className='col-sm-5 control-label'>{'Retype New Password'}</label>
+ <div className='col-sm-7'>
+ <input
+ className='form-control'
+ type='password'
+ onChange={this.updateConfirmPassword}
+ value={this.state.confirmPassword}
+ />
</div>
- );
- }
+ </div>
+ );
updateSectionStatus = function resetSection(e) {
this.props.updateSection('');
@@ -164,51 +151,157 @@ export default class SecurityTab extends React.Component {
e.preventDefault();
}.bind(this);
- passwordSection = (
+ return (
<SettingItemMax
title='Password'
inputs={inputs}
- submit={submit}
- server_error={serverError}
- client_error={passwordError}
+ submit={this.submitPassword}
+ server_error={this.state.serverError}
+ client_error={this.state.passwordError}
updateSection={updateSectionStatus}
/>
);
- } else {
- var describe;
- if (this.props.user.auth_service === '') {
- var d = new Date(this.props.user.last_password_update);
- var hour = '12';
- if (d.getHours() % 12) {
- hour = String(d.getHours() % 12);
- }
- var min = String(d.getMinutes());
- if (d.getMinutes() < 10) {
- min = '0' + d.getMinutes();
- }
- var timeOfDay = ' am';
- if (d.getHours() >= 12) {
- timeOfDay = ' pm';
- }
+ }
+
+ var describe;
+ var d = new Date(this.props.user.last_password_update);
+ var hour = '12';
+ if (d.getHours() % 12) {
+ hour = String(d.getHours() % 12);
+ }
+ var min = String(d.getMinutes());
+ if (d.getMinutes() < 10) {
+ min = '0' + d.getMinutes();
+ }
+ var timeOfDay = ' am';
+ if (d.getHours() >= 12) {
+ timeOfDay = ' pm';
+ }
- describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
- } else {
- describe = 'Log in done through GitLab';
+ describe = 'Last updated ' + Constants.MONTHS[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear() + ' at ' + hour + ':' + min + timeOfDay;
+
+ updateSectionStatus = function updateSection() {
+ this.props.updateSection('password');
+ }.bind(this);
+
+ return (
+ <SettingItemMin
+ title='Password'
+ describe={describe}
+ updateSection={updateSectionStatus}
+ />
+ );
+ }
+ createSignInSection() {
+ let updateSectionStatus;
+ const user = this.props.user;
+
+ if (this.props.activeSection === 'signin') {
+ const inputs = [];
+ const teamName = TeamStore.getCurrent().name;
+
+ let emailOption;
+ if (global.window.mm_config.EnableSignUpWithEmail === 'true' && user.auth_service !== '') {
+ emailOption = (
+ <div>
+ <a
+ className='btn btn-primary'
+ href={'/' + teamName + '/claim?email=' + user.email}
+ >
+ {'Switch to using email and password'}
+ </a>
+ <br/>
+ </div>
+ );
}
- updateSectionStatus = function updateSection() {
- this.props.updateSection('password');
+ let gitlabOption;
+ if (global.window.mm_config.EnableSignUpWithGitLab === 'true' && user.auth_service === '') {
+ gitlabOption = (
+ <div>
+ <a
+ className='btn btn-primary'
+ href={'/' + teamName + '/claim?email=' + user.email + '&new_type=' + Constants.GITLAB_SERVICE}
+ >
+ {'Switch to using GitLab SSO'}
+ </a>
+ <br/>
+ </div>
+ );
+ }
+
+ let googleOption;
+ if (global.window.mm_config.EnableSignUpWithGoogle === 'true' && user.auth_service === '') {
+ googleOption = (
+ <div>
+ <a
+ className='btn btn-primary'
+ href={'/' + teamName + '/claim?email=' + user.email + '&new_type=' + Constants.GOOGLE_SERVICE}
+ >
+ {'Switch to using Google SSO'}
+ </a>
+ <br/>
+ </div>
+ );
+ }
+
+ inputs.push(
+ <div key='userSignInOption'>
+ {emailOption}
+ {gitlabOption}
+ <br/>
+ {googleOption}
+ </div>
+ );
+
+ updateSectionStatus = function updateSection(e) {
+ this.props.updateSection('');
+ this.setState({serverError: null});
+ e.preventDefault();
}.bind(this);
- passwordSection = (
- <SettingItemMin
- title='Password'
- describe={describe}
+ const extraInfo = <span>{'You may only have one sign-in method at a time. Switching sign-in method will send an email notifying you if the change was successful.'}</span>;
+
+ return (
+ <SettingItemMax
+ title='Sign-in Method'
+ extraInfo={extraInfo}
+ inputs={inputs}
+ server_error={this.state.serverError}
updateSection={updateSectionStatus}
/>
);
}
+ updateSectionStatus = function updateSection() {
+ this.props.updateSection('signin');
+ }.bind(this);
+
+ let describe = 'Email and Password';
+ if (this.props.user.auth_service === Constants.GITLAB_SERVICE) {
+ describe = 'GitLab SSO';
+ }
+
+ return (
+ <SettingItemMin
+ title='Sign-in Method'
+ describe={describe}
+ updateSection={updateSectionStatus}
+ />
+ );
+ }
+ render() {
+ const passwordSection = this.createPasswordSection();
+ let signInSection;
+
+ let numMethods = 0;
+ numMethods = global.window.mm_config.EnableSignUpWithGitLab === 'true' ? numMethods + 1 : numMethods;
+ numMethods = global.window.mm_config.EnableSignUpWithGoogle === 'true' ? numMethods + 1 : numMethods;
+
+ if (global.window.mm_config.EnableSignUpWithEmail && numMethods > 0) {
+ signInSection = this.createSignInSection();
+ }
+
return (
<div>
<div className='modal-header'>
@@ -233,9 +326,11 @@ export default class SecurityTab extends React.Component {
</h4>
</div>
<div className='user-settings'>
- <h3 className='tab-header'>Security Settings</h3>
+ <h3 className='tab-header'>{'Security Settings'}</h3>
<div className='divider-dark first'/>
{passwordSection}
+ <div className='divider-light'/>
+ {signInSection}
<div className='divider-dark'/>
<br></br>
<ToggleModalButton
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index 7edf6283b..196a44bd0 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -31,19 +31,12 @@ export default class ViewImageModal extends React.Component {
this.onMouseEnterImage = this.onMouseEnterImage.bind(this);
this.onMouseLeaveImage = this.onMouseLeaveImage.bind(this);
- const loaded = [];
- const progress = [];
- for (var i = 0; i < this.props.filenames.length; i++) {
- loaded.push(false);
- progress.push(0);
- }
-
this.state = {
imgId: this.props.startId,
- fileInfo: new Map(),
+ fileInfo: null,
imgHeight: '100%',
- loaded,
- progress,
+ loaded: Utils.fillArray(false, this.props.filenames.length),
+ progress: Utils.fillArray(0, this.props.filenames.length),
showFooter: false
};
}
@@ -104,17 +97,28 @@ export default class ViewImageModal extends React.Component {
} else if (nextProps.show === false && this.props.show === true) {
this.onModalHidden();
}
+
+ if (!Utils.areObjectsEqual(this.props.filenames, nextProps.filenames)) {
+ this.setState({
+ loaded: Utils.fillArray(false, nextProps.filenames.length),
+ progress: Utils.fillArray(0, nextProps.filenames.length)
+ });
+ }
}
onFileStoreChange(filename) {
const id = this.props.filenames.indexOf(filename);
- if (id !== -1 && !this.state.loaded[id]) {
- const fileInfo = this.state.fileInfo;
- fileInfo.set(filename, FileStore.getInfo(filename));
- this.setState({fileInfo});
+ if (id !== -1) {
+ if (id === this.state.imgId) {
+ this.setState({
+ fileInfo: FileStore.getInfo(filename)
+ });
+ }
- this.loadImage(id, filename);
+ if (!this.state.loaded[id]) {
+ this.loadImage(id, filename);
+ }
}
}
@@ -132,6 +136,10 @@ export default class ViewImageModal extends React.Component {
return;
}
+ this.setState({
+ fileInfo: FileStore.getInfo(filename)
+ });
+
if (!this.state.loaded[id]) {
this.loadImage(id, filename);
}
@@ -227,8 +235,8 @@ export default class ViewImageModal extends React.Component {
var content;
if (this.state.loaded[this.state.imgId]) {
- // if a file has been loaded, we also have its info
- const fileInfo = this.state.fileInfo.get(filename);
+ // this.state.fileInfo is for the current image and we shoudl have it before we load the image
+ const fileInfo = this.state.fileInfo;
const extension = Utils.splitFileLocation(filename).ext;
const fileType = Utils.getFileType(extension);
diff --git a/web/react/pages/claim_account.jsx b/web/react/pages/claim_account.jsx
new file mode 100644
index 000000000..bca203d96
--- /dev/null
+++ b/web/react/pages/claim_account.jsx
@@ -0,0 +1,19 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import ClaimAccount from '../components/claim/claim_account.jsx';
+
+function setupClaimAccountPage(props) {
+ ReactDOM.render(
+ <ClaimAccount
+ email={props.Email}
+ currentType={props.CurrentType}
+ newType={props.NewType}
+ teamName={props.TeamName}
+ teamDisplayName={props.TeamDisplayName}
+ />,
+ document.getElementById('claim')
+ );
+}
+
+global.window.setup_claim_account_page = setupClaimAccountPage;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 8a4cee589..e1c331aff 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -228,6 +228,40 @@ export function resetPassword(data, success, error) {
track('api', 'api_users_reset_password');
}
+export function switchToSSO(data, success, error) {
+ $.ajax({
+ url: '/api/v1/users/switch_to_sso',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(data),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('switchToSSO', xhr, status, err);
+ error(e);
+ }
+ });
+
+ track('api', 'api_users_switch_to_sso');
+}
+
+export function switchToEmail(data, success, error) {
+ $.ajax({
+ url: '/api/v1/users/switch_to_email',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(data),
+ success,
+ error: function onError(xhr, status, err) {
+ var e = handleError('switchToEmail', xhr, status, err);
+ error(e);
+ }
+ });
+
+ track('api', 'api_users_switch_to_email');
+}
+
export function logout() {
track('api', 'api_users_logout');
var currentTeamUrl = TeamStore.getCurrentTeamUrl();
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index ea4921417..0298ce533 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -117,6 +117,8 @@ export default {
GITLAB_SERVICE: 'gitlab',
GOOGLE_SERVICE: 'google',
EMAIL_SERVICE: 'email',
+ SIGNIN_CHANGE: 'signin_change',
+ SIGNIN_VERIFIED: 'verified',
POST_CHUNK_SIZE: 60,
MAX_POST_CHUNKS: 3,
POST_FOCUS_CONTEXT_RADIUS: 10,
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 24d27b10a..a808c9be3 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -1261,3 +1261,13 @@ export function isFeatureEnabled(feature) {
export function isSystemMessage(post) {
return post.type && (post.type.lastIndexOf(Constants.SYSTEM_MESSAGE_PREFIX) === 0);
}
+
+export function fillArray(value, length) {
+ const arr = [];
+
+ for (let i = 0; i < length; i++) {
+ arr.push(value);
+ }
+
+ return arr;
+}