Merge pull request #388 from matrix-org/dbkr/refactor_field_errors
Refactor UI error effects
This commit is contained in:
commit
e8dbf978c3
4 changed files with 95 additions and 39 deletions
27
src/UiEffects.js
Normal file
27
src/UiEffects.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions for applying common thematic effects to UI elements.
|
||||||
|
* Ideally this would be themeable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Velocity from 'velocity-vector';
|
||||||
|
import 'velocity-vector/velocity.ui';
|
||||||
|
|
||||||
|
export function field_input_incorrect(element) {
|
||||||
|
Velocity(element, "callout.shake", 300);
|
||||||
|
}
|
|
@ -53,6 +53,7 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
return {
|
return {
|
||||||
busy: false,
|
busy: false,
|
||||||
errorText: null,
|
errorText: null,
|
||||||
|
loginIncorrect: false,
|
||||||
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
|
||||||
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
|
||||||
|
|
||||||
|
@ -68,13 +69,15 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
onPasswordLogin: function(username, password) {
|
onPasswordLogin: function(username, password) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.setState({
|
self.setState({
|
||||||
busy: true
|
busy: true,
|
||||||
|
errorText: null,
|
||||||
|
loginIncorrect: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._loginLogic.loginViaPassword(username, password).then(function(data) {
|
this._loginLogic.loginViaPassword(username, password).then(function(data) {
|
||||||
self.props.onLoggedIn(data);
|
self.props.onLoggedIn(data);
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
self._setErrorTextFromError(error);
|
self._setStateFromError(error, true);
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
self.setState({
|
self.setState({
|
||||||
busy: false
|
busy: false
|
||||||
|
@ -100,7 +103,7 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
this.setState({
|
this.setState({
|
||||||
enteredIdentityServerUrl: newIsUrl
|
enteredIdentityServerUrl: newIsUrl
|
||||||
}, function() {
|
}, function() {
|
||||||
self._initLoginLogic(null, newIsUrl);
|
self._initLoginLogic(null, newIsUrl);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -120,7 +123,7 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
// logins so let's skip that for now).
|
// logins so let's skip that for now).
|
||||||
loginLogic.chooseFlow(0);
|
loginLogic.chooseFlow(0);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
self._setErrorTextFromError(err);
|
self._setStateFromError(err, false);
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
self.setState({
|
self.setState({
|
||||||
busy: false
|
busy: false
|
||||||
|
@ -131,7 +134,8 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
enteredHomeserverUrl: hsUrl,
|
enteredHomeserverUrl: hsUrl,
|
||||||
enteredIdentityServerUrl: isUrl,
|
enteredIdentityServerUrl: isUrl,
|
||||||
busy: true,
|
busy: true,
|
||||||
errorText: null // reset err messages
|
errorText: null, // reset err messages
|
||||||
|
loginIncorrect: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -139,25 +143,30 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null
|
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null
|
||||||
},
|
},
|
||||||
|
|
||||||
_setErrorTextFromError: function(err) {
|
_setStateFromError: function(err, isLoginAttempt) {
|
||||||
|
this.setState({
|
||||||
|
errorText: this._errorTextFromError(err),
|
||||||
|
// https://matrix.org/jira/browse/SYN-744
|
||||||
|
loginIncorrect: isLoginAttempt && (err.httpStatus == 401 || err.httpStatus == 403)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_errorTextFromError(err) {
|
||||||
if (err.friendlyText) {
|
if (err.friendlyText) {
|
||||||
this.setState({
|
return err.friendlyText;
|
||||||
errorText: err.friendlyText
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var errCode = err.errcode;
|
let errCode = err.errcode;
|
||||||
if (!errCode && err.httpStatus) {
|
if (!errCode && err.httpStatus) {
|
||||||
errCode = "HTTP " + err.httpStatus;
|
errCode = "HTTP " + err.httpStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorText = "Error: Problem communicating with the given homeserver " +
|
let errorText = "Error: Problem communicating with the given homeserver " +
|
||||||
(errCode ? "(" + errCode + ")" : "")
|
(errCode ? "(" + errCode + ")" : "")
|
||||||
|
|
||||||
if (err.cors === 'rejected') {
|
if (err.cors === 'rejected') {
|
||||||
if (window.location.protocol === 'https:' &&
|
if (window.location.protocol === 'https:' &&
|
||||||
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
||||||
!this.state.enteredHomeserverUrl.startsWith("http")))
|
!this.state.enteredHomeserverUrl.startsWith("http")))
|
||||||
{
|
{
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
|
@ -173,9 +182,7 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
return errorText;
|
||||||
errorText: errorText
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentForStep: function(step) {
|
componentForStep: function(step) {
|
||||||
|
@ -186,7 +193,9 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
onSubmit={this.onPasswordLogin}
|
onSubmit={this.onPasswordLogin}
|
||||||
initialUsername={this.state.username}
|
initialUsername={this.state.username}
|
||||||
onUsernameChanged={this.onUsernameChanged}
|
onUsernameChanged={this.onUsernameChanged}
|
||||||
onForgotPasswordClick={this.props.onForgotPasswordClick} />
|
onForgotPasswordClick={this.props.onForgotPasswordClick}
|
||||||
|
loginIncorrect={this.state.loginIncorrect}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
case 'm.login.cas':
|
||||||
return (
|
return (
|
||||||
|
@ -221,7 +230,7 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
|
|
||||||
var returnToAppJsx;
|
var returnToAppJsx;
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
returnToAppJsx =
|
returnToAppJsx =
|
||||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||||
Return to app
|
Return to app
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -14,8 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var ReactDOM = require('react-dom');
|
import ReactDOM from 'react-dom';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {field_input_incorrect} from '../../../UiEffects';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pure UI component which displays a username/password form.
|
* A pure UI component which displays a username/password form.
|
||||||
|
@ -28,6 +31,7 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
initialPassword: React.PropTypes.string,
|
initialPassword: React.PropTypes.string,
|
||||||
onUsernameChanged: React.PropTypes.func,
|
onUsernameChanged: React.PropTypes.func,
|
||||||
onPasswordChanged: React.PropTypes.func,
|
onPasswordChanged: React.PropTypes.func,
|
||||||
|
loginIncorrect: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -36,6 +40,7 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
onPasswordChanged: function() {},
|
onPasswordChanged: function() {},
|
||||||
initialUsername: "",
|
initialUsername: "",
|
||||||
initialPassword: "",
|
initialPassword: "",
|
||||||
|
loginIncorrect: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -46,6 +51,16 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._passwordField = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
|
||||||
|
field_input_incorrect(this._passwordField);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onSubmitForm: function(ev) {
|
onSubmitForm: function(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.props.onSubmit(this.state.username, this.state.password);
|
this.props.onSubmit(this.state.username, this.state.password);
|
||||||
|
@ -72,14 +87,19 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pwFieldClass = classNames({
|
||||||
|
mx_Login_field: true,
|
||||||
|
error: this.props.loginIncorrect,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
<input className="mx_Login_field" ref="user" type="text"
|
<input className="mx_Login_field" type="text"
|
||||||
value={this.state.username} onChange={this.onUsernameChanged}
|
value={this.state.username} onChange={this.onUsernameChanged}
|
||||||
placeholder="Email or user name" autoFocus />
|
placeholder="Email or user name" autoFocus />
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" ref="pass" type="password"
|
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
||||||
value={this.state.password} onChange={this.onPasswordChanged}
|
value={this.state.password} onChange={this.onPasswordChanged}
|
||||||
placeholder="Password" />
|
placeholder="Password" />
|
||||||
<br />
|
<br />
|
||||||
|
@ -89,4 +109,4 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,8 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Velocity = require('velocity-vector');
|
var UiEffects = require('../../../UiEffects');
|
||||||
require('velocity-vector/velocity.ui');
|
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var Email = require('../../../email');
|
var Email = require('../../../email');
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
|
@ -117,7 +116,7 @@ module.exports = React.createClass({
|
||||||
promise.finally(function() {
|
promise.finally(function() {
|
||||||
ev.target.disabled = false;
|
ev.target.disabled = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,7 +195,7 @@ module.exports = React.createClass({
|
||||||
fieldValid[field_id] = val;
|
fieldValid[field_id] = val;
|
||||||
this.setState({fieldValid: fieldValid});
|
this.setState({fieldValid: fieldValid});
|
||||||
if (!val) {
|
if (!val) {
|
||||||
Velocity(this.fieldElementById(field_id), "callout.shake", 300);
|
UiEffects.field_input_incorrect(this.fieldElementById(field_id));
|
||||||
this.props.onError(error_code);
|
this.props.onError(error_code);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -214,12 +213,13 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_styleField: function(field_id, baseStyle) {
|
_classForField: function(field_id, baseClass) {
|
||||||
var style = baseStyle || {};
|
let cls = baseClass || '';
|
||||||
if (this.state.fieldValid[field_id] === false) {
|
if (this.state.fieldValid[field_id] === false) {
|
||||||
style['borderColor'] = 'red';
|
if (cls) cls += ' ';
|
||||||
|
cls += 'error';
|
||||||
}
|
}
|
||||||
return style;
|
return cls;
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
@ -227,10 +227,10 @@ module.exports = React.createClass({
|
||||||
var emailSection, registerButton;
|
var emailSection, registerButton;
|
||||||
if (this.props.showEmail) {
|
if (this.props.showEmail) {
|
||||||
emailSection = (
|
emailSection = (
|
||||||
<input className="mx_Login_field" type="text" ref="email"
|
<input type="text" ref="email"
|
||||||
autoFocus={true} placeholder="Email address (optional)"
|
autoFocus={true} placeholder="Email address (optional)"
|
||||||
defaultValue={this.props.defaultEmail}
|
defaultValue={this.props.defaultEmail}
|
||||||
style={this._styleField(FIELD_EMAIL)}
|
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_EMAIL)}} />
|
onBlur={function() {self.validateField(FIELD_EMAIL)}} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -250,22 +250,22 @@ module.exports = React.createClass({
|
||||||
<form onSubmit={this.onSubmit}>
|
<form onSubmit={this.onSubmit}>
|
||||||
{emailSection}
|
{emailSection}
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" type="text" ref="username"
|
<input type="text" ref="username"
|
||||||
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
placeholder={ placeholderUserName } defaultValue={this.props.defaultUsername}
|
||||||
style={this._styleField(FIELD_USERNAME)}
|
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_USERNAME)}} />
|
onBlur={function() {self.validateField(FIELD_USERNAME)}} />
|
||||||
<br />
|
<br />
|
||||||
{ this.props.guestUsername ?
|
{ this.props.guestUsername ?
|
||||||
<div className="mx_Login_fieldLabel">Setting a user name will create a fresh account</div> : null
|
<div className="mx_Login_fieldLabel">Setting a user name will create a fresh account</div> : null
|
||||||
}
|
}
|
||||||
<input className="mx_Login_field" type="password" ref="password"
|
<input type="password" ref="password"
|
||||||
style={this._styleField(FIELD_PASSWORD)}
|
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_PASSWORD)}}
|
onBlur={function() {self.validateField(FIELD_PASSWORD)}}
|
||||||
placeholder="Password" defaultValue={this.props.defaultPassword} />
|
placeholder="Password" defaultValue={this.props.defaultPassword} />
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" type="password" ref="passwordConfirm"
|
<input type="password" ref="passwordConfirm"
|
||||||
placeholder="Confirm password"
|
placeholder="Confirm password"
|
||||||
style={this._styleField(FIELD_PASSWORD_CONFIRM)}
|
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM)}}
|
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM)}}
|
||||||
defaultValue={this.props.defaultPassword} />
|
defaultValue={this.props.defaultPassword} />
|
||||||
<br />
|
<br />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue