From f469bbbb6484922d9e24bd8f410906cb17487fb4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 15:59:17 +0100 Subject: [PATCH] Refactor UI error effects And add error effects to the login page to be consistent with the registration page --- src/UiEffects.js | 27 ++++++++++++++++ src/components/structures/login/Login.js | 23 ++++++++++--- src/components/views/login/PasswordLogin.js | 30 ++++++++++++++--- .../views/login/RegistrationForm.js | 32 +++++++++---------- 4 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 src/UiEffects.js diff --git a/src/UiEffects.js b/src/UiEffects.js new file mode 100644 index 0000000000..76db0b7f12 --- /dev/null +++ b/src/UiEffects.js @@ -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); +} diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index a73ad30f87..b402f12502 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -52,6 +52,7 @@ module.exports = React.createClass({displayName: 'Login', getInitialState: function() { return { busy: false, + error: null, // The error as-is from the login request errorText: null, enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl, enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl, @@ -62,18 +63,22 @@ module.exports = React.createClass({displayName: 'Login', }, componentWillMount: function() { + this._passwordLogin = null; this._initLoginLogic(); }, onPasswordLogin: function(username, password) { var self = this; self.setState({ - busy: true + busy: true, + error: null, + errorText: null, }); this._loginLogic.loginViaPassword(username, password).then(function(data) { self.props.onLoggedIn(data); }, function(error) { + self.setState({error: error}); self._setErrorTextFromError(error); }).finally(function() { self.setState({ @@ -100,7 +105,7 @@ module.exports = React.createClass({displayName: 'Login', this.setState({ enteredIdentityServerUrl: newIsUrl }, function() { - self._initLoginLogic(null, newIsUrl); + self._initLoginLogic(null, newIsUrl); }); }, @@ -157,7 +162,7 @@ module.exports = React.createClass({displayName: 'Login', if (err.cors === 'rejected') { if (window.location.protocol === 'https:' && - (this.state.enteredHomeserverUrl.startsWith("http:") || + (this.state.enteredHomeserverUrl.startsWith("http:") || !this.state.enteredHomeserverUrl.startsWith("http"))) { errorText = @@ -181,12 +186,20 @@ module.exports = React.createClass({displayName: 'Login', componentForStep: function(step) { switch (step) { case 'm.login.password': + // https://matrix.org/jira/browse/SYN-744 + const loginIncorrect = ( + this.state.error && + (this.state.error.httpStatus == 401 || this.state.error.httpStatus == 403) + ); return ( + onForgotPasswordClick={this.props.onForgotPasswordClick} + ref={(e) => {this._passwordLogin = e;}} + loginIncorrect={loginIncorrect} + /> ); case 'm.login.cas': return ( @@ -221,7 +234,7 @@ module.exports = React.createClass({displayName: 'Login', var returnToAppJsx; if (this.props.onCancelClick) { - returnToAppJsx = + returnToAppJsx = Return to app diff --git a/src/components/views/login/PasswordLogin.js b/src/components/views/login/PasswordLogin.js index c5474f60c1..1a40ddde6b 100644 --- a/src/components/views/login/PasswordLogin.js +++ b/src/components/views/login/PasswordLogin.js @@ -14,8 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -var React = require('react'); -var ReactDOM = require('react-dom'); +import React from 'react'; +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. @@ -28,6 +31,7 @@ module.exports = React.createClass({displayName: 'PasswordLogin', initialPassword: React.PropTypes.string, onUsernameChanged: React.PropTypes.func, onPasswordChanged: React.PropTypes.func, + loginIncorrect: React.PropTypes.bool, }, getDefaultProps: function() { @@ -36,6 +40,7 @@ module.exports = React.createClass({displayName: 'PasswordLogin', onPasswordChanged: function() {}, initialUsername: "", 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) { ev.preventDefault(); 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 (
-
- {this._passwordField = e;}} type="password" value={this.state.password} onChange={this.onPasswordChanged} placeholder="Password" />
@@ -89,4 +109,4 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
); } -}); \ No newline at end of file +}); diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index 39c1acc625..33809fbfd6 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -17,8 +17,7 @@ limitations under the License. 'use strict'; var React = require('react'); -var Velocity = require('velocity-vector'); -require('velocity-vector/velocity.ui'); +var UiEffects = require('../../../UiEffects'); var sdk = require('../../../index'); var Email = require('../../../email'); var Modal = require("../../../Modal"); @@ -117,7 +116,7 @@ module.exports = React.createClass({ promise.finally(function() { ev.target.disabled = false; }); - } + } }, /** @@ -196,7 +195,7 @@ module.exports = React.createClass({ fieldValid[field_id] = val; this.setState({fieldValid: fieldValid}); if (!val) { - Velocity(this.fieldElementById(field_id), "callout.shake", 300); + UiEffects.field_input_incorrect(this.fieldElementById(field_id)); this.props.onError(error_code); } }, @@ -214,12 +213,13 @@ module.exports = React.createClass({ } }, - _styleField: function(field_id, baseStyle) { - var style = baseStyle || {}; + _classForField: function(field_id, baseClass) { + let cls = baseClass || ''; if (this.state.fieldValid[field_id] === false) { - style['borderColor'] = 'red'; + if (cls) cls += ' '; + cls += 'error'; } - return style; + return cls; }, render: function() { @@ -227,10 +227,10 @@ module.exports = React.createClass({ var emailSection, registerButton; if (this.props.showEmail) { emailSection = ( - ); } @@ -250,22 +250,22 @@ module.exports = React.createClass({ {emailSection}
-
{ this.props.guestUsername ?
Setting a user name will create a fresh account
: null } -
-