Backporting to beta (#4175)
* verification: check if server is running (#4140) * verification: check if server is running See also ethcore/email-verification#67c6466 and ethcore/sms-verification#a585e42. * verification: show in the UI if server is running * verification: code style ✨, more i18n * fix i18n key * Optimized hash lookups (#4144) * Optimize hash comparison * Use libc * Ropsten fork detection (#4163) * Stop flickering + added loader in AddressSelector (#4149) * Stop UI flickering + added loader to AddressSelector #4103 * PR Grumbles * Add a password strength component (#4153) * Added new PasswordStrength Component * Added tests * PR Grumbles * icarus -> update, increase web timeout. (#4165) * icarus -> update, increase web timeout. * Fix estimate gas * Fix token images // Error in Contract Queries (#4169) * Fix dapps not loading (#4170) * Add secure to dappsreg * Remove trailing slash // fix dapps
This commit is contained in:
committed by
Gav Wood
parent
5b30a61158
commit
65594b8865
@@ -27,6 +27,7 @@
|
||||
|
||||
transition: transform ease-out 0.1s;
|
||||
transform: scale(1);
|
||||
overflow: hidden;
|
||||
|
||||
&.copied {
|
||||
animation-duration: 0.25s;
|
||||
|
||||
@@ -62,11 +62,15 @@ class Balance extends Component {
|
||||
value = api.util.fromWei(balance.value).toFormat(3);
|
||||
}
|
||||
|
||||
let imagesrc = token.image;
|
||||
if (!imagesrc) {
|
||||
imagesrc = images[token.address]
|
||||
? `${api.dappsUrl}${images[token.address]}`
|
||||
: unknownImage;
|
||||
const imageurl = token.image || images[token.address];
|
||||
let imagesrc = unknownImage;
|
||||
|
||||
if (imageurl) {
|
||||
const host = /^(\/)?api/.test(imageurl)
|
||||
? api.dappsUrl
|
||||
: '';
|
||||
|
||||
imagesrc = `${host}${imageurl}`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -15,6 +15,22 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.outerInput {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.loader {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
box-sizing: border-box;
|
||||
appearance: textfield;
|
||||
@@ -58,13 +74,13 @@
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 1rem 2.5rem 0.25em;
|
||||
margin: 1rem 0.5rem 0.25em;
|
||||
color: rgba(255, 255, 255, 0.498039);
|
||||
}
|
||||
|
||||
.underline {
|
||||
position: relative;
|
||||
margin: 0 9rem 0 2.5rem;
|
||||
margin: 0 0.5rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@@ -78,7 +94,7 @@
|
||||
|
||||
.input {
|
||||
font-size: 1.5em;
|
||||
padding: 0 9rem 0.5em 2.5rem;
|
||||
padding: 0 9rem 0.5em 0.5rem;
|
||||
display: block;
|
||||
|
||||
padding-right: 6rem;
|
||||
@@ -92,7 +108,7 @@
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
margin: 2rem 2rem 0;
|
||||
margin: 2rem 0 0;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
@@ -107,8 +123,11 @@
|
||||
|
||||
.title {
|
||||
text-transform: uppercase;
|
||||
font-size: 1.5em;
|
||||
font-color: white;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cards {
|
||||
|
||||
@@ -25,6 +25,7 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
|
||||
|
||||
import AccountCard from '~/ui/AccountCard';
|
||||
import InputAddress from '~/ui/Form/InputAddress';
|
||||
import Loading from '~/ui/Loading';
|
||||
import Portal from '~/ui/Portal';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
import { validateAddress } from '~/util/validation';
|
||||
@@ -130,7 +131,7 @@ class AddressSelect extends Component {
|
||||
const input = (
|
||||
<InputAddress
|
||||
accountsInfo={ accountsInfo }
|
||||
allowCopy={ allowCopy }
|
||||
allowCopy={ (disabled || readOnly) && allowCopy ? allowCopy : false }
|
||||
className={ className }
|
||||
disabled={ disabled || readOnly }
|
||||
error={ error }
|
||||
@@ -182,17 +183,18 @@ class AddressSelect extends Component {
|
||||
<label className={ styles.label } htmlFor={ id }>
|
||||
{ label }
|
||||
</label>
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ ilHint }
|
||||
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
onChange={ this.handleChange }
|
||||
|
||||
ref={ this.setInputRef }
|
||||
/>
|
||||
<div className={ styles.outerInput }>
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ ilHint }
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
onChange={ this.handleChange }
|
||||
ref={ this.setInputRef }
|
||||
/>
|
||||
{ this.renderLoader() }
|
||||
</div>
|
||||
|
||||
<div className={ styles.underline }>
|
||||
<TextFieldUnderline
|
||||
@@ -210,6 +212,19 @@ class AddressSelect extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderLoader () {
|
||||
if (!this.store.loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Loading
|
||||
className={ styles.loader }
|
||||
size={ 0.5 }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderCurrentInput () {
|
||||
const { inputValue } = this.state;
|
||||
|
||||
@@ -304,7 +319,9 @@ class AddressSelect extends Component {
|
||||
|
||||
return (
|
||||
<div className={ styles.category } key={ `${key}_${index}` }>
|
||||
<div className={ styles.title }>{ label }</div>
|
||||
<div className={ styles.title }>
|
||||
<h3>{ label }</h3>
|
||||
</div>
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from 'react';
|
||||
import { observable, action } from 'mobx';
|
||||
import { observable, action, transaction } from 'mobx';
|
||||
import { flatMap, uniqBy } from 'lodash';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -26,6 +26,7 @@ const ZERO = /^(0x)?0*$/;
|
||||
|
||||
export default class AddressSelectStore {
|
||||
|
||||
@observable loading = false;
|
||||
@observable values = [];
|
||||
@observable registryValues = [];
|
||||
|
||||
@@ -224,21 +225,28 @@ export default class AddressSelectStore {
|
||||
};
|
||||
});
|
||||
|
||||
// Registries Lookup
|
||||
this.registryValues = [];
|
||||
// Clear the previous results after 50ms
|
||||
// if still fetching
|
||||
const timeoutId = setTimeout(() => {
|
||||
transaction(() => {
|
||||
this.registryValues = [];
|
||||
this.loading = true;
|
||||
});
|
||||
}, 50);
|
||||
|
||||
const lookups = this.regLookups.map((regLookup) => regLookup(value));
|
||||
|
||||
Promise
|
||||
// Registries Lookup
|
||||
return Promise
|
||||
.all(lookups)
|
||||
.then((results) => {
|
||||
return results
|
||||
.filter((result) => result && !ZERO.test(result.address));
|
||||
})
|
||||
.then((results) => {
|
||||
results = uniqBy(results, (result) => result.address);
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
this.registryValues = results
|
||||
const registryValues = uniqBy(results, (result) => result.address)
|
||||
.map((result) => {
|
||||
const lowercaseAddress = result.address.toLowerCase();
|
||||
|
||||
@@ -253,6 +261,11 @@ export default class AddressSelectStore {
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
transaction(() => {
|
||||
this.loading = false;
|
||||
this.registryValues = registryValues;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class InputAddress extends Component {
|
||||
|
||||
const props = {};
|
||||
|
||||
if (!readOnly && !disabled) {
|
||||
if (!disabled) {
|
||||
props.focused = focused;
|
||||
}
|
||||
|
||||
|
||||
17
js/src/ui/Form/PasswordStrength/index.js
Normal file
17
js/src/ui/Form/PasswordStrength/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './passwordStrength';
|
||||
31
js/src/ui/Form/PasswordStrength/passwordStrength.css
Normal file
31
js/src/ui/Form/PasswordStrength/passwordStrength.css
Normal file
@@ -0,0 +1,31 @@
|
||||
/* Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
/* This file is part of Parity.
|
||||
/*
|
||||
/* Parity is free software: you can redistribute it and/or modify
|
||||
/* it under the terms of the GNU General Public License as published by
|
||||
/* the Free Software Foundation, either version 3 of the License, or
|
||||
/* (at your option) any later version.
|
||||
/*
|
||||
/* Parity is distributed in the hope that it will be useful,
|
||||
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
/* GNU General Public License for more details.
|
||||
/*
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.strength {
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
.feedback {
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.label {
|
||||
user-select: none;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.498039);
|
||||
}
|
||||
125
js/src/ui/Form/PasswordStrength/passwordStrength.js
Normal file
125
js/src/ui/Form/PasswordStrength/passwordStrength.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { debounce } from 'lodash';
|
||||
import { LinearProgress } from 'material-ui';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import zxcvbn from 'zxcvbn';
|
||||
|
||||
import styles from './passwordStrength.css';
|
||||
|
||||
const BAR_STYLE = {
|
||||
borderRadius: 1,
|
||||
height: 7,
|
||||
marginTop: '0.5em'
|
||||
};
|
||||
|
||||
export default class PasswordStrength extends Component {
|
||||
static propTypes = {
|
||||
input: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
strength: null
|
||||
};
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.updateStrength = debounce(this._updateStrength, 50, { leading: true });
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.updateStrength(this.props.input);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.input !== this.props.input) {
|
||||
this.updateStrength(nextProps.input);
|
||||
}
|
||||
}
|
||||
|
||||
_updateStrength (input = '') {
|
||||
const strength = zxcvbn(input);
|
||||
this.setState({ strength });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { strength } = this.state;
|
||||
|
||||
if (!strength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { score, feedback } = strength;
|
||||
|
||||
// Score is between 0 and 4
|
||||
const value = score * 100 / 5 + 20;
|
||||
const color = this.getStrengthBarColor(score);
|
||||
|
||||
return (
|
||||
<div className={ styles.strength }>
|
||||
<label className={ styles.label }>
|
||||
<FormattedMessage
|
||||
id='ui.passwordStrength.label'
|
||||
defaultMessage='password strength'
|
||||
/>
|
||||
</label>
|
||||
<LinearProgress
|
||||
color={ color }
|
||||
mode='determinate'
|
||||
style={ BAR_STYLE }
|
||||
value={ value }
|
||||
/>
|
||||
<div className={ styles.feedback }>
|
||||
{ this.renderFeedback(feedback) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Note that the suggestions are in english, thus it wouldn't
|
||||
// make sense to add translations to surrounding words
|
||||
renderFeedback (feedback = {}) {
|
||||
const { suggestions = [] } = feedback;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
{ suggestions.join(' ') }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getStrengthBarColor (score) {
|
||||
switch (score) {
|
||||
case 4:
|
||||
case 3:
|
||||
return 'lightgreen';
|
||||
|
||||
case 2:
|
||||
return 'yellow';
|
||||
|
||||
case 1:
|
||||
return 'orange';
|
||||
|
||||
default:
|
||||
return 'red';
|
||||
}
|
||||
}
|
||||
}
|
||||
62
js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
Normal file
62
js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import PasswordStrength from './passwordStrength';
|
||||
|
||||
const INPUT_A = 'l33t_test';
|
||||
const INPUT_B = 'Fu£dk;s$%kdlaOe9)_';
|
||||
const INPUT_NULL = '';
|
||||
|
||||
function render (props) {
|
||||
return shallow(
|
||||
<PasswordStrength { ...props } />
|
||||
).shallow();
|
||||
}
|
||||
|
||||
describe('ui/Form/PasswordStrength', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders', () => {
|
||||
expect(render({ input: INPUT_A })).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders a linear progress', () => {
|
||||
expect(render({ input: INPUT_A }).find('LinearProgress')).to.be.ok;
|
||||
});
|
||||
|
||||
describe('compute strength', () => {
|
||||
it('has low score with empty input', () => {
|
||||
expect(
|
||||
render({ input: INPUT_NULL }).find('LinearProgress').props().value
|
||||
).to.equal(20);
|
||||
});
|
||||
|
||||
it('has medium score', () => {
|
||||
expect(
|
||||
render({ input: INPUT_A }).find('LinearProgress').props().value
|
||||
).to.equal(60);
|
||||
});
|
||||
|
||||
it('has high score', () => {
|
||||
expect(
|
||||
render({ input: INPUT_B }).find('LinearProgress').props().value
|
||||
).to.equal(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -71,7 +71,9 @@ export default class TypedInput extends Component {
|
||||
const { isEth, value } = this.props;
|
||||
|
||||
if (typeof isEth === 'boolean' && value) {
|
||||
const ethValue = isEth ? fromWei(value) : value;
|
||||
// Remove formatting commas
|
||||
const sanitizedValue = typeof value === 'string' ? value.replace(/,/g, '') : value;
|
||||
const ethValue = isEth ? fromWei(sanitizedValue) : value;
|
||||
this.setState({ isEth, ethValue });
|
||||
}
|
||||
}
|
||||
@@ -393,7 +395,9 @@ export default class TypedInput extends Component {
|
||||
return this.setState({ isEth: !isEth });
|
||||
}
|
||||
|
||||
const value = isEth ? toWei(ethValue) : fromWei(ethValue);
|
||||
// Remove formatting commas
|
||||
const sanitizedValue = typeof ethValue === 'string' ? ethValue.replace(/,/g, '') : ethValue;
|
||||
const value = isEth ? toWei(sanitizedValue) : fromWei(sanitizedValue);
|
||||
this.setState({ isEth: !isEth, ethValue: value }, () => {
|
||||
this.onEthValueChange(null, value);
|
||||
});
|
||||
|
||||
@@ -21,15 +21,22 @@ import styles from './loading.css';
|
||||
|
||||
export default class Loading extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
size: PropTypes.number
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
size: 2
|
||||
};
|
||||
|
||||
render () {
|
||||
const size = (this.props.size || 2) * 60;
|
||||
const { className, size } = this.props;
|
||||
const computedSize = size * 60;
|
||||
|
||||
return (
|
||||
<div className={ styles.loading }>
|
||||
<CircularProgress size={ size } />
|
||||
<div className={ [ styles.loading, className ].join(' ') }>
|
||||
<CircularProgress size={ computedSize } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ $top: 20vh;
|
||||
opacity: 0;
|
||||
z-index: -10;
|
||||
|
||||
padding: 1em;
|
||||
box-sizing: border-box;
|
||||
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -83,6 +86,7 @@ $top: 20vh;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
font-size: 4em;
|
||||
z-index: 100;
|
||||
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.25s;
|
||||
|
||||
@@ -43,6 +43,7 @@ import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal';
|
||||
import muiTheme from './Theme';
|
||||
import Page from './Page';
|
||||
import ParityBackground from './ParityBackground';
|
||||
import PasswordStrength from './Form/PasswordStrength';
|
||||
import ShortenedHash from './ShortenedHash';
|
||||
import SignerIcon from './SignerIcon';
|
||||
import Tags from './Tags';
|
||||
@@ -91,6 +92,7 @@ export {
|
||||
muiTheme,
|
||||
Page,
|
||||
ParityBackground,
|
||||
PasswordStrength,
|
||||
RadioButtons,
|
||||
ShortenedHash,
|
||||
Select,
|
||||
|
||||
Reference in New Issue
Block a user