From de08c041589ebf5680fca9b215a4309c17baf1cc Mon Sep 17 00:00:00 2001 From: Alex Liebowitz <alex@alexliebowitz.com> Date: Fri, 1 Sep 2017 02:14:38 -0400 Subject: [PATCH] Add regexp option to FormField Still needs logic to notify the form when there's invalid input Add address validation to Send page Trim address input on Send page Adds trim prop to FormField Improve LBRY address regexp On Send page, don't prevent form submit, and only show error on blur This isn't a full fix (it only handles blur, it's still the form's job to do validation on submit). A proper solution to this (that can generalize to other forms) will probably involve looking at all of the inputs and asking each one whether it has an error, which will require some tricky state management. On Send page, use error message from daemon Update CHANGELOG.md Further improve invalid address regexp - Remove incorrect check for second character - Add "O" to chars excluded from b58 section - Allow for 33-character addresses Don't internationalize daemon error message on Send page remove console, add i18n --- CHANGELOG.md | 2 +- ui/js/component/formField/view.jsx | 33 ++++++++++++++++++++++++++++- ui/js/component/walletSend/index.js | 2 ++ ui/js/component/walletSend/view.jsx | 6 +++++- ui/js/lbryuri.js | 1 + ui/js/selectors/wallet.js | 5 +++++ 6 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a94afa13..1fa1b399b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ### Fixed * Some CSS changes to prevent the card row from clipping the scroll arrows after the window width is reduced below a certain point - * + * Clearly notify user when they try to send credits to an invalid address ### Deprecated * diff --git a/ui/js/component/formField/view.jsx b/ui/js/component/formField/view.jsx index 2473eaf8d..224b607ec 100644 --- a/ui/js/component/formField/view.jsx +++ b/ui/js/component/formField/view.jsx @@ -12,6 +12,15 @@ class FormField extends React.PureComponent { prefix: React.PropTypes.string, postfix: React.PropTypes.string, hasError: React.PropTypes.bool, + trim: React.PropTypes.bool, + regexp: React.PropTypes.oneOfType([ + React.PropTypes.instanceOf(RegExp), + React.PropTypes.string, + ]), + }; + + static defaultProps = { + trim: false, }; constructor(props) { @@ -77,6 +86,13 @@ class FormField extends React.PureComponent { }); } + clearError() { + this.setState({ + isError: false, + errorMessage: "", + }); + } + focus() { this.refs.field.focus(); } @@ -87,7 +103,9 @@ class FormField extends React.PureComponent { } else if (this.props.type == "SimpleMDE") { return this.refs.field.simplemde.value(); } else { - return this.refs.field.value; + return this.props.trim + ? this.refs.field.value.trim() + : this.refs.field.value; } } @@ -99,6 +117,16 @@ class FormField extends React.PureComponent { return this.refs.field.options; } + validate() { + if ("regexp" in this.props) { + if (!this.getValue().match(this.props.regexp)) { + this.showError(__("Invalid format.")); + } else { + this.clearError(); + } + } + } + render() { // Pass all unhandled props to the field element const otherProps = Object.assign({}, this.props), @@ -116,6 +144,8 @@ class FormField extends React.PureComponent { delete otherProps.postfix; delete otherProps.prefix; delete otherProps.dispatch; + delete otherProps.regexp; + delete otherProps.trim; const element = ( <this._element @@ -124,6 +154,7 @@ class FormField extends React.PureComponent { name={this.props.name} ref="field" placeholder={this.props.placeholder} + onBlur={() => this.validate()} className={ "form-field__input form-field__input-" + this.props.type + diff --git a/ui/js/component/walletSend/index.js b/ui/js/component/walletSend/index.js index ab13440db..57e761c30 100644 --- a/ui/js/component/walletSend/index.js +++ b/ui/js/component/walletSend/index.js @@ -10,6 +10,7 @@ import { selectCurrentModal } from "selectors/app"; import { selectDraftTransactionAmount, selectDraftTransactionAddress, + selectDraftTransactionError, } from "selectors/wallet"; import WalletSend from "./view"; @@ -18,6 +19,7 @@ const select = state => ({ modal: selectCurrentModal(state), address: selectDraftTransactionAddress(state), amount: selectDraftTransactionAmount(state), + error: selectDraftTransactionError(state), }); const perform = dispatch => ({ diff --git a/ui/js/component/walletSend/view.jsx b/ui/js/component/walletSend/view.jsx index 680106584..2751c42ab 100644 --- a/ui/js/component/walletSend/view.jsx +++ b/ui/js/component/walletSend/view.jsx @@ -2,6 +2,7 @@ import React from "react"; import Link from "component/link"; import Modal from "modal/modal"; import { FormRow } from "component/form"; +import lbryuri from "lbryuri"; const WalletSend = props => { const { @@ -12,6 +13,7 @@ const WalletSend = props => { setAddress, amount, address, + error, } = props; return ( @@ -41,6 +43,8 @@ const WalletSend = props => { size="60" onChange={setAddress} value={address} + regexp={lbryuri.REGEXP_ADDRESS} + trim={true} /> <div className="form-row-submit"> <Link @@ -77,7 +81,7 @@ const WalletSend = props => { contentLabel={__("Transaction failed")} onConfirmed={closeModal} > - {__("Something went wrong")}: + {error} </Modal>} </section> ); diff --git a/ui/js/lbryuri.js b/ui/js/lbryuri.js index a7f43890a..922cbfd33 100644 --- a/ui/js/lbryuri.js +++ b/ui/js/lbryuri.js @@ -4,6 +4,7 @@ const CLAIM_ID_MAX_LEN = 40; const lbryuri = {}; lbryuri.REGEXP_INVALID_URI = /[^A-Za-z0-9-]/g; +lbryuri.REGEXP_ADDRESS = /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/; /** * Parses a LBRY name into its component parts. Throws errors with user-friendly diff --git a/ui/js/selectors/wallet.js b/ui/js/selectors/wallet.js index bf8fcb61b..de7fd337b 100644 --- a/ui/js/selectors/wallet.js +++ b/ui/js/selectors/wallet.js @@ -82,6 +82,11 @@ export const selectDraftTransactionAddress = createSelector( draft => draft.address ); +export const selectDraftTransactionError = createSelector( + selectDraftTransaction, + draft => draft.error +); + export const selectBlocks = createSelector(_selectState, state => state.blocks); export const makeSelectBlockDate = block => {