Enhance dialog layouts (round 1) (#4637)

* Added SelectionList component for selections

* Use SelectionList in DappPermisions

* AddDapps uses SelectionList

* Fix AccountCard to consistent height

* Display type icons in creation dialog

* Complimentary colours

* Convert Signer defaults to SelectionList

* Fix Geth import - actually pass addresses through

* Work from addresses returned via RPC

* Display actual addresses imported (not selected)

* Update tests to cover bug fixed

* Prettyfy Geth import

* Description on selection actions

* SelectionList as entry point

* Update failing tests

* Subtle selection border

* Styling updates for account details

* Add ModalBox summary

* AddAddress updated

* Convert VaultAccounts to SelectionList

* Add tests for SectionList component

* Add tests for ModalBox component

* Re-apply stretch fix

* Apply scroll fixes from lates commit in #4621

* Clear name on switch (test in #4652, not pulling in)

* Remove refs (cleanup)
This commit is contained in:
Jaco Greeff 2017-02-24 18:05:39 +01:00 committed by GitHub
parent 570e6f32b0
commit 609e24ef04
25 changed files with 638 additions and 293 deletions

View File

@ -18,8 +18,8 @@ import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Button, Form, Input, InputAddress, Portal } from '~/ui'; import { Button, Form, Input, InputAddress, ModalBox, Portal } from '~/ui';
import { AddIcon, CancelIcon } from '~/ui/Icons'; import { AddIcon, AddressesIcon, CancelIcon } from '~/ui/Icons';
import Store from './store'; import Store from './store';
@ -76,7 +76,6 @@ export default class AddAddress extends Component {
/> />
} }
onClick={ this.onClose } onClick={ this.onClose }
ref='closeButton'
/>, />,
<Button <Button
disabled={ hasError } disabled={ hasError }
@ -89,7 +88,6 @@ export default class AddAddress extends Component {
/> />
} }
onClick={ this.onAdd } onClick={ this.onAdd }
ref='addButton'
/> />
]; ];
} }
@ -98,64 +96,71 @@ export default class AddAddress extends Component {
const { address, addressError, description, name, nameError } = this.store; const { address, addressError, description, name, nameError } = this.store;
return ( return (
<Form> <ModalBox
<InputAddress icon={ <AddressesIcon /> }
allowCopy={ false } summary={
autoFocus <FormattedMessage
disabled={ !!this.props.address } id='addAddress.header'
error={ addressError } defaultMessage='To add a new entry to your addressbook, you need the network address of the account and can supply an optional description. Once added it will reflect in your address book.'
hint={ />
<FormattedMessage }
id='addAddress.input.address.hint' >
defaultMessage='the network address for the entry' <Form>
/> <InputAddress
} allowCopy={ false }
label={ autoFocus
<FormattedMessage disabled={ !!this.props.address }
id='addAddress.input.address.label' error={ addressError }
defaultMessage='network address' hint={
/> <FormattedMessage
} id='addAddress.input.address.hint'
onChange={ this.onEditAddress } defaultMessage='the network address for the entry'
ref='inputAddress' />
value={ address } }
/> label={
<Input <FormattedMessage
error={ nameError } id='addAddress.input.address.label'
hint={ defaultMessage='network address'
<FormattedMessage />
id='addAddress.input.name.hint' }
defaultMessage='a descriptive name for the entry' onChange={ this.onEditAddress }
/> value={ address }
} />
label={ <Input
<FormattedMessage error={ nameError }
id='addAddress.input.name.label' hint={
defaultMessage='address name' <FormattedMessage
/> id='addAddress.input.name.hint'
} defaultMessage='a descriptive name for the entry'
onChange={ this.onEditName } />
ref='inputName' }
value={ name } label={
/> <FormattedMessage
<Input id='addAddress.input.name.label'
hint={ defaultMessage='address name'
<FormattedMessage />
id='addAddress.input.description.hint' }
defaultMessage='an expanded description for the entry' onChange={ this.onEditName }
/> value={ name }
} />
label={ <Input
<FormattedMessage hint={
id='addAddress.input.description.label' <FormattedMessage
defaultMessage='(optional) address description' id='addAddress.input.description.hint'
/> defaultMessage='an expanded description for the entry'
} />
onChange={ this.onEditDescription } }
ref='inputDescription' label={
value={ description } <FormattedMessage
/> id='addAddress.input.description.label'
</Form> defaultMessage='(optional) address description'
/>
}
onChange={ this.onEditDescription }
value={ description }
/>
</Form>
</ModalBox>
); );
} }

View File

@ -18,7 +18,7 @@ import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Form, Input, InputAddress, QrCode } from '~/ui'; import { IdentityIcon, Input, QrCode, Title } from '~/ui';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
@ -29,56 +29,51 @@ export default class AccountDetails extends Component {
} }
render () { render () {
const { address, name } = this.props.store; const { address, description, name } = this.props.store;
return ( return (
<div className={ styles.details }> <div className={ styles.details }>
<p> <div className={ styles.info }>
<FormattedMessage <div className={ styles.account }>
id='createAccount.accountDetails.intro' <div className={ styles.name }>
defaultMessage='Your account has been created with the following details:' <IdentityIcon
/> address={ address }
</p> className={ styles.icon }
<Form className={ styles.infoForm }> center
<Input
allowCopy
hint={
<FormattedMessage
id='createAccount.accountDetails.name.hint'
defaultMessage='a descriptive name for the account'
/> />
} <Title
label={ byline={ description }
<FormattedMessage className={ styles.title }
id='createAccount.accountDetails.name.label' title={ name }
defaultMessage='account name'
/> />
} </div>
readOnly <div className={ styles.description }>
value={ name } <Input
/> readOnly
<InputAddress hideUnderline
disabled hint={
hint={ <FormattedMessage
<FormattedMessage id='createAccount.accountDetails.address.hint'
id='createAccount.accountDetails.address.hint' defaultMessage='the network address for the account'
defaultMessage='the network address for the account' />
}
label={
<FormattedMessage
id='createAccount.accountDetails.address.label'
defaultMessage='address'
/>
}
value={ address }
allowCopy={ address }
/> />
} { this.renderPhrase() }
label={ </div>
<FormattedMessage </div>
id='createAccount.accountDetails.address.label' <QrCode
defaultMessage='address' className={ styles.qr }
/>
}
value={ address } value={ address }
/> />
{ this.renderPhrase() } </div>
</Form>
<QrCode
className={ styles.qr }
value={ address }
/>
</div> </div>
); );
} }

View File

@ -1,23 +0,0 @@
/* Copyright 2015-2017 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/>.
*/
.address {
color: #999;
line-height: 1.618em;
padding-left: 2em;
padding-top: 1em;
}

View File

@ -18,7 +18,10 @@ import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import styles from './accountDetailsGeth.css'; import { SectionList } from '~/ui';
import GethCard from '../GethCard';
import styles from '../createAccount.css';
@observer @observer
export default class AccountDetailsGeth extends Component { export default class AccountDetailsGeth extends Component {
@ -27,33 +30,37 @@ export default class AccountDetailsGeth extends Component {
} }
render () { render () {
const { gethImported } = this.props.store; const { gethAccountsAvailable, gethImported } = this.props.store;
const accounts = gethAccountsAvailable.filter((account) => gethImported.includes(account.address));
return ( return (
<div> <div>
<div> <div className={ styles.summary }>
<FormattedMessage <FormattedMessage
id='createAccount.accountDetailsGeth.imported' id='createAccount.accountDetailsGeth.imported'
defaultMessage='You have imported {number} addresses from the Geth keystore:' defaultMessage='You have completed the import of {number} addresses from the Geth keystore. These will now be available in your accounts list as a normal account, along with their associated balances on the network.'
values={ { values={ {
number: gethImported.length number: gethImported.length
} } } }
/> />
</div> </div>
<div className={ styles.address }> <SectionList
{ this.formatAddresses(gethImported) } items={ accounts }
</div> noStretch
renderItem={ this.renderAccount }
/>
</div> </div>
); );
} }
formatAddresses (addresses) { renderAccount = (account, index) => {
return addresses.map((address, index) => { return (
const comma = !index <GethCard
? '' address={ account.address }
: ((index === addresses.length - 1) ? ' & ' : ', '); balance={ account.balance }
name={ `Geth Import ${index + 1}` }
return `${comma}${address}`; />
}).join(''); );
} }
} }

View File

@ -39,22 +39,4 @@ describe('modals/CreateAccount/AccountDetailsGeth', () => {
it('renders with defaults', () => { it('renders with defaults', () => {
expect(render()).to.be.ok; expect(render()).to.be.ok;
}); });
describe('utility', () => {
describe('formatAddresses', () => {
let instance;
beforeEach(() => {
instance = component.instance();
});
it('renders a single item', () => {
expect(instance.formatAddresses(['one'])).to.equal('one');
});
it('renders multiple items', () => {
expect(instance.formatAddresses(['one', 'two', 'three'])).to.equal('one, two & three');
});
});
});
}); });

View File

@ -18,8 +18,9 @@ import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { RadioButtons } from '~/ui'; import { Container, SelectionList, Title } from '~/ui';
import TypeIcon from '../TypeIcon';
import styles from '../createAccount.css'; import styles from '../createAccount.css';
const TYPES = [ const TYPES = [
@ -27,13 +28,13 @@ const TYPES = [
description: ( description: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromNew.description' id='createAccount.creationType.fromNew.description'
defaultMessage='Create an account by selecting your identity icon and specifying the password' defaultMessage='Selecting your identity icon and specifying the password'
/> />
), ),
label: ( label: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromNew.label' id='createAccount.creationType.fromNew.label'
defaultMessage='Create new account manually' defaultMessage='New Account'
/> />
), ),
key: 'fromNew' key: 'fromNew'
@ -42,13 +43,13 @@ const TYPES = [
description: ( description: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromPhrase.description' id='createAccount.creationType.fromPhrase.description'
defaultMessage='Recover an account by entering a previously stored recovery phrase and new password' defaultMessage='Recover using a previously stored recovery phrase and new password'
/> />
), ),
label: ( label: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromPhrase.label' id='createAccount.creationType.fromPhrase.label'
defaultMessage='Recover account from recovery phrase' defaultMessage='Recovery phrase'
/> />
), ),
key: 'fromPhrase' key: 'fromPhrase'
@ -57,13 +58,13 @@ const TYPES = [
description: ( description: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromGeth.description' id='createAccount.creationType.fromGeth.description'
defaultMessage='Import an accounts from the Geth keystore with the original password' defaultMessage='Import accounts from the Geth keystore with the original password'
/> />
), ),
label: ( label: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromGeth.label' id='createAccount.creationType.fromGeth.label'
defaultMessage='Import accounts from Geth keystore' defaultMessage='Geth keystore'
/> />
), ),
key: 'fromGeth' key: 'fromGeth'
@ -72,13 +73,13 @@ const TYPES = [
description: ( description: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromJSON.description' id='createAccount.creationType.fromJSON.description'
defaultMessage='Create an account by importing an industry-standard JSON keyfile' defaultMessage='Import an industry-standard JSON keyfile with the original password'
/> />
), ),
label: ( label: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromJSON.label' id='createAccount.creationType.fromJSON.label'
defaultMessage='Import account from a backup JSON file' defaultMessage='JSON file'
/> />
), ),
key: 'fromJSON' key: 'fromJSON'
@ -87,13 +88,13 @@ const TYPES = [
description: ( description: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromPresale.description' id='createAccount.creationType.fromPresale.description'
defaultMessage='Create an account by importing an Ethereum presale wallet file' defaultMessage='Import an Ethereum presale wallet file with the original password'
/> />
), ),
label: ( label: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromPresale.label' id='createAccount.creationType.fromPresale.label'
defaultMessage='Import account from an Ethereum pre-sale wallet' defaultMessage='Presale wallet'
/> />
), ),
key: 'fromPresale' key: 'fromPresale'
@ -102,13 +103,13 @@ const TYPES = [
description: ( description: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromRaw.description' id='createAccount.creationType.fromRaw.description'
defaultMessage='Create an account by entering a previously backed-up raw private key' defaultMessage='Enter a previously created raw private key with a new password'
/> />
), ),
label: ( label: (
<FormattedMessage <FormattedMessage
id='createAccount.creationType.fromRaw.label' id='createAccount.creationType.fromRaw.label'
defaultMessage='Import raw private key' defaultMessage='Private key'
/> />
), ),
key: 'fromRaw' key: 'fromRaw'
@ -125,20 +126,58 @@ export default class CreationType extends Component {
const { createType } = this.props.store; const { createType } = this.props.store;
return ( return (
<div className={ styles.spaced }> <div>
<RadioButtons <div className={ styles.summary }>
name='creationType' <FormattedMessage
onChange={ this.onChange } id='createAccount.creationType.info'
value={ createType } defaultMessage='Please select the type of account you want to create. Either create an account via name & password, or import it from a variety of existing sources. From here the wizard will guid you through the process of completing your account creation.'
values={ TYPES } />
/> </div>
{ this.renderList(createType) }
</div> </div>
); );
} }
onChange = (item, type) => { renderList () {
return (
<SelectionList
isChecked={ this.isSelected }
items={ TYPES }
noStretch
onSelectClick={ this.onChange }
renderItem={ this.renderItem }
/>
);
}
renderItem = (item) => {
return (
<Container>
<div className={ styles.selectItem }>
<TypeIcon
className={ styles.icon }
store={ this.props.store }
type={ item.key }
/>
<Title
byline={ item.description }
className={ styles.info }
title={ item.label }
/>
</div>
</Container>
);
}
isSelected = (item) => {
const { createType } = this.props.store;
return item.key === createType;
}
onChange = (item) => {
const { store } = this.props; const { store } = this.props;
store.setCreateType(type); store.setCreateType(item.key);
} }
} }

View File

@ -22,6 +22,7 @@ import { createStore } from '../createAccount.test.js';
import CreationType from './'; import CreationType from './';
let component; let component;
let instance;
let store; let store;
function render () { function render () {
@ -31,6 +32,7 @@ function render () {
store={ store } store={ store }
/> />
); );
instance = component.instance();
return component; return component;
} }
@ -44,28 +46,10 @@ describe('modals/CreateAccount/CreationType', () => {
expect(component).to.be.ok; expect(component).to.be.ok;
}); });
describe('selector', () => {
const SELECT_TYPE = 'fromRaw';
let selector;
beforeEach(() => {
store.setCreateType(SELECT_TYPE);
selector = component.find('RadioButtons');
});
it('renders the selector', () => {
expect(selector.get(0)).to.be.ok;
});
it('passes the store type to defaultSelected', () => {
expect(selector.props().value).to.equal(SELECT_TYPE);
});
});
describe('events', () => { describe('events', () => {
describe('onChange', () => { describe('onChange', () => {
beforeEach(() => { beforeEach(() => {
component.instance().onChange(null, 'testing'); instance.onChange({ key: 'testing' });
}); });
it('changes the store createType', () => { it('changes the store createType', () => {

View File

@ -0,0 +1,51 @@
// Copyright 2015-2017 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 imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
import { AccountCard } from '~/ui';
export default class GethCard extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
balance: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
}
render () {
const { address, balance, name } = this.props;
return (
<AccountCard
account={ {
address,
name
} }
balance={ {
tokens: [ {
value: balance,
token: {
image: imagesEthereum,
native: true,
tag: 'ETH'
}
} ]
} }
/>
);
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 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 './gethCard';

View File

@ -17,11 +17,11 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Checkbox } from 'material-ui';
import { IdentityIcon } from '~/ui'; import { SelectionList } from '~/ui';
import styles from './newGeth.css'; import GethCard from '../GethCard';
import styles from '../createAccount.css';
@observer @observer
export default class NewGeth extends Component { export default class NewGeth extends Component {
@ -36,60 +36,62 @@ export default class NewGeth extends Component {
render () { render () {
const { gethAccountsAvailable, gethAddresses } = this.props.store; const { gethAccountsAvailable, gethAddresses } = this.props.store;
if (!gethAccountsAvailable.length) { return gethAccountsAvailable.length
return ( ? (
<div className={ styles.list }> <div>
<div className={ styles.summary }>
<FormattedMessage
id='createAccount.newGeth.available'
defaultMessage='There are currently {count} importable keys available from the Geth keystore which are not already available on your Parity instance. Select the accounts you wish to import and move to the next step to complete the import.'
values={ {
count: gethAccountsAvailable.length
} }
/>
</div>
{ this.renderList(gethAccountsAvailable, gethAddresses) }
</div>
)
: (
<div className={ styles.summary }>
<FormattedMessage <FormattedMessage
id='createAccount.newGeth.noKeys' id='createAccount.newGeth.noKeys'
defaultMessage='There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance' defaultMessage='There are currently no importable keys available from the Geth keystore, which are not already available on your Parity instance'
/> />
</div> </div>
); );
} }
const checkboxes = gethAccountsAvailable.map((account) => {
const onSelect = (event) => this.onSelectAddress(event, account.address);
const label = (
<div className={ styles.selection }>
<div className={ styles.icon }>
<IdentityIcon
address={ account.address }
center
inline
/>
</div>
<div className={ styles.detail }>
<div className={ styles.address }>
{ account.address }
</div>
<div className={ styles.balance }>
{ account.balance } ETH
</div>
</div>
</div>
);
return (
<Checkbox
checked={ gethAddresses.includes(account.address) }
key={ account.address }
label={ label }
onCheck={ onSelect }
/>
);
});
renderList (gethAccountsAvailable) {
return ( return (
<div className={ styles.list }> <SelectionList
{ checkboxes } isChecked={ this.isSelected }
</div> items={ gethAccountsAvailable }
noStretch
onSelectClick={ this.onSelect }
renderItem={ this.renderAccount }
/>
); );
} }
onSelectAddress = (event, address) => { renderAccount = (account, index) => {
return (
<GethCard
address={ account.address }
balance={ account.balance }
name={ `Geth Account ${index + 1}` }
/>
);
}
isSelected = (account) => {
const { gethAddresses } = this.props.store;
return gethAddresses.includes(account.address);
}
onSelect = (account) => {
const { store } = this.props; const { store } = this.props;
store.selectGethAccount(address); store.selectGethAccount(account.address);
} }
} }

View File

@ -48,10 +48,10 @@ describe('modals/CreateAccount/NewGeth', () => {
}); });
describe('events', () => { describe('events', () => {
describe('onSelectAddress', () => { describe('onSelect', () => {
beforeEach(() => { beforeEach(() => {
sinon.spy(store, 'selectGethAccount'); sinon.spy(store, 'selectGethAccount');
instance.onSelectAddress(null, 'testAddress'); instance.onSelect({ address: 'testAddress' });
}); });
afterEach(() => { afterEach(() => {

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 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 './typeIcon';

View File

@ -0,0 +1,59 @@
// Copyright 2015-2017 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 { AccountsIcon, DoneIcon, FileIcon, FileUploadIcon, KeyboardIcon, KeyIcon, MembershipIcon } from '~/ui/Icons';
import { STAGE_INFO } from '../store';
export default class TypeIcon extends Component {
static propTypes = {
className: PropTypes.string,
store: PropTypes.object.isRequired,
type: PropTypes.string
}
render () {
const { className, store, type } = this.props;
const { createType, stage } = store;
if (stage === STAGE_INFO) {
return <DoneIcon className={ className } />;
}
switch (type || createType) {
case 'fromGeth':
return <FileUploadIcon className={ className } />;
case 'fromPhrase':
return <KeyboardIcon className={ className } />;
case 'fromRaw':
return <KeyIcon className={ className } />;
case 'fromJSON':
return <FileIcon className={ className } />;
case 'fromPresale':
return <MembershipIcon className={ className } />;
case 'fromNew':
default:
return <AccountsIcon className={ className } />;
}
}
}

View File

@ -16,15 +16,42 @@
*/ */
.details { .details {
text-align: center; width: 100%;
.infoForm { .info {
background: rgba(0, 0, 0, 0.1); display: flex;
padding: 1.5em; flex-direction: row;
} flex-wrap: nowrap;
.qr { .account {
margin: 1.5em auto 0 auto; display: flex;
flex-direction: column;
flex: 1;
.name {
display: flex;
flex-direction: row;
flex: 1;
.icon {
flex: 0 0 56px;
margin: 0 1em 0 0;
}
.title {
flex: 1;
}
}
.description {
flex: 1;
}
}
.qr {
flex: 0 0 136px;
margin: 1.5em 0 0 1.5em;
}
} }
} }
@ -88,3 +115,24 @@
.checkbox { .checkbox {
margin-top: 2em; margin-top: 2em;
} }
.selectItem {
display: flex;
.icon {
flex: 0 0 56px;
height: 56px !important;
margin-right: 0.75em;
width: 56px !important;
}
.info {
flex: 1 1;
}
}
.summary {
line-height: 1.618em;
padding: 0 4em 1.5em 4em;
text-align: center;
}

View File

@ -22,7 +22,7 @@ import { bindActionCreators } from 'redux';
import { createIdentityImg } from '~/api/util/identity'; import { createIdentityImg } from '~/api/util/identity';
import { newError } from '~/redux/actions'; import { newError } from '~/redux/actions';
import { Button, Portal } from '~/ui'; import { Button, ModalBox, Portal } from '~/ui';
import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons'; import { CancelIcon, CheckIcon, DoneIcon, NextIcon, PrevIcon, PrintIcon } from '~/ui/Icons';
import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg'; import ParityLogo from '~/../assets/images/parity-logo-black-no-text.svg';
@ -35,6 +35,7 @@ import NewImport from './NewImport';
import RawKey from './RawKey'; import RawKey from './RawKey';
import RecoveryPhrase from './RecoveryPhrase'; import RecoveryPhrase from './RecoveryPhrase';
import Store, { STAGE_CREATE, STAGE_INFO, STAGE_SELECT_TYPE } from './store'; import Store, { STAGE_CREATE, STAGE_INFO, STAGE_SELECT_TYPE } from './store';
import TypeIcon from './TypeIcon';
import print from './print'; import print from './print';
import recoveryPage from './recoveryPage.ejs'; import recoveryPage from './recoveryPage.ejs';
@ -97,7 +98,9 @@ class CreateAccount extends Component {
: STAGE_IMPORT : STAGE_IMPORT
} }
> >
{ this.renderPage() } <ModalBox icon={ <TypeIcon store={ this.store } /> }>
{ this.renderPage() }
</ModalBox>
</Portal> </Portal>
); );
} }
@ -246,11 +249,11 @@ class CreateAccount extends Component {
: null, : null,
<Button <Button
icon={ <DoneIcon /> } icon={ <DoneIcon /> }
key='close' key='done'
label={ label={
<FormattedMessage <FormattedMessage
id='createAccount.button.close' id='createAccount.button.done'
defaultMessage='Close' defaultMessage='Done'
/> />
} }
onClick={ this.onClose } onClick={ this.onClose }

View File

@ -91,6 +91,7 @@ export default class Store {
this.password = ''; this.password = '';
this.passwordRepeat = ''; this.passwordRepeat = '';
this.phrase = ''; this.phrase = '';
this.name = '';
this.nameError = null; this.nameError = null;
this.rawKeyError = null; this.rawKeyError = null;
this.walletFileError = null; this.walletFileError = null;

View File

@ -24,7 +24,7 @@ import { setAddressImage } from './imagesActions';
import * as ABIS from '~/contracts/abi'; import * as ABIS from '~/contracts/abi';
import { notifyTransaction } from '~/util/notifications'; import { notifyTransaction } from '~/util/notifications';
import { LOG_KEYS, getLogger } from '~/config'; import { LOG_KEYS, getLogger } from '~/config';
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
const log = getLogger(LOG_KEYS.Balances); const log = getLogger(LOG_KEYS.Balances);

View File

@ -16,6 +16,7 @@
*/ */
.form { .form {
margin-top: -1em; margin-top: -1em;
width: 100%;
} }
.autofill { .autofill {

View File

@ -15,13 +15,16 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export AccountsIcon from 'material-ui/svg-icons/action/account-balance-wallet'; export AccountsIcon from 'material-ui/svg-icons/action/account-balance-wallet';
export AddressesIcon from 'material-ui/svg-icons/communication/contacts';
export AddIcon from 'material-ui/svg-icons/content/add'; export AddIcon from 'material-ui/svg-icons/content/add';
export AppsIcon from 'material-ui/svg-icons/navigation/apps';
export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file'; export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file';
export CancelIcon from 'material-ui/svg-icons/content/clear'; export CancelIcon from 'material-ui/svg-icons/content/clear';
export CheckIcon from 'material-ui/svg-icons/navigation/check'; export CheckIcon from 'material-ui/svg-icons/navigation/check';
export CloseIcon from 'material-ui/svg-icons/navigation/close'; export CloseIcon from 'material-ui/svg-icons/navigation/close';
export CompareIcon from 'material-ui/svg-icons/action/compare-arrows'; export CompareIcon from 'material-ui/svg-icons/action/compare-arrows';
export ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac'; export ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac';
export ContactsIcon from 'material-ui/svg-icons/image/grid-on';
export ContractIcon from 'material-ui/svg-icons/action/code'; export ContractIcon from 'material-ui/svg-icons/action/code';
export CopyIcon from 'material-ui/svg-icons/content/content-copy'; export CopyIcon from 'material-ui/svg-icons/content/content-copy';
export DashboardIcon from 'material-ui/svg-icons/action/dashboard'; export DashboardIcon from 'material-ui/svg-icons/action/dashboard';
@ -30,10 +33,13 @@ export DoneIcon from 'material-ui/svg-icons/action/done-all';
export EditIcon from 'material-ui/svg-icons/content/create'; export EditIcon from 'material-ui/svg-icons/content/create';
export ErrorIcon from 'material-ui/svg-icons/alert/error'; export ErrorIcon from 'material-ui/svg-icons/alert/error';
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload'; export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
export FileIcon from 'material-ui/svg-icons/editor/insert-drive-file';
export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint';
export KeyIcon from 'material-ui/svg-icons/communication/vpn-key'; export KeyIcon from 'material-ui/svg-icons/communication/vpn-key';
export KeyboardIcon from 'material-ui/svg-icons/hardware/keyboard';
export LinkIcon from 'material-ui/svg-icons/content/link'; export LinkIcon from 'material-ui/svg-icons/content/link';
export LockedIcon from 'material-ui/svg-icons/action/lock'; export LockedIcon from 'material-ui/svg-icons/action/lock';
export MembershipIcon from 'material-ui/svg-icons/action/card-membership';
export MoveIcon from 'material-ui/svg-icons/action/open-with'; export MoveIcon from 'material-ui/svg-icons/action/open-with';
export NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; export NextIcon from 'material-ui/svg-icons/navigation/arrow-forward';
export PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; export PrevIcon from 'material-ui/svg-icons/navigation/arrow-back';
@ -41,10 +47,12 @@ export PrintIcon from 'material-ui/svg-icons/action/print';
export RefreshIcon from 'material-ui/svg-icons/action/autorenew'; export RefreshIcon from 'material-ui/svg-icons/action/autorenew';
export SaveIcon from 'material-ui/svg-icons/content/save'; export SaveIcon from 'material-ui/svg-icons/content/save';
export SendIcon from 'material-ui/svg-icons/content/send'; export SendIcon from 'material-ui/svg-icons/content/send';
export SettingsIcon from 'material-ui/svg-icons/action/settings';
export SnoozeIcon from 'material-ui/svg-icons/av/snooze'; export SnoozeIcon from 'material-ui/svg-icons/av/snooze';
export StarCircleIcon from 'material-ui/svg-icons/action/stars'; export StarCircleIcon from 'material-ui/svg-icons/action/stars';
export StarIcon from 'material-ui/svg-icons/toggle/star'; export StarIcon from 'material-ui/svg-icons/toggle/star';
export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border'; export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border';
export StatusIcon from 'material-ui/svg-icons/action/track-changes';
export UnlockedIcon from 'material-ui/svg-icons/action/lock-open'; export UnlockedIcon from 'material-ui/svg-icons/action/lock-open';
export UpdateIcon from 'material-ui/svg-icons/action/system-update-alt'; export UpdateIcon from 'material-ui/svg-icons/action/system-update-alt';
export UpdateWaitIcon from 'material-ui/svg-icons/action/update'; export UpdateWaitIcon from 'material-ui/svg-icons/action/update';

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 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 './modalBox';

View File

@ -14,29 +14,49 @@
/* You should have received a copy of the GNU General Public License /* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.list {
input+div>div {
top: 13px;
}
}
.selection { .body {
display: inline-block; display: flex;
margin-bottom: 0.5em; justify-content: flex-start;
.content {
flex: 1;
.body {
width: 100%;
}
.summary {
line-height: 1.618em;
padding: 0 4em 1.5em 4em;
text-align: center;
}
}
.icon { .icon {
display: inline-block; align-items: center;
} background: rgb(167, 151, 0);
border-radius: 50%;
display: flex;
flex: 0 0 5em;
flex-direction: column;
height: 5em;
justify-content: center;
margin-right: 1.5em;
padding: 0.75em;
width: 5em;
.detail { img {
display: inline-block; margin: 0;
padding: 0;
.address {
color: #aaa;
} }
.balance { svg {
font-family: 'Roboto Mono', monospace; fill: white !important;
font-size: 3.5em !important;
height: 3.5em !important;
margin: 0 !important;
width: 3.5em !important;
} }
} }
} }

View File

@ -0,0 +1,61 @@
// Copyright 2015-2017 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 { nodeOrStringProptype } from '~/util/proptypes';
import styles from './modalBox.css';
export default class ModalBox extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
icon: PropTypes.node.isRequired,
summary: nodeOrStringProptype()
}
render () {
const { children, icon } = this.props;
return (
<div className={ styles.body }>
<div className={ styles.icon }>
{ icon }
</div>
<div className={ styles.content }>
{ this.renderSummary() }
<div className={ styles.body }>
{ children }
</div>
</div>
</div>
);
}
renderSummary () {
const { summary } = this.props;
if (!summary) {
return null;
}
return (
<div className={ styles.summary }>
{ summary }
</div>
);
}
}

View File

@ -0,0 +1,56 @@
// Copyright 2015-2017 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 ModalBox from './';
let component;
function render () {
component = shallow(
<ModalBox
children={ <div id='testChild'>testChild</div> }
icon={ <div id='testIcon'>testIcon</div> }
summary={ <div id='testSummary'>testSummary</div> }
/>
);
return component;
}
describe('ui/ModalBox', () => {
beforeEach(() => {
render();
});
it('renders defaults', () => {
expect(component).to.be.ok;
});
it('adds the children as supplied', () => {
expect(component.find('#testChild').text()).to.equal('testChild');
});
it('adds the icon as supplied', () => {
expect(component.find('#testIcon').text()).to.equal('testIcon');
});
it('adds the summary as supplied', () => {
expect(component.find('#testSummary').text()).to.equal('testSummary');
});
});

View File

@ -40,6 +40,7 @@ export LanguageSelector from './LanguageSelector';
export Loading from './Loading'; export Loading from './Loading';
export MethodDecoding from './MethodDecoding'; export MethodDecoding from './MethodDecoding';
export { Busy as BusyStep, Completed as CompletedStep } from './Modal'; export { Busy as BusyStep, Completed as CompletedStep } from './Modal';
export ModalBox from './ModalBox';
export muiTheme from './Theme'; export muiTheme from './Theme';
export Page from './Page'; export Page from './Page';
export ParityBackground from './ParityBackground'; export ParityBackground from './ParityBackground';

View File

@ -15,15 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react'; import React from 'react';
import ActionAccountBalanceWallet from 'material-ui/svg-icons/action/account-balance-wallet';
import ActionFingerprint from 'material-ui/svg-icons/action/fingerprint';
import ActionTrackChanges from 'material-ui/svg-icons/action/track-changes';
import ActionSettings from 'material-ui/svg-icons/action/settings';
import CommunicationContacts from 'material-ui/svg-icons/communication/contacts';
import ImageGridOn from 'material-ui/svg-icons/image/grid-on';
import NavigationApps from 'material-ui/svg-icons/navigation/apps';
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon, StatusIcon } from '~/ui/Icons';
import styles from './views.css'; import styles from './views.css';
@ -44,35 +38,35 @@ const defaultViews = {
accounts: { accounts: {
active: true, active: true,
fixed: true, fixed: true,
icon: <ActionAccountBalanceWallet />, icon: <AccountsIcon />,
route: '/accounts', route: '/accounts',
value: 'account' value: 'account'
}, },
addresses: { addresses: {
active: true, active: true,
icon: <CommunicationContacts />, icon: <AddressesIcon />,
route: '/addresses', route: '/addresses',
value: 'address' value: 'address'
}, },
apps: { apps: {
active: true, active: true,
icon: <NavigationApps />, icon: <AppsIcon />,
route: '/apps', route: '/apps',
value: 'app' value: 'app'
}, },
contracts: { contracts: {
active: false, active: false,
icon: <ImageGridOn />, icon: <ContactsIcon />,
route: '/contracts', route: '/contracts',
value: 'contract' value: 'contract'
}, },
status: { status: {
active: false, active: false,
icon: <ActionTrackChanges />, icon: <StatusIcon />,
route: '/status', route: '/status',
value: 'status' value: 'status'
}, },
@ -80,7 +74,7 @@ const defaultViews = {
signer: { signer: {
active: true, active: true,
fixed: true, fixed: true,
icon: <ActionFingerprint />, icon: <FingerprintIcon />,
route: '/signer', route: '/signer',
value: 'signer' value: 'signer'
}, },
@ -88,7 +82,7 @@ const defaultViews = {
settings: { settings: {
active: true, active: true,
fixed: true, fixed: true,
icon: <ActionSettings />, icon: <SettingsIcon />,
route: '/settings', route: '/settings',
value: 'settings' value: 'settings'
} }