Improve form validation (#1772)

* Change gas price validation to be string input based

* Change sanitization to use Nunber

* Have validators use Number over parseFloat

* Fix css validation class

* Add valid css to address field

* Add data field validation

* Remove unused import

* Fix button being hidden on inputs

* Dead code removal

* Unify textarea and input class validation

* Adjust validity styling to only apply after a value has been inputted

* Do not pass custom props to DOM
This commit is contained in:
HenryNguyen5 2018-05-13 15:24:50 -04:00 committed by Daniel Ternyak
parent 32416469e4
commit 8d27d0ba4d
52 changed files with 196 additions and 150 deletions

View File

@ -21,7 +21,7 @@ export const AddressField: React.SFC<Props> = ({ isReadOnly, isSelfAddress, isCh
{translate(isSelfAddress ? 'X_ADDRESS' : 'SEND_ADDR')}
</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={isCheckSummed ? toChecksumAddress(currentTo.raw) : currentTo.raw}
placeholder={donationAddressMap.ETH}

View File

@ -23,9 +23,7 @@ export const AmountField: React.SFC<Props> = ({
<label className="AmountField-group input-group input-group-inline">
<div className="input-group-header">{translate('SEND_AMOUNT_SHORT')}</div>
<Input
className={`input-group-input ${
isAmountValid(raw, customValidator, isValid) ? '' : 'invalid'
}`}
isValid={isAmountValid(raw, customValidator, isValid)}
type="number"
placeholder="1"
value={raw}

View File

@ -66,9 +66,8 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
<label className="AddCustom-field form-group" key={field.name}>
<div className="input-group-header">{field.label}</div>
<Input
className={`${
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
} input-group-input-small`}
isValid={!errors[field.name]}
className="input-group-input-small"
type="text"
name={field.name}
value={field.value}

View File

@ -20,7 +20,12 @@ class DetailsClass extends Component<StateProps> {
<div className="tx-modal-details">
<label className="input-group">
<div className="input-group-header">Network</div>
<Input readOnly={true} value={`${network} network - provided by ${service}`} />
<Input
isValid={true}
showValidAsPlain={true}
readOnly={true}
value={`${network} network - provided by ${service}`}
/>
</label>
<SerializedTransaction

View File

@ -117,7 +117,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-9 input-group flex-grow-1">
<div className="input-group-header">{translate('CUSTOM_NODE_NAME')}</div>
<Input
className={`input-group-input ${this.state.name && invalids.name ? 'invalid' : ''}`}
isValid={!(this.state.name && invalids.name)}
type="text"
placeholder="My Node"
value={this.state.name}
@ -142,9 +142,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_NAME')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkId && invalids.customNetworkId ? 'invalid' : ''
}`}
isValid={!(this.state.customNetworkId && invalids.customNetworkId)}
type="text"
placeholder="My Custom Network"
value={this.state.customNetworkId}
@ -154,9 +152,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_CURRENCY')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkUnit && invalids.customNetworkUnit ? 'invalid' : ''
}`}
isValid={!(this.state.customNetworkUnit && invalids.customNetworkUnit)}
type="text"
placeholder="ETH"
value={this.state.customNetworkUnit}
@ -166,11 +162,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_CHAIN_ID')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkChainId && invalids.customNetworkChainId
? 'invalid'
: ''
}`}
isValid={!(this.state.customNetworkChainId && invalids.customNetworkChainId)}
type="text"
placeholder="1"
value={this.state.customNetworkChainId}
@ -183,7 +175,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_URL')}</div>
<Input
className={`input-group-input ${this.state.url && invalids.url ? 'invalid' : ''}`}
isValid={!(this.state.url && invalids.url)}
type="text"
placeholder="https://127.0.0.1:8545/"
value={this.state.url}
@ -207,9 +199,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_USERNAME_LABEL')}</div>
<Input
className={`input-group-input ${
this.state.username && invalids.username ? 'invalid' : ''
}`}
isValid={!(this.state.username && invalids.username)}
type="text"
value={this.state.username}
onChange={e => this.setState({ username: e.currentTarget.value })}
@ -218,9 +208,7 @@ class CustomNodeModal extends React.Component<Props, State> {
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_PASSWORD_LABEL')}</div>
<Input
className={`input-group-input ${
this.state.password && invalids.password ? 'invalid' : ''
}`}
isValid={!(this.state.password && invalids.password)}
type="password"
value={this.state.password}
onChange={e => this.setState({ password: e.currentTarget.value })}

View File

@ -6,12 +6,12 @@ import { Input } from 'components/ui';
export const DataField: React.SFC<{}> = () => (
<DataFieldFactory
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
withProps={({ data: { raw }, validData, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">{translate('OFFLINE_STEP2_LABEL_6')}</div>
<Input
className={dataExists ? 'is-valid' : 'is-invalid'}
isValid={validData}
type="text"
placeholder={donationAddressMap.ETH}
value={raw}

View File

@ -7,7 +7,7 @@ import { isEtherTransaction } from 'selectors/transaction';
import { AppState } from 'reducers';
export interface CallBackProps {
data: AppState['transaction']['fields']['data'];
dataExists: boolean;
validData: boolean;
readOnly: boolean;
onChange(ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>): void;
}

View File

@ -1,9 +1,10 @@
import React, { Component } from 'react';
import { Query } from 'components/renderCbs';
import { getData, getDataExists } from 'selectors/transaction';
import { getData } from 'selectors/transaction';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { CallBackProps } from 'components/DataFieldFactory';
import { isHexString } from 'ethereumjs-util';
interface OwnProps {
withProps(props: CallBackProps): React.ReactElement<any> | null;
@ -11,19 +12,19 @@ interface OwnProps {
}
interface StateProps {
data: AppState['transaction']['fields']['data'];
dataExists: boolean;
validData: boolean;
}
type Props = OwnProps & StateProps;
class DataInputClass extends Component<Props> {
public render() {
const { data, onChange, dataExists } = this.props;
const { data, onChange, validData } = this.props;
return (
<Query
params={['readOnly']}
withQuery={({ readOnly }) =>
this.props.withProps({ data, onChange, readOnly: !!readOnly, dataExists })
this.props.withProps({ data, onChange, readOnly: !!readOnly, validData })
}
/>
);
@ -32,5 +33,5 @@ class DataInputClass extends Component<Props> {
export const DataInput = connect((state: AppState) => ({
data: getData(state),
dataExists: getDataExists(state)
validData: getData(state).raw === '' || isHexString(getData(state).raw)
}))(DataInputClass);

View File

@ -30,7 +30,7 @@ export const GasLimitField: React.SFC<Props> = ({
/>
</div>
<Input
className={gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}
isValid={gasLimitValidator(raw)}
type="number"
placeholder="21000"
readOnly={!!readOnly}

View File

@ -34,7 +34,7 @@
&-refresh {
position: absolute;
right: 0;
bottom: 1rem;
bottom: 0rem;
padding: 0.75rem 1rem;
}
}

View File

@ -42,7 +42,8 @@ class NonceField extends React.Component<Props> {
/>
</div>
<Input
className={`Nonce-field-input ${!!value ? 'is-valid' : 'is-invalid'}`}
isValid={!!value}
className="Nonce-field-input"
type="number"
placeholder="7"
value={raw}

View File

@ -9,7 +9,6 @@ import { NonceField, GasLimitField, DataField } from 'components';
import { connect } from 'react-redux';
import { getAutoGasLimitEnabled } from 'selectors/config';
import { isValidGasPrice } from 'selectors/transaction';
import { sanitizeNumericalInput } from 'libs/values';
import { Input } from 'components/ui';
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
import { getScheduleGasPrice, getTimeBounty } from 'selectors/schedule';
@ -83,9 +82,11 @@ class AdvancedGas extends React.Component<Props, State> {
<div className="input-group-header">
{translateRaw('OFFLINE_STEP2_LABEL_3')} (gwei)
</div>
{/*We leave type as string instead of number, because things such as multiple decimals
or invalid exponent notation does not fire the onchange handler
so the component will not display as invalid for such things */}
<Input
className={!!gasPrice.raw && !validGasPrice ? 'invalid' : ''}
type="number"
isValid={validGasPrice}
placeholder="40"
value={gasPrice.raw}
onChange={this.handleGasPriceChange}
@ -173,7 +174,7 @@ class AdvancedGas extends React.Component<Props, State> {
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.inputGasPrice(sanitizeNumericalInput(value));
this.props.inputGasPrice(value);
};
private handleToggleAutoGasLimit = (_: React.FormEvent<HTMLInputElement>) => {

View File

@ -54,6 +54,9 @@ class FeeSummary extends React.Component<Props> {
scheduleGasLimit
} = this.props;
if (!gasPrice.value || gasPrice.value.eqn(0) || !gasLimit.value || gasLimit.value.eqn(0)) {
return null;
}
if (isGasEstimating) {
return (
<div className="FeeSummary is-loading">

View File

@ -69,7 +69,8 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
<div className={`TogglablePassword input-group input-group-inline`}>
{isTextareaWhenVisible && isVisible ? (
<TextArea
className={`${className} ${!isValid ? 'invalid' : ''}`}
isValid={!!isValid}
className={className}
value={value}
name={name}
disabled={disabled}
@ -84,11 +85,12 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
/>
) : (
<Input
isValid={!!isValid}
value={value}
name={name}
disabled={disabled}
type={isVisible ? 'text' : 'password'}
className={`${className} ${!isValid ? 'invalid' : ''} border-rad-right-0`}
className={`${className} border-rad-right-0`}
placeholder={placeholder}
onChange={onChange}
onFocus={onFocus}

View File

@ -132,7 +132,7 @@ class DeterministicWalletsModalClass extends React.PureComponent<Props, State> {
<React.Fragment>
<div className="DWModal-path-custom">
<Input
className={customPath ? (isValidPath(customPath) ? 'valid' : 'invalid') : ''}
isValid={customPath ? isValidPath(customPath) : true}
value={customPath}
placeholder="m/44'/60'/0'/0"
onChange={this.handleChangeCustomPath}

View File

@ -65,9 +65,8 @@ export class KeystoreDecrypt extends PureComponent {
{isWalletPending ? <Spinner /> : ''}
<Input
className={`${password.length > 0 ? 'is-valid' : 'is-invalid'} ${
file.length && isWalletPending ? 'hidden' : ''
}`}
isValid={password.length > 0}
className={`${file.length && isWalletPending ? 'hidden' : ''}`}
disabled={!file}
value={password}
onChange={this.onPasswordChange}

View File

@ -67,6 +67,8 @@ class MnemonicDecryptClass extends PureComponent<Props, State> {
<div className="form-group">
<p>{translate('ADD_LABEL_8')}</p>
<Input
isValid={true}
showValidAsPlain={true}
value={pass}
onChange={this.onPasswordChange}
placeholder={translateRaw('INPUT_PASSWORD_LABEL')}

View File

@ -74,7 +74,7 @@ export class PrivateKeyDecrypt extends PureComponent<Props> {
<label className="input-group">
<div className="input-group-header">{translate('ADD_LABEL_3')}</div>
<Input
className={`form-control ${password.length > 0 ? 'is-valid' : 'is-invalid'}`}
isValid={password.length > 0}
value={password}
onChange={this.onPasswordChange}
onKeyDown={this.onKeyDown}

View File

@ -60,7 +60,8 @@ class ViewOnlyDecryptClass extends PureComponent<Props, State> {
)}
<Input
className={`ViewOnly-input ${isValid ? 'is-valid' : 'is-invalid'}`}
isValid={isValid}
className="ViewOnly-input"
value={address}
onChange={this.changeAddress}
placeholder={translateRaw('VIEW_ONLY_ENTER')}

View File

@ -1,25 +0,0 @@
import React, { Component } from 'react';
interface RequiredProps {
condition: boolean;
conditionalProps: {
[key: string]: any;
};
}
/**
* Optional
*/
export const withConditional = <WrappedComponentProps extends {}>(
PassedComponent: React.ComponentType<WrappedComponentProps>
) =>
class extends Component<WrappedComponentProps & RequiredProps, {}> {
public render() {
const { condition, conditionalProps, ...passedProps } = this.props as any;
return condition ? (
<PassedComponent {...{ ...passedProps, ...(conditionalProps as object) }} />
) : (
<PassedComponent {...passedProps} />
);
}
};

View File

@ -1 +0,0 @@
export * from './Conditional';

View File

@ -1,6 +0,0 @@
import React from 'react';
import { withConditional } from 'components/hocs';
import { Input } from 'components/ui';
const inpt: React.SFC<React.InputHTMLAttributes<any>> = props => <Input {...props} />;
export const ConditionalInput = withConditional(inpt);

View File

@ -1,33 +1,65 @@
import React, { HTMLProps } from 'react';
import classnames from 'classnames';
import './Input.scss';
interface State {
hasBlurred: boolean;
/**
* @description when the input has not had any values inputted yet
* e.g. "Pristine" condition
*/
isStateless: boolean;
}
class Input extends React.Component<HTMLProps<HTMLInputElement>, State> {
interface OwnProps extends HTMLProps<HTMLInputElement> {
isValid: boolean;
showValidAsPlain?: boolean;
}
class Input extends React.Component<OwnProps, State> {
public state: State = {
hasBlurred: false
hasBlurred: false,
isStateless: true
};
public render() {
const { showValidAsPlain, isValid, ...htmlProps } = this.props;
const hasValue = !!this.props.value && this.props.value.toString().length > 0;
const classname = classnames(
this.props.className,
'input-group-input',
'form-control',
this.state.isStateless
? ''
: isValid ? (showValidAsPlain ? '' : `is-valid valid`) : `is-invalid invalid`,
this.state.hasBlurred && 'has-blurred',
hasValue && 'has-value'
);
return (
<input
{...this.props}
{...htmlProps}
onBlur={e => {
this.setState({ hasBlurred: true });
if (this.props && this.props.onBlur) {
this.props.onBlur(e);
}
}}
onChange={this.handleOnChange}
onWheel={this.props.type === 'number' ? this.preventNumberScroll : undefined}
className={`input-group-input ${this.props.className} ${
this.state.hasBlurred ? 'has-blurred' : ''
} ${!!this.props.value && this.props.value.toString().length > 0 ? 'has-value' : ''}`}
className={classname}
/>
);
}
private handleOnChange = (args: React.FormEvent<HTMLInputElement>) => {
if (this.state.isStateless) {
this.setState({ isStateless: false });
}
if (this.props.onChange) {
this.props.onChange(args);
}
};
// When number inputs are scrolled on while in focus, the number changes. So we blur
// it if it's focused to prevent that behavior, without preventing the scroll.
private preventNumberScroll(ev: React.WheelEvent<HTMLInputElement>) {

View File

@ -11,7 +11,7 @@ export default class SimpleSelect extends PureComponent<Props, {}> {
return (
<select
value={this.props.value || this.props.options[0]}
className={'form-control'}
className="form-control"
onChange={this.props.onChange}
>
{this.props.options.map((obj, i) => {

View File

@ -1,30 +1,62 @@
import React, { HTMLProps } from 'react';
import classnames from 'classnames';
import './Input.scss';
interface State {
hasBlurred: boolean;
/**
* @description when the input has not had any values inputted yet
* e.g. "Pristine" condition
*/
isStateless: boolean;
}
class TextArea extends React.Component<HTMLProps<HTMLTextAreaElement>, State> {
interface OwnProps extends HTMLProps<HTMLTextAreaElement> {
isValid: boolean;
showValidAsPlain?: boolean;
}
class TextArea extends React.Component<OwnProps, State> {
public state: State = {
hasBlurred: false
hasBlurred: false,
isStateless: true
};
public render() {
const { showValidAsPlain, isValid, ...htmlProps } = this.props;
const classname = classnames(
this.props.className,
'input-group-input',
'form-control',
this.state.isStateless
? ''
: isValid ? (showValidAsPlain ? '' : `is-valid valid`) : `is-invalid invalid`,
this.state.hasBlurred && 'has-blurred'
);
return (
<textarea
{...this.props}
{...htmlProps}
onBlur={e => {
this.setState({ hasBlurred: true });
if (this.props && this.props.onBlur) {
this.props.onBlur(e);
}
}}
className={`input-group-input ${this.props.className} ${
this.state.hasBlurred ? 'has-blurred' : ''
}`}
onChange={this.handleOnChange}
className={classname}
/>
);
}
private handleOnChange = (args: React.FormEvent<HTMLTextAreaElement>) => {
if (this.state.isStateless) {
this.setState({ isStateless: false });
}
if (this.props.onChange) {
this.props.onChange(args);
}
};
}
export default TextArea;

View File

@ -17,6 +17,5 @@ export { default as TextArea } from './TextArea';
export { default as Address } from './Address';
export { default as CodeBlock } from './CodeBlock';
export { default as Toggle } from './Toggle';
export * from './ConditionalInput';
export * from './Expandable';
export * from './InlineSpinner';

View File

@ -65,7 +65,7 @@ class BroadcastTx extends Component<Props> {
<Input
type="text"
placeholder="0xf86b0284ee6b2800825208944bbeeb066ed09b7aed07bf39eee0460dfa26152088016345785d8a00008029a03ba7a0cc6d1756cd771f2119cf688b6d4dc9d37096089f0331fe0de0d1cc1254a02f7bcd19854c8d46f8de09e457aec25b127ab4328e1c0d24bfbff8702ee1f474"
className={stateTransaction ? '' : 'invalid'}
isValid={!!stateTransaction}
value={userInput}
onChange={this.handleChange}
/>

View File

@ -42,7 +42,7 @@ class TxHashInput extends React.Component<Props, State> {
public render() {
const { recentTxs } = this.props;
const { hash } = this.state;
const validClass = hash ? (isValidTxHash(hash) ? 'is-valid' : 'is-invalid') : '';
let selectOptions: Option[] = [];
if (recentTxs && recentTxs.length) {
@ -75,8 +75,9 @@ class TxHashInput extends React.Component<Props, State> {
<Input
value={hash}
isValid={hash ? isValidTxHash(hash) : true}
placeholder="0x16e521..."
className={`TxHashInput-field ${validClass}`}
className="TxHashInput-field"
onChange={this.handleChange}
/>

View File

@ -1,5 +1,4 @@
import translate from 'translations';
import classnames from 'classnames';
import { DataFieldFactory } from 'components/DataFieldFactory';
import { SendButtonFactory } from 'components/SendButtonFactory';
import WalletDecrypt, { DISABLE_WALLETS } from 'components/WalletDecrypt';
@ -31,16 +30,15 @@ class DeployClass extends Component<DispatchProps> {
<label className="input-group">
<div className="input-group-header">{translate('CONTRACT_BYTECODE')}</div>
<DataFieldFactory
withProps={({ data: { raw, value }, onChange, readOnly }) => (
withProps={({ data: { raw }, onChange, readOnly, validData }) => (
<TextArea
isValid={validData && !!raw}
name="byteCode"
placeholder="0x8f87a973e..."
rows={6}
onChange={onChange}
disabled={readOnly}
className={classnames('Deploy-field-input', {
'is-valid': value && value.length > 0
})}
className="Deploy-field-input"
value={raw}
/>
)}

View File

@ -1,6 +1,6 @@
import { AmountFieldFactory } from 'components/AmountFieldFactory';
import React from 'react';
import classnames from 'classnames';
import { Input } from 'components/ui';
export const AmountField: React.SFC = () => (
@ -12,11 +12,10 @@ export const AmountField: React.SFC = () => (
<Input
name="value"
value={raw}
isValid={isValid || raw === ''}
onChange={onChange}
readOnly={readOnly}
className={classnames('InteractExplorer-field-input', 'form-control', {
'is-invalid': !(isValid || raw === '')
})}
className="InteractExplorer-field-input"
/>
)}
/>

View File

@ -116,6 +116,7 @@ class InteractExplorerClass extends Component<Props, State> {
<div className="input-group-header">{name + ' ' + type}</div>
<Input
className="InteractExplorer-func-in-input"
isValid={!!(inputs[name] && inputs[name].rawData)}
name={name}
value={(inputs[name] && inputs[name].rawData) || ''}
onChange={this.handleInputChange}
@ -138,7 +139,8 @@ class InteractExplorerClass extends Component<Props, State> {
<label className="input-group">
<div className="input-group-header"> {name + ' ' + type}</div>
<Input
className="InteractExplorer-func-out-input "
className="InteractExplorer-func-out-input"
isValid={!!decodedFieldValue}
value={decodedFieldValue}
disabled={true}
/>

View File

@ -4,7 +4,6 @@ import { getNetworkContracts } from 'selectors/config';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { isValidETHAddress, isValidAbiJson } from 'libs/validators';
import classnames from 'classnames';
import { NetworkContract } from 'types/network';
import { donationAddressMap } from 'config';
import { Input, TextArea, CodeBlock, Dropdown } from 'components/ui';
@ -126,9 +125,8 @@ class InteractForm extends Component<Props, State> {
name="contract_address"
autoComplete="off"
value={currentTo.raw}
className={classnames('InteractForm-address-field-input', {
invalid: !isValid
})}
isValid={isValid}
className="InteractForm-address-field-input"
spellCheck={false}
onChange={onChange}
/>
@ -144,7 +142,8 @@ class InteractForm extends Component<Props, State> {
contract.name === 'Custom' ? (
<TextArea
placeholder={this.abiJsonPlaceholder}
className={`InteractForm-interface-field-input ${validAbiJson ? '' : 'invalid'}`}
isValid={!!validAbiJson}
className="InteractForm-interface-field-input"
onChange={this.handleInput('abiJson')}
value={abiJson}
rows={6}
@ -155,7 +154,8 @@ class InteractForm extends Component<Props, State> {
) : (
<TextArea
placeholder={this.abiJsonPlaceholder}
className={`InteractForm-interface-field-input ${validAbiJson ? '' : 'invalid'}`}
isValid={!!validAbiJson}
className="InteractForm-interface-field-input"
onChange={this.handleInput('abiJson')}
value={abiJson}
rows={6}

View File

@ -37,9 +37,8 @@ class NameInput extends Component<Props, State> {
<label className="input-group input-group-inline ENSInput-name">
<Input
value={domainToCheck}
className={`${
!domainToCheck ? '' : isValidDomain ? '' : 'invalid'
} border-rad-right-0`}
isValid={!!domainToCheck && isValidDomain}
className="border-rad-right-0"
type="text"
placeholder="mycrypto"
onChange={this.onChange}

View File

@ -21,6 +21,8 @@ const PaperWallet: React.SFC<Props> = props => (
<h1 className="GenPaper-title">{translate('GEN_LABEL_5')}</h1>
<Input
value={stripHexPrefix(props.privateKey)}
showValidAsPlain={true}
isValid={true}
aria-label={translateRaw('X_PRIVKEY')}
aria-describedby="x_PrivKeyDesc"
type="text"

View File

@ -61,7 +61,13 @@ export default class MnemonicWord extends React.Component<Props, State> {
{word}
</button>
) : (
<Input className="MnemonicWord-word-input" value={word} readOnly={true} />
<Input
className="MnemonicWord-word-input"
value={word}
readOnly={true}
showValidAsPlain={true}
isValid={true}
/>
)}
</label>
</div>

View File

@ -38,7 +38,7 @@ class ScheduleDepositFieldClass extends Component<Props> {
</span>
</div>
<Input
className={!!scheduleDeposit.raw && !validScheduleDeposit ? 'invalid' : ''}
isValid={scheduleDeposit.raw && validScheduleDeposit}
type="number"
placeholder="0.00001"
value={scheduleDeposit.raw}

View File

@ -34,7 +34,7 @@ class ScheduleGasLimitFieldClass extends React.Component<Props> {
<InlineSpinner active={gasEstimationPending} text="Calculating" />
</div>
<Input
className={!!scheduleGasLimit.raw && !validScheduleGasLimit ? 'invalid' : ''}
isValid={scheduleGasLimit.raw && validScheduleGasLimit}
type="number"
placeholder={EAC_SCHEDULING_CONFIG.SCHEDULE_GAS_LIMIT_FALLBACK.toString()}
value={scheduleGasLimit.raw}

View File

@ -27,7 +27,7 @@ class ScheduleGasPriceFieldClass extends React.Component<Props> {
<label className="input-group">
<div className="input-group-header">{translateRaw('SCHEDULE_GAS_PRICE')} (gwei)</div>
<Input
className={!!scheduleGasPrice.raw && !validScheduleGasPrice ? 'invalid' : ''}
isValid={scheduleGasPrice.raw && validScheduleGasPrice}
type="number"
placeholder="40"
value={scheduleGasPrice.raw}

View File

@ -25,7 +25,7 @@ export const TimeBountyField: React.SFC<Props> = ({ isReadOnly }) => (
</span>
</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={currentTimeBounty.raw}
placeholder={translateRaw('SCHEDULE_TIMEBOUNTY_PLACEHOLDER')}

View File

@ -32,7 +32,7 @@ export const WindowSizeField: React.SFC<Props> = ({ isReadOnly }) => (
</span>
</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={currentWindowSize.raw}
placeholder={

View File

@ -14,7 +14,7 @@ export const WindowStartField: React.SFC<Props> = ({ isReadOnly }) => (
<label className="input-group">
<div className="input-group-header">{translate('SCHEDULE_BLOCK')}</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
isValid={isValid}
type="text"
value={currentWindowStart.raw}
placeholder={translateRaw('SCHEDULE_BLOCK_PLACEHOLDER')}

View File

@ -57,7 +57,8 @@ export class SignMessage extends Component<Props, State> {
<label className="input-group">
<div className="input-group-header">{translate('MSG_MESSAGE')}</div>
<TextArea
className={`SignMessage-inputBox ${message ? 'is-valid' : 'is-invalid'}`}
isValid={!!message}
className="SignMessage-inputBox"
placeholder={messagePlaceholder}
value={message}
onChange={this.handleMessageChange}

View File

@ -41,7 +41,8 @@ export class VerifyMessage extends Component<Props, State> {
<label className="input-group">
<div className="input-group-header">{translate('MSG_SIGNATURE')}</div>
<TextArea
className={`VerifyMessage-inputBox ${signature ? 'is-valid' : 'is-invalid'}`}
isValid={!!signature}
className="VerifyMessage-inputBox"
placeholder={signaturePlaceholder}
value={signature}
onChange={this.handleSignatureChange}

View File

@ -316,12 +316,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
<div className="input-group input-group-inline">
<Input
id="origin-swap-input"
className={`input-group-input ${
!!origin.amount &&
this.isMinMaxValid(origin.amount, origin.label, destination.label)
? ''
: 'invalid'
}`}
isValid={this.isMinMaxValid(origin.amount, origin.label, destination.label)}
type="number"
placeholder={translateRaw('SEND_AMOUNT_SHORT')}
value={isNaN(origin.amount) ? '' : origin.amount}
@ -341,12 +336,7 @@ export default class CurrencySwap extends PureComponent<Props, State> {
<div className="input-group-header">{translate('SWAP_RECEIVE_INPUT_LABEL')}</div>
<Input
id="destination-swap-input"
className={`${
!!destination.amount &&
this.isMinMaxValid(origin.amount, origin.label, destination.label)
? ''
: 'invalid'
}`}
isValid={this.isMinMaxValid(origin.amount, origin.label, destination.label)}
type="number"
placeholder={translateRaw('SEND_AMOUNT_SHORT')}
value={isNaN(destination.amount) ? '' : destination.amount}

View File

@ -37,7 +37,12 @@ class FieldsClass extends Component<Props> {
<div className="col-xs-12">
<AddressFieldFactory
withProps={({ currentTo }) => (
<Input type="text" value={currentTo.raw} readOnly={true} />
<Input
type="text"
value={currentTo.raw}
readOnly={true}
isValid={!!currentTo.raw}
/>
)}
/>
</div>
@ -60,6 +65,8 @@ class FieldsClass extends Component<Props> {
)}
{isValid && (
<Input
isValid={true}
showValidAsPlain={true}
type="text"
value={`${currentValue.raw} ${this.props.unit}`}
readOnly={true}

View File

@ -21,6 +21,7 @@ export default class PaymentInfo extends PureComponent<Props, {}> {
})}
<Input
className="SwapPayment-address"
isValid={!!this.props.paymentAddress}
value={this.props.paymentAddress || undefined}
disabled={true}
/>

View File

@ -79,7 +79,8 @@ export default class ReceivingAddress extends PureComponent<StateProps & ActionP
</h4>
<Input
className={`SwapAddress-address-input ${!validAddress ? 'invalid' : ''}`}
isValid={validAddress}
className="SwapAddress-address-input"
type="text"
value={destinationAddress}
onChange={this.onChangeDestinationAddress}

View File

@ -83,7 +83,13 @@ Rate: ${rates[pair].rate} ${origin.label}/${destination.label}`;
<small>{translate('SWAP_SUPPORT_LINK_BROKEN')}</small>
</p>
{open ? (
<TextArea defaultValue={fallbackBody} className="form-control input-sm" rows={9} />
<TextArea
isValid={true}
showValidAsPlain={true}
defaultValue={fallbackBody}
className="form-control input-sm"
rows={9}
/>
) : null}
</div>
</section>

View File

@ -140,10 +140,10 @@ export function isValidPath(dPath: string) {
}
export const isValidValue = (value: string) =>
!!(value && isFinite(parseFloat(value)) && parseFloat(value) >= 0);
!!(value && isFinite(Number(value)) && Number(value) >= 0);
export const gasLimitValidator = (gasLimit: number | string) => {
const gasLimitFloat = typeof gasLimit === 'string' ? parseFloat(gasLimit) : gasLimit;
const gasLimitFloat = typeof gasLimit === 'string' ? Number(gasLimit) : gasLimit;
return (
validNumber(gasLimitFloat) &&
gasLimitFloat >= GAS_LIMIT_LOWER_BOUND &&
@ -152,7 +152,7 @@ export const gasLimitValidator = (gasLimit: number | string) => {
};
export const gasPriceValidator = (gasPrice: number | string): boolean => {
const gasPriceFloat = typeof gasPrice === 'string' ? parseFloat(gasPrice) : gasPrice;
const gasPriceFloat = typeof gasPrice === 'string' ? Number(gasPrice) : gasPrice;
return (
validNumber(gasPriceFloat) &&
gasPriceFloat >= GAS_PRICE_GWEI_LOWER_BOUND &&
@ -172,7 +172,7 @@ export const timeBountyValidator = (timeBounty: BN | number | string | null): bo
);
}
const timeBountyFloat = typeof timeBounty === 'string' ? parseFloat(timeBounty) : timeBounty;
const timeBountyFloat = typeof timeBounty === 'string' ? Number(timeBounty) : timeBounty;
return validNumber(timeBountyFloat);
};

View File

@ -44,7 +44,7 @@ export const buildEIP681TokenRequest = (
}`;
export const sanitizeNumericalInput = (input: string): string => {
const inputFloat = parseFloat(input);
const inputFloat = Number(input);
if (!input || isNaN(inputFloat)) {
return input;

View File

@ -31,12 +31,12 @@ export function* handleGasLimitInput({ payload }: InputGasLimitAction): SagaIter
}
export function* handleGasPriceInput({ payload }: InputGasPriceAction): SagaIterator {
const priceFloat = parseFloat(payload);
const validGasPrice: boolean = yield call(gasPriceValidator, priceFloat);
const gasPrice = Number(payload);
const validGasPrice: boolean = yield call(gasPriceValidator, gasPrice);
yield put(
setGasPriceField({
raw: payload,
value: validGasPrice ? gasPriceToBase(priceFloat) : Wei('0')
value: validGasPrice ? gasPriceToBase(gasPrice) : Wei('0')
})
);
}

View File

@ -6,6 +6,7 @@
padding: 0;
background: none;
border: none;
z-index: 999;
cursor: pointer;
}