Merge pull request #388 from matrix-org/dbkr/refactor_field_errors
Refactor UI error effectspull/21833/head
						commit
						e8dbf978c3
					
				|  | @ -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 { | ||||
|             busy: false, | ||||
|             errorText: null, | ||||
|             loginIncorrect: false, | ||||
|             enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl, | ||||
|             enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl, | ||||
| 
 | ||||
|  | @ -68,13 +69,15 @@ module.exports = React.createClass({displayName: 'Login', | |||
|     onPasswordLogin: function(username, password) { | ||||
|         var self = this; | ||||
|         self.setState({ | ||||
|             busy: true | ||||
|             busy: true, | ||||
|             errorText: null, | ||||
|             loginIncorrect: false, | ||||
|         }); | ||||
| 
 | ||||
|         this._loginLogic.loginViaPassword(username, password).then(function(data) { | ||||
|             self.props.onLoggedIn(data); | ||||
|         }, function(error) { | ||||
|             self._setErrorTextFromError(error); | ||||
|             self._setStateFromError(error, true); | ||||
|         }).finally(function() { | ||||
|             self.setState({ | ||||
|                 busy: false | ||||
|  | @ -100,7 +103,7 @@ module.exports = React.createClass({displayName: 'Login', | |||
|         this.setState({ | ||||
|             enteredIdentityServerUrl: newIsUrl | ||||
|         }, 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).
 | ||||
|             loginLogic.chooseFlow(0); | ||||
|         }, function(err) { | ||||
|             self._setErrorTextFromError(err); | ||||
|             self._setStateFromError(err, false); | ||||
|         }).finally(function() { | ||||
|             self.setState({ | ||||
|                 busy: false | ||||
|  | @ -131,7 +134,8 @@ module.exports = React.createClass({displayName: 'Login', | |||
|             enteredHomeserverUrl: hsUrl, | ||||
|             enteredIdentityServerUrl: isUrl, | ||||
|             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 | ||||
|     }, | ||||
| 
 | ||||
|     _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) { | ||||
|             this.setState({ | ||||
|                 errorText: err.friendlyText | ||||
|             }); | ||||
|             return; | ||||
|             return err.friendlyText; | ||||
|         } | ||||
| 
 | ||||
|         var errCode = err.errcode; | ||||
|         let errCode = err.errcode; | ||||
|         if (!errCode && 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 + ")" : "") | ||||
| 
 | ||||
|         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 = <span> | ||||
|  | @ -173,9 +182,7 @@ module.exports = React.createClass({displayName: 'Login', | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.setState({ | ||||
|             errorText: errorText | ||||
|         }); | ||||
|         return errorText; | ||||
|     }, | ||||
| 
 | ||||
|     componentForStep: function(step) { | ||||
|  | @ -186,7 +193,9 @@ module.exports = React.createClass({displayName: 'Login', | |||
|                         onSubmit={this.onPasswordLogin} | ||||
|                         initialUsername={this.state.username} | ||||
|                         onUsernameChanged={this.onUsernameChanged} | ||||
|                         onForgotPasswordClick={this.props.onForgotPasswordClick} /> | ||||
|                         onForgotPasswordClick={this.props.onForgotPasswordClick} | ||||
|                         loginIncorrect={this.state.loginIncorrect} | ||||
|                     /> | ||||
|                 ); | ||||
|             case 'm.login.cas': | ||||
|                 return ( | ||||
|  | @ -221,7 +230,7 @@ module.exports = React.createClass({displayName: 'Login', | |||
| 
 | ||||
|         var returnToAppJsx; | ||||
|         if (this.props.onCancelClick) { | ||||
|             returnToAppJsx =  | ||||
|             returnToAppJsx = | ||||
|                 <a className="mx_Login_create" onClick={this.props.onCancelClick} href="#"> | ||||
|                     Return to app | ||||
|                 </a> | ||||
|  |  | |||
|  | @ -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 ( | ||||
|             <div> | ||||
|                 <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} | ||||
|                     placeholder="Email or user name" autoFocus /> | ||||
|                 <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} | ||||
|                     placeholder="Password" /> | ||||
|                 <br /> | ||||
|  | @ -89,4 +109,4 @@ module.exports = React.createClass({displayName: 'PasswordLogin', | |||
|             </div> | ||||
|         ); | ||||
|     } | ||||
| }); | ||||
| }); | ||||
|  |  | |||
|  | @ -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 = ( | ||||
|                 <input className="mx_Login_field" type="text" ref="email" | ||||
|                 <input type="text" ref="email" | ||||
|                     autoFocus={true} placeholder="Email address (optional)" | ||||
|                     defaultValue={this.props.defaultEmail} | ||||
|                     style={this._styleField(FIELD_EMAIL)} | ||||
|                     className={this._classForField(FIELD_EMAIL, 'mx_Login_field')} | ||||
|                     onBlur={function() {self.validateField(FIELD_EMAIL)}} /> | ||||
|             ); | ||||
|         } | ||||
|  | @ -250,22 +250,22 @@ module.exports = React.createClass({ | |||
|                 <form onSubmit={this.onSubmit}> | ||||
|                     {emailSection} | ||||
|                     <br /> | ||||
|                     <input className="mx_Login_field" type="text" ref="username" | ||||
|                     <input type="text" ref="username" | ||||
|                         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)}} /> | ||||
|                     <br /> | ||||
|                     { this.props.guestUsername ? | ||||
|                         <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" | ||||
|                         style={this._styleField(FIELD_PASSWORD)} | ||||
|                     <input type="password" ref="password" | ||||
|                         className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')} | ||||
|                         onBlur={function() {self.validateField(FIELD_PASSWORD)}} | ||||
|                         placeholder="Password" defaultValue={this.props.defaultPassword} /> | ||||
|                     <br /> | ||||
|                     <input className="mx_Login_field" type="password" ref="passwordConfirm" | ||||
|                     <input type="password" ref="passwordConfirm" | ||||
|                         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)}} | ||||
|                         defaultValue={this.props.defaultPassword} /> | ||||
|                     <br /> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Richard van der Hoff
						Richard van der Hoff