Import AddresBook from exported JSON (#3433)

* Adds error handler in Importer // Import AddressBook #2885

* More robust import #2885
This commit is contained in:
Jaco Greeff 2016-11-15 09:24:59 +01:00 committed by GitHub
commit 604be3e463
4 changed files with 151 additions and 20 deletions

View File

@ -29,6 +29,8 @@ const initialState = {
show: false, show: false,
validate: false, validate: false,
validationBody: null, validationBody: null,
error: false,
errorText: '',
content: '' content: ''
}; };
@ -65,7 +67,7 @@ export default class ActionbarImport extends Component {
renderModal () { renderModal () {
const { title, renderValidation } = this.props; const { title, renderValidation } = this.props;
const { show, step } = this.state; const { show, step, error } = this.state;
if (!show) { if (!show) {
return null; return null;
@ -73,7 +75,7 @@ export default class ActionbarImport extends Component {
const hasSteps = typeof renderValidation === 'function'; const hasSteps = typeof renderValidation === 'function';
const steps = hasSteps ? [ 'select a file', 'validate' ] : null; const steps = hasSteps ? [ 'select a file', error ? 'error' : 'validate' ] : null;
return ( return (
<Modal <Modal
@ -89,7 +91,7 @@ export default class ActionbarImport extends Component {
} }
renderActions () { renderActions () {
const { validate } = this.state; const { validate, error } = this.state;
const cancelBtn = ( const cancelBtn = (
<Button <Button
@ -100,6 +102,10 @@ export default class ActionbarImport extends Component {
/> />
); );
if (error) {
return [ cancelBtn ];
}
if (validate) { if (validate) {
const confirmBtn = ( const confirmBtn = (
<Button <Button
@ -117,7 +123,15 @@ export default class ActionbarImport extends Component {
} }
renderBody () { renderBody () {
const { validate } = this.state; const { validate, errorText, error } = this.state;
if (error) {
return (
<div>
<p>An error occured: { errorText }</p>
</div>
);
}
if (validate) { if (validate) {
return this.renderValidation(); return this.renderValidation();
@ -169,10 +183,20 @@ export default class ActionbarImport extends Component {
return this.onCloseModal(); return this.onCloseModal();
} }
const validationBody = renderValidation(content);
if (validationBody && validationBody.error) {
return this.setState({
step: 1,
error: true,
errorText: validationBody.error
});
}
this.setState({ this.setState({
step: 1, step: 1,
validate: true, validate: true,
validationBody: renderValidation(content), validationBody,
content content
}); });
}; };

View File

@ -28,11 +28,12 @@ class IdentityName extends Component {
tokens: PropTypes.object, tokens: PropTypes.object,
empty: PropTypes.bool, empty: PropTypes.bool,
shorten: PropTypes.bool, shorten: PropTypes.bool,
unknown: PropTypes.bool unknown: PropTypes.bool,
name: PropTypes.string
} }
render () { render () {
const { address, accountsInfo, tokens, empty, shorten, unknown, className } = this.props; const { address, accountsInfo, tokens, empty, name, shorten, unknown, className } = this.props;
const account = accountsInfo[address] || tokens[address]; const account = accountsInfo[address] || tokens[address];
const hasAccount = account && (!account.meta || !account.meta.deleted); const hasAccount = account && (!account.meta || !account.meta.deleted);
@ -43,13 +44,14 @@ class IdentityName extends Component {
const addressFallback = shorten ? this.formatHash(address) : address; const addressFallback = shorten ? this.formatHash(address) : address;
const fallback = unknown ? defaultName : addressFallback; const fallback = unknown ? defaultName : addressFallback;
const isUuid = hasAccount && account.name === account.uuid; const isUuid = hasAccount && account.name === account.uuid;
const name = hasAccount && !isUuid const displayName = (name && name.toUpperCase().trim()) ||
(hasAccount && !isUuid
? account.name.toUpperCase().trim() ? account.name.toUpperCase().trim()
: fallback; : fallback);
return ( return (
<span className={ className }> <span className={ className }>
{ name && name.length ? name : fallback } { displayName && displayName.length ? displayName : fallback }
</span> </span>
); );
} }

View File

@ -22,22 +22,28 @@ import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, I
export default class Summary extends Component { export default class Summary extends Component {
static contextTypes = { static contextTypes = {
api: React.PropTypes.object api: React.PropTypes.object
} };
static propTypes = { static propTypes = {
account: PropTypes.object.isRequired, account: PropTypes.object.isRequired,
balance: PropTypes.object.isRequired, balance: PropTypes.object,
link: PropTypes.string, link: PropTypes.string,
name: PropTypes.string,
noLink: PropTypes.bool,
children: PropTypes.node, children: PropTypes.node,
handleAddSearchToken: PropTypes.func handleAddSearchToken: PropTypes.func
} };
static defaultProps = {
noLink: false
};
state = { state = {
name: 'Unnamed' name: 'Unnamed'
} };
render () { render () {
const { account, balance, children, link, handleAddSearchToken } = this.props; const { account, children, handleAddSearchToken } = this.props;
const { tags } = account.meta; const { tags } = account.meta;
if (!account) { if (!account) {
@ -45,7 +51,6 @@ export default class Summary extends Component {
} }
const { address } = account; const { address } = account;
const viewLink = `/${link || 'account'}/${address}`;
const addressComponent = ( const addressComponent = (
<Input <Input
@ -62,12 +67,45 @@ export default class Summary extends Component {
<IdentityIcon <IdentityIcon
address={ address } /> address={ address } />
<ContainerTitle <ContainerTitle
title={ <Link to={ viewLink }>{ <IdentityName address={ address } unknown /> }</Link> } title={ this.renderLink() }
byline={ addressComponent } /> byline={ addressComponent } />
<Balance
balance={ balance } /> { this.renderBalance() }
{ children } { children }
</Container> </Container>
); );
} }
renderLink () {
const { link, noLink, account, name } = this.props;
const { address } = account;
const viewLink = `/${link || 'account'}/${address}`;
const content = (
<IdentityName address={ address } name={ name } unknown />
);
if (noLink) {
return content;
}
return (
<Link to={ viewLink }>
{ content }
</Link>
);
}
renderBalance () {
const { balance } = this.props;
if (!balance) {
return null;
}
return (
<Balance balance={ balance } />
);
}
} }

View File

@ -21,8 +21,9 @@ import ContentAdd from 'material-ui/svg-icons/content/add';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import List from '../Accounts/List'; import List from '../Accounts/List';
import Summary from '../Accounts/Summary';
import { AddAddress } from '../../modals'; import { AddAddress } from '../../modals';
import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui'; import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui';
import styles from './addresses.css'; import styles from './addresses.css';
@ -107,6 +108,12 @@ class Addresses extends Component {
content={ contacts } content={ contacts }
filename='addressbook.json' />, filename='addressbook.json' />,
<ActionbarImport
key='importAddressbook'
onConfirm={ this.onImport }
renderValidation={ this.renderValidation }
/>,
this.renderSearchButton(), this.renderSearchButton(),
this.renderSortButton() this.renderSortButton()
]; ];
@ -134,6 +141,66 @@ class Addresses extends Component {
); );
} }
renderValidation = (content) => {
const error = {
error: 'The provided file is invalid...'
};
try {
const addresses = JSON.parse(content);
if (!addresses || Object.keys(addresses).length === 0) {
return error;
}
const body = Object
.values(addresses)
.filter((account) => account && account.address)
.map((account, index) => (
<Summary
key={ index }
account={ account }
name={ account.name }
noLink
/>
));
return (
<div>
{ body }
</div>
);
} catch (e) { return error; }
}
onImport = (content) => {
try {
const addresses = JSON.parse(content);
Object.values(addresses).forEach((account) => {
this.onAddAccount(account);
});
} catch (e) {
console.error('onImport', content, e);
}
}
onAddAccount = (account) => {
const { api } = this.context;
const { address, name, meta } = account;
Promise.all([
api.parity.setAccountName(address, name),
api.parity.setAccountMeta(address, {
...meta,
timestamp: Date.now(),
deleted: false
})
]).catch((error) => {
console.error('onAddAccount', error);
});
}
onAddSearchToken = (token) => { onAddSearchToken = (token) => {
const { searchTokens } = this.state; const { searchTokens } = this.state;
const newSearchTokens = uniq([].concat(searchTokens, token)); const newSearchTokens = uniq([].concat(searchTokens, token));