[beta] UI updates for 1.5.1 (#4429)
* s/Delete Contract/Forget Contract/ (#4237) * Adjust the location of the signer snippet (#4155) * Additional building-block UI components (#4239) * Currency WIP * Expand tests * Pass className * Add QrCode * Export new components in ~/ui * s/this.props.netSymbol/netSymbol/ * Fix import case * ui/SectionList component (#4292) * array chunking utility * add SectionList component * Add TODOs to indicate possible future work * Add missing overlay style (as used in dapps at present) * Add a Playground for the UI Components (#4301) * Playground // WIP * Linting * Add Examples with code * CSS Linting * Linting * Add Connected Currency Symbol * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * Added `renderSymbol` tests * PR grumbles * Add Eth and Btc QRCode examples * 2015-2017 * Add tests for playground * Fixing tests * Split Dapp icon into ui/DappIcon (#4308) * Add QrCode & Copy to ShapeShift (#4322) * Extract CopyIcon to ~/ui/Icons * Add copy & QrCode address * Default size 4 * Add bitcoin: link * use protocol links applicable to coin exchanged * Remove .only * Display QrCode for accounts, addresses & contracts (#4329) * Allow Portal to be used as top-level modal (#4338) * Portal * Allow Portal to be used in as both top-level and popover * modal/popover variable naming * export Portal in ~/ui * Properly handle optional onKeyDown * Add simple Playground Example * Add proper event listener to Portal (#4359) * Display AccountCard name via IdentityName (#4235) * Fix signing (#4363) * Dapp Account Selection & Defaults (#4355) * Add parity_defaultAccount RPC (with subscription) (#4383) * Default Account selector in Signer overlay (#4375) * Typo, fixes #4271 (#4391) * Fix ParityBar account selection overflows (#4405) * Available Dapp selection alignment with Permissions (Portal) (#4374) * registry dapp: make lookup use lower case (#4409) * Dapps use defaultAccount instead of own selectors (#4386) * Poll for defaultAccount to update dapp & overlay subscriptions (#4417) * Poll for defaultAccount (Fixes #4413) * Fix nextTimeout on catch * Store timers * Re-enable default updates on change detection * Add block & timestamp conditions to Signer (#4411) * Extension installation overlay (#4423) * Extension installation overlay * Pr gumbles * Spelling * Update Chrome URL * Fix for non-included jsonrpc * Extend Portal component (as per Modal) #4392
This commit is contained in:
@@ -15,15 +15,23 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.modal {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: .5em !important;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
.background {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
margin: 0 -1.5em;
|
||||
padding: 0.5em 1.5em;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -37,3 +45,26 @@
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
.selectIcon {
|
||||
position: absolute;
|
||||
right: 0.5em;
|
||||
top: 0.5em;
|
||||
}
|
||||
|
||||
.selected,
|
||||
.unselected {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.unselected {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
|
||||
.selectIcon {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,14 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Checkbox } from 'material-ui';
|
||||
import { List, ListItem } from 'material-ui/List';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Modal, Button } from '~/ui';
|
||||
import { DoneIcon } from '~/ui/Icons';
|
||||
import { DappCard, Portal, SectionList } from '~/ui';
|
||||
import { CheckIcon } from '~/ui/Icons';
|
||||
|
||||
import styles from './addDapps.css';
|
||||
|
||||
@@ -39,61 +37,61 @@ export default class AddDapps extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
actions={ [
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='dapps.add.button.done'
|
||||
defaultMessage='Done' />
|
||||
}
|
||||
onClick={ store.closeModal } />
|
||||
] }
|
||||
compact
|
||||
<Portal
|
||||
className={ styles.modal }
|
||||
onClose={ store.closeModal }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='dapps.add.label'
|
||||
defaultMessage='visible applications' />
|
||||
defaultMessage='visible applications'
|
||||
/>
|
||||
}
|
||||
visible>
|
||||
<div className={ styles.warning } />
|
||||
{
|
||||
this.renderList(store.sortedLocal,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.label'
|
||||
defaultMessage='Applications locally available' />,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.desc'
|
||||
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.' />
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedBuiltin,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.label'
|
||||
defaultMessage='Applications bundled with Parity' />,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.desc'
|
||||
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.' />
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedNetwork,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.label'
|
||||
defaultMessage='Applications on the global network' />,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.desc'
|
||||
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.' />
|
||||
)
|
||||
}
|
||||
</Modal>
|
||||
>
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.warning } />
|
||||
{
|
||||
this.renderList(store.sortedLocal, store.displayApps,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.label'
|
||||
defaultMessage='Applications locally available'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.local.desc'
|
||||
defaultMessage='All applications installed locally on the machine by the user for access by the Parity client.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedBuiltin, store.displayApps,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.label'
|
||||
defaultMessage='Applications bundled with Parity'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.builtin.desc'
|
||||
defaultMessage='Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.renderList(store.sortedNetwork, store.displayApps,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.label'
|
||||
defaultMessage='Applications on the global network'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='dapps.add.network.desc'
|
||||
defaultMessage='These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderList (items, header, byline) {
|
||||
renderList (items, visibleItems, header, byline) {
|
||||
if (!items || !items.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -104,41 +102,40 @@ export default class AddDapps extends Component {
|
||||
<div className={ styles.header }>{ header }</div>
|
||||
<div className={ styles.byline }>{ byline }</div>
|
||||
</div>
|
||||
<List>
|
||||
{ items.map(this.renderApp) }
|
||||
</List>
|
||||
<SectionList
|
||||
items={ items }
|
||||
noStretch
|
||||
renderItem={ this.renderApp }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderApp = (app) => {
|
||||
const { store } = this.props;
|
||||
const isHidden = !store.displayApps[app.id].visible;
|
||||
const isVisible = store.displayApps[app.id].visible;
|
||||
|
||||
const onCheck = () => {
|
||||
if (isHidden) {
|
||||
store.showApp(app.id);
|
||||
} else {
|
||||
const onClick = () => {
|
||||
if (isVisible) {
|
||||
store.hideApp(app.id);
|
||||
} else {
|
||||
store.showApp(app.id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
<DappCard
|
||||
app={ app }
|
||||
className={
|
||||
isVisible
|
||||
? styles.selected
|
||||
: styles.unselected
|
||||
}
|
||||
key={ app.id }
|
||||
leftCheckbox={
|
||||
<Checkbox
|
||||
checked={ !isHidden }
|
||||
onCheck={ onCheck }
|
||||
/>
|
||||
}
|
||||
primaryText={ app.name }
|
||||
secondaryText={
|
||||
<div className={ styles.description }>
|
||||
{ app.description }
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
onClick={ onClick }
|
||||
>
|
||||
<CheckIcon className={ styles.selectIcon } />
|
||||
</DappCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ describe('modals/AddDapps', () => {
|
||||
|
||||
it('does not render the modal with modalOpen = false', () => {
|
||||
expect(
|
||||
renderShallow({ modalOpen: false }).find('Connect(Modal)')
|
||||
renderShallow({ modalOpen: false }).find('Portal')
|
||||
).to.have.length(0);
|
||||
});
|
||||
|
||||
it('does render the modal with modalOpen = true', () => {
|
||||
expect(
|
||||
renderShallow({ modalOpen: true }).find('Connect(Modal)')
|
||||
renderShallow({ modalOpen: true }).find('Portal')
|
||||
).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,33 +15,54 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.item {
|
||||
.info {
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.address {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 0.5em;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin: 0.5em 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
right: 0.5em;
|
||||
top: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.selected, .unselected {
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.unselected {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
|
||||
&.default {
|
||||
background: rgba(255, 255, 255, 0.35) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.unselected {
|
||||
}
|
||||
|
||||
.iconDisabled {
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.legend {
|
||||
opacity: 0.75;
|
||||
|
||||
span {
|
||||
line-height: 24px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,14 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Checkbox } from 'material-ui';
|
||||
import { List, ListItem } from 'material-ui/List';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button, IdentityIcon, Modal } from '~/ui';
|
||||
import { DoneIcon } from '~/ui/Icons';
|
||||
import { AccountCard, Portal, SectionList } from '~/ui';
|
||||
import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons';
|
||||
|
||||
import styles from './dappPermissions.css';
|
||||
|
||||
@@ -39,74 +37,80 @@ export default class DappPermissions extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
actions={ [
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='dapps.permissions.button.done'
|
||||
defaultMessage='Done' />
|
||||
}
|
||||
onClick={ store.closeModal } />
|
||||
] }
|
||||
compact
|
||||
<Portal
|
||||
buttons={
|
||||
<div className={ styles.legend }>
|
||||
<FormattedMessage
|
||||
id='dapps.permissions.description'
|
||||
defaultMessage='{activeIcon} account is available to application, {defaultIcon} account is the default account'
|
||||
values={ {
|
||||
activeIcon: <CheckIcon />,
|
||||
defaultIcon: <StarIcon />
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
onClose={ store.closeModal }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='dapps.permissions.label'
|
||||
defaultMessage='visible dapp accounts' />
|
||||
defaultMessage='visible dapp accounts'
|
||||
/>
|
||||
}
|
||||
visible>
|
||||
<List>
|
||||
{ this.renderListItems() }
|
||||
</List>
|
||||
</Modal>
|
||||
>
|
||||
<div className={ styles.container }>
|
||||
<SectionList
|
||||
items={ store.accounts }
|
||||
noStretch
|
||||
renderItem={ this.renderAccount }
|
||||
/>
|
||||
</div>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderListItems () {
|
||||
renderAccount = (account) => {
|
||||
const { store } = this.props;
|
||||
|
||||
return store.accounts.map((account) => {
|
||||
const onCheck = () => {
|
||||
store.selectAccount(account.address);
|
||||
};
|
||||
const onMakeDefault = () => {
|
||||
store.setDefaultAccount(account.address);
|
||||
};
|
||||
|
||||
// TODO: Once new modal & account selection is in, this should be updated
|
||||
// to conform to the new (as of this code WIP) look & feel for selection.
|
||||
// For now in the current/old style, not as pretty but consistent.
|
||||
return (
|
||||
<ListItem
|
||||
className={
|
||||
const onSelect = () => {
|
||||
store.selectAccount(account.address);
|
||||
};
|
||||
|
||||
let className;
|
||||
|
||||
if (account.checked) {
|
||||
className = account.default
|
||||
? `${styles.selected} ${styles.default}`
|
||||
: styles.selected;
|
||||
} else {
|
||||
className = styles.unselected;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.item }>
|
||||
<AccountCard
|
||||
account={ account }
|
||||
className={ className }
|
||||
onClick={ onSelect }
|
||||
/>
|
||||
<div className={ styles.overlay }>
|
||||
{
|
||||
account.checked && account.default
|
||||
? <StarIcon />
|
||||
: <StarOutlineIcon className={ styles.iconDisabled } onClick={ onMakeDefault } />
|
||||
}
|
||||
{
|
||||
account.checked
|
||||
? styles.selected
|
||||
: styles.unselected
|
||||
? <CheckIcon onClick={ onSelect } />
|
||||
: <CheckIcon className={ styles.iconDisabled } onClick={ onSelect } />
|
||||
}
|
||||
key={ account.address }
|
||||
leftCheckbox={
|
||||
<Checkbox
|
||||
checked={ account.checked }
|
||||
onCheck={ onCheck }
|
||||
/>
|
||||
}
|
||||
primaryText={
|
||||
<div className={ styles.item }>
|
||||
<IdentityIcon address={ account.address } />
|
||||
<div className={ styles.info }>
|
||||
<h3 className={ styles.name }>
|
||||
{ account.name }
|
||||
</h3>
|
||||
<div className={ styles.address }>
|
||||
{ account.address }
|
||||
</div>
|
||||
<div className={ styles.description }>
|
||||
{ account.description }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} />
|
||||
);
|
||||
});
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ describe('modals/DappPermissions', () => {
|
||||
|
||||
it('does not render the modal with modalOpen = false', () => {
|
||||
expect(
|
||||
renderShallow({ modalOpen: false }).find('Connect(Modal)')
|
||||
renderShallow({ modalOpen: false }).find('Portal')
|
||||
).to.have.length(0);
|
||||
});
|
||||
|
||||
it('does render the modal with modalOpen = true', () => {
|
||||
expect(
|
||||
renderShallow({ modalOpen: true, accounts: [] }).find('Connect(Modal)')
|
||||
renderShallow({ modalOpen: true, accounts: [] }).find('Portal')
|
||||
).to.have.length(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,12 +29,17 @@ export default class Store {
|
||||
|
||||
@action closeModal = () => {
|
||||
transaction(() => {
|
||||
const accounts = this.accounts
|
||||
.filter((account) => account.checked)
|
||||
.map((account) => account.address);
|
||||
let addresses = null;
|
||||
const checkedAccounts = this.accounts.filter((account) => account.checked);
|
||||
|
||||
if (checkedAccounts.length) {
|
||||
addresses = checkedAccounts.filter((account) => account.default)
|
||||
.concat(checkedAccounts.filter((account) => !account.default))
|
||||
.map((account) => account.address);
|
||||
}
|
||||
|
||||
this.modalOpen = false;
|
||||
this.updateWhitelist(accounts.length === this.accounts.length ? null : accounts);
|
||||
this.updateWhitelist(addresses);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,12 +47,15 @@ export default class Store {
|
||||
transaction(() => {
|
||||
this.accounts = Object
|
||||
.values(accounts)
|
||||
.map((account) => {
|
||||
.map((account, index) => {
|
||||
return {
|
||||
address: account.address,
|
||||
checked: this.whitelist
|
||||
? this.whitelist.includes(account.address)
|
||||
: true,
|
||||
default: this.whitelist
|
||||
? this.whitelist[0] === account.address
|
||||
: index === 0,
|
||||
description: account.meta.description,
|
||||
name: account.name
|
||||
};
|
||||
@@ -57,9 +65,31 @@ export default class Store {
|
||||
}
|
||||
|
||||
@action selectAccount = (address) => {
|
||||
transaction(() => {
|
||||
this.accounts = this.accounts.map((account) => {
|
||||
if (account.address === address) {
|
||||
account.checked = !account.checked;
|
||||
account.default = false;
|
||||
}
|
||||
|
||||
return account;
|
||||
});
|
||||
|
||||
this.setDefaultAccount((
|
||||
this.accounts.find((account) => account.default) ||
|
||||
this.accounts.find((account) => account.checked) ||
|
||||
{}
|
||||
).address);
|
||||
});
|
||||
}
|
||||
|
||||
@action setDefaultAccount = (address) => {
|
||||
this.accounts = this.accounts.map((account) => {
|
||||
if (account.address === address) {
|
||||
account.checked = !account.checked;
|
||||
account.checked = true;
|
||||
account.default = true;
|
||||
} else if (account.default) {
|
||||
account.default = false;
|
||||
}
|
||||
|
||||
return account;
|
||||
|
||||
@@ -23,21 +23,25 @@ const ACCOUNTS = {
|
||||
'456': { address: '456', name: '456', meta: { description: '456' } },
|
||||
'789': { address: '789', name: '789', meta: { description: '789' } }
|
||||
};
|
||||
const WHITELIST = ['123', '456'];
|
||||
const WHITELIST = ['456', '789'];
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function create () {
|
||||
api = {
|
||||
parity: {
|
||||
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
|
||||
setNewDappsWhitelist: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
|
||||
store = new Store(api);
|
||||
}
|
||||
|
||||
describe('modals/DappPermissions/store', () => {
|
||||
let api;
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
api = {
|
||||
parity: {
|
||||
getNewDappsWhitelist: sinon.stub().resolves(WHITELIST),
|
||||
setNewDappsWhitelist: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
|
||||
store = new Store(api);
|
||||
create();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
@@ -51,49 +55,71 @@ describe('modals/DappPermissions/store', () => {
|
||||
});
|
||||
|
||||
describe('@actions', () => {
|
||||
describe('openModal', () => {
|
||||
beforeEach(() => {
|
||||
store.openModal(ACCOUNTS);
|
||||
});
|
||||
beforeEach(() => {
|
||||
store.openModal(ACCOUNTS);
|
||||
});
|
||||
|
||||
describe('openModal', () => {
|
||||
it('sets the modalOpen status', () => {
|
||||
expect(store.modalOpen).to.be.true;
|
||||
});
|
||||
|
||||
it('sets accounts with checked interfaces', () => {
|
||||
expect(store.accounts.peek()).to.deep.equal([
|
||||
{ address: '123', name: '123', description: '123', checked: true },
|
||||
{ address: '456', name: '456', description: '456', checked: true },
|
||||
{ address: '789', name: '789', description: '789', checked: false }
|
||||
{ address: '123', name: '123', description: '123', default: false, checked: false },
|
||||
{ address: '456', name: '456', description: '456', default: true, checked: true },
|
||||
{ address: '789', name: '789', description: '789', default: false, checked: true }
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closeModal', () => {
|
||||
beforeEach(() => {
|
||||
store.openModal(ACCOUNTS);
|
||||
store.selectAccount('789');
|
||||
store.setDefaultAccount('789');
|
||||
store.closeModal();
|
||||
});
|
||||
|
||||
it('calls setNewDappsWhitelist', () => {
|
||||
expect(api.parity.setNewDappsWhitelist).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('has the default account in first position', () => {
|
||||
expect(api.parity.setNewDappsWhitelist).to.have.been.calledWith(['789', '456']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectAccount', () => {
|
||||
beforeEach(() => {
|
||||
store.openModal(ACCOUNTS);
|
||||
store.selectAccount('123');
|
||||
store.selectAccount('789');
|
||||
});
|
||||
|
||||
it('unselects previous selected accounts', () => {
|
||||
expect(store.accounts.find((account) => account.address === '123').checked).to.be.false;
|
||||
expect(store.accounts.find((account) => account.address === '123').checked).to.be.true;
|
||||
});
|
||||
|
||||
it('selects previous unselected accounts', () => {
|
||||
expect(store.accounts.find((account) => account.address === '789').checked).to.be.true;
|
||||
expect(store.accounts.find((account) => account.address === '789').checked).to.be.false;
|
||||
});
|
||||
|
||||
it('sets a new default when default was unselected', () => {
|
||||
store.selectAccount('456');
|
||||
expect(store.accounts.find((account) => account.address === '456').default).to.be.false;
|
||||
expect(store.accounts.find((account) => account.address === '123').default).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDefaultAccount', () => {
|
||||
beforeEach(() => {
|
||||
store.setDefaultAccount('789');
|
||||
});
|
||||
|
||||
it('unselects previous default', () => {
|
||||
expect(store.accounts.find((account) => account.address === '456').default).to.be.false;
|
||||
});
|
||||
|
||||
it('selects new default', () => {
|
||||
expect(store.accounts.find((account) => account.address === '789').default).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -15,42 +15,22 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Input, GasPriceEditor } from '~/ui';
|
||||
import { GasPriceEditor } from '~/ui';
|
||||
|
||||
import styles from '../executeContract.css';
|
||||
|
||||
export default class AdvancedStep extends Component {
|
||||
static propTypes = {
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
minBlock: PropTypes.string,
|
||||
minBlockError: PropTypes.string,
|
||||
onMinBlockChange: PropTypes.func
|
||||
gasStore: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { gasStore, minBlock, minBlockError, onMinBlockChange } = this.props;
|
||||
const { gasStore } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
error={ minBlockError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='executeContract.advanced.minBlock.hint'
|
||||
defaultMessage='Only post the transaction after this block' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.advanced.minBlock.label'
|
||||
defaultMessage='BlockNumber to send from' />
|
||||
}
|
||||
value={ minBlock }
|
||||
onSubmit={ onMinBlockChange } />
|
||||
<div className={ styles.gaseditor }>
|
||||
<GasPriceEditor store={ gasStore } />
|
||||
</div>
|
||||
<div className={ styles.gaseditor }>
|
||||
<GasPriceEditor store={ gasStore } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { pick } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
@@ -41,27 +40,32 @@ const TITLES = {
|
||||
transfer: (
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.transfer'
|
||||
defaultMessage='function details' />
|
||||
defaultMessage='function details'
|
||||
/>
|
||||
),
|
||||
sending: (
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.sending'
|
||||
defaultMessage='sending' />
|
||||
defaultMessage='sending'
|
||||
/>
|
||||
),
|
||||
complete: (
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.complete'
|
||||
defaultMessage='complete' />
|
||||
defaultMessage='complete'
|
||||
/>
|
||||
),
|
||||
advanced: (
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.advanced'
|
||||
defaultMessage='advanced options' />
|
||||
defaultMessage='advanced options'
|
||||
/>
|
||||
),
|
||||
rejected: (
|
||||
<FormattedMessage
|
||||
id='executeContract.steps.rejected'
|
||||
defaultMessage='rejected' />
|
||||
defaultMessage='rejected'
|
||||
/>
|
||||
)
|
||||
};
|
||||
const STAGES_BASIC = [TITLES.transfer, TITLES.sending, TITLES.complete];
|
||||
@@ -95,8 +99,6 @@ class ExecuteContract extends Component {
|
||||
fromAddressError: null,
|
||||
func: null,
|
||||
funcError: null,
|
||||
minBlock: '0',
|
||||
minBlockError: null,
|
||||
rejected: false,
|
||||
sending: false,
|
||||
step: STEP_DETAILS,
|
||||
@@ -139,7 +141,8 @@ class ExecuteContract extends Component {
|
||||
advancedOptions
|
||||
? [STEP_BUSY]
|
||||
: [STEP_BUSY_OR_ADVANCED]
|
||||
}>
|
||||
}
|
||||
>
|
||||
{ this.renderExceptionWarning() }
|
||||
{ this.renderStep() }
|
||||
</Modal>
|
||||
@@ -161,8 +164,8 @@ class ExecuteContract extends Component {
|
||||
|
||||
renderDialogActions () {
|
||||
const { onClose, fromAddress } = this.props;
|
||||
const { advancedOptions, sending, step, fromAddressError, minBlockError, valuesError } = this.state;
|
||||
const hasError = fromAddressError || minBlockError || valuesError.find((error) => error);
|
||||
const { advancedOptions, sending, step, fromAddressError, valuesError } = this.state;
|
||||
const hasError = fromAddressError || valuesError.find((error) => error);
|
||||
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
@@ -170,10 +173,12 @@ class ExecuteContract extends Component {
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.cancel'
|
||||
defaultMessage='cancel' />
|
||||
defaultMessage='cancel'
|
||||
/>
|
||||
}
|
||||
icon={ <CancelIcon /> }
|
||||
onClick={ onClose } />
|
||||
onClick={ onClose }
|
||||
/>
|
||||
);
|
||||
const postBtn = (
|
||||
<Button
|
||||
@@ -181,11 +186,13 @@ class ExecuteContract extends Component {
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.post'
|
||||
defaultMessage='post transaction' />
|
||||
defaultMessage='post transaction'
|
||||
/>
|
||||
}
|
||||
disabled={ !!(sending || hasError) }
|
||||
icon={ <IdentityIcon address={ fromAddress } button /> }
|
||||
onClick={ this.postTransaction } />
|
||||
onClick={ this.postTransaction }
|
||||
/>
|
||||
);
|
||||
const nextBtn = (
|
||||
<Button
|
||||
@@ -193,10 +200,12 @@ class ExecuteContract extends Component {
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.next'
|
||||
defaultMessage='next' />
|
||||
defaultMessage='next'
|
||||
/>
|
||||
}
|
||||
icon={ <NextIcon /> }
|
||||
onClick={ this.onNextClick } />
|
||||
onClick={ this.onNextClick }
|
||||
/>
|
||||
);
|
||||
const prevBtn = (
|
||||
<Button
|
||||
@@ -204,10 +213,12 @@ class ExecuteContract extends Component {
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.prev'
|
||||
defaultMessage='prev' />
|
||||
defaultMessage='prev'
|
||||
/>
|
||||
}
|
||||
icon={ <PrevIcon /> }
|
||||
onClick={ this.onPrevClick } />
|
||||
onClick={ this.onPrevClick }
|
||||
/>
|
||||
);
|
||||
|
||||
if (step === STEP_DETAILS) {
|
||||
@@ -233,16 +244,18 @@ class ExecuteContract extends Component {
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='executeContract.button.done'
|
||||
defaultMessage='done' />
|
||||
defaultMessage='done'
|
||||
/>
|
||||
}
|
||||
icon={ <DoneIcon /> }
|
||||
onClick={ onClose } />
|
||||
onClick={ onClose }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
renderStep () {
|
||||
const { onFromAddressChange } = this.props;
|
||||
const { advancedOptions, step, busyState, minBlock, minBlockError, txhash, rejected } = this.state;
|
||||
const { advancedOptions, step, busyState, txhash, rejected } = this.state;
|
||||
|
||||
if (rejected) {
|
||||
return (
|
||||
@@ -250,13 +263,16 @@ class ExecuteContract extends Component {
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='executeContract.rejected.title'
|
||||
defaultMessage='The execution has been rejected' />
|
||||
defaultMessage='The execution has been rejected'
|
||||
/>
|
||||
}
|
||||
state={
|
||||
<FormattedMessage
|
||||
id='executeContract.rejected.state'
|
||||
defaultMessage='You can safely close this window, the function execution will not occur.' />
|
||||
} />
|
||||
defaultMessage='You can safely close this window, the function execution will not occur.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -269,7 +285,8 @@ class ExecuteContract extends Component {
|
||||
onFromAddressChange={ onFromAddressChange }
|
||||
onFuncChange={ this.onFuncChange }
|
||||
onAdvancedClick={ this.onAdvancedClick }
|
||||
onValueChange={ this.onValueChange } />
|
||||
onValueChange={ this.onValueChange }
|
||||
/>
|
||||
);
|
||||
} else if (step === (advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED)) {
|
||||
return (
|
||||
@@ -277,17 +294,15 @@ class ExecuteContract extends Component {
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='executeContract.busy.title'
|
||||
defaultMessage='The function execution is in progress' />
|
||||
defaultMessage='The function execution is in progress'
|
||||
/>
|
||||
}
|
||||
state={ busyState } />
|
||||
state={ busyState }
|
||||
/>
|
||||
);
|
||||
} else if (advancedOptions && (step === STEP_BUSY_OR_ADVANCED)) {
|
||||
return (
|
||||
<AdvancedStep
|
||||
gasStore={ this.gasStore }
|
||||
minBlock={ minBlock }
|
||||
minBlockError={ minBlockError }
|
||||
onMinBlockChange={ this.onMinBlockChange } />
|
||||
<AdvancedStep gasStore={ this.gasStore } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -306,6 +321,7 @@ class ExecuteContract extends Component {
|
||||
onFuncChange = (event, func) => {
|
||||
const values = (func.abi.inputs || []).map((input) => {
|
||||
const parsedType = parseAbiType(input.type);
|
||||
|
||||
return parsedType.default;
|
||||
});
|
||||
|
||||
@@ -315,15 +331,6 @@ class ExecuteContract extends Component {
|
||||
}, this.estimateGas);
|
||||
}
|
||||
|
||||
onMinBlockChange = (minBlock) => {
|
||||
const minBlockError = validateUint(minBlock).valueError;
|
||||
|
||||
this.setState({
|
||||
minBlock,
|
||||
minBlockError
|
||||
});
|
||||
}
|
||||
|
||||
onValueChange = (event, index, _value) => {
|
||||
const { func, values, valuesError } = this.state;
|
||||
const input = func.inputs.find((input, _index) => index === _index);
|
||||
@@ -385,17 +392,14 @@ class ExecuteContract extends Component {
|
||||
postTransaction = () => {
|
||||
const { api, store } = this.context;
|
||||
const { fromAddress } = this.props;
|
||||
const { advancedOptions, amount, func, minBlock, values } = this.state;
|
||||
const { advancedOptions, amount, func, values } = this.state;
|
||||
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
||||
const finalstep = steps.length - 1;
|
||||
|
||||
const options = {
|
||||
gas: this.gasStore.gas,
|
||||
gasPrice: this.gasStore.price,
|
||||
const options = this.gasStore.overrideTransaction({
|
||||
from: fromAddress,
|
||||
minBlock: new BigNumber(minBlock || 0).gt(0) ? minBlock : null,
|
||||
value: api.util.toWei(amount || 0)
|
||||
};
|
||||
});
|
||||
|
||||
this.setState({ sending: true, step: advancedOptions ? STEP_BUSY : STEP_BUSY_OR_ADVANCED });
|
||||
|
||||
@@ -406,7 +410,8 @@ class ExecuteContract extends Component {
|
||||
busyState: (
|
||||
<FormattedMessage
|
||||
id='executeContract.busy.waitAuth'
|
||||
defaultMessage='Waiting for authorization in the Parity Signer' />
|
||||
defaultMessage='Waiting for authorization in the Parity Signer'
|
||||
/>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -429,7 +434,8 @@ class ExecuteContract extends Component {
|
||||
busyState: (
|
||||
<FormattedMessage
|
||||
id='executeContract.busy.posted'
|
||||
defaultMessage='Your transaction has been posted to the network' />
|
||||
defaultMessage='Your transaction has been posted to the network'
|
||||
/>
|
||||
)
|
||||
});
|
||||
})
|
||||
|
||||
@@ -18,6 +18,8 @@ import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { CopyToClipboard, QrCode } from '~/ui';
|
||||
|
||||
import Value from '../Value';
|
||||
import styles from '../shapeshift.css';
|
||||
|
||||
@@ -59,9 +61,7 @@ export default class AwaitingDepositStep extends Component {
|
||||
typeSymbol
|
||||
} } />
|
||||
</div>
|
||||
<div className={ styles.hero }>
|
||||
{ depositAddress }
|
||||
</div>
|
||||
{ this.renderAddress(depositAddress, coinSymbol) }
|
||||
<div className={ styles.price }>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
@@ -76,4 +76,42 @@ export default class AwaitingDepositStep extends Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAddress (depositAddress, coinSymbol) {
|
||||
const qrcode = (
|
||||
<QrCode
|
||||
className={ styles.qrcode }
|
||||
value={ depositAddress }
|
||||
/>
|
||||
);
|
||||
let protocolLink = null;
|
||||
|
||||
// TODO: Expand for other coins where protocols are available
|
||||
switch (coinSymbol) {
|
||||
case 'BTC':
|
||||
protocolLink = `bitcoin:${depositAddress}`;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.addressInfo }>
|
||||
{
|
||||
protocolLink
|
||||
? (
|
||||
<a
|
||||
href={ protocolLink }
|
||||
target='_blank'
|
||||
>
|
||||
{ qrcode }
|
||||
</a>
|
||||
)
|
||||
: qrcode
|
||||
}
|
||||
<div className={ styles.address }>
|
||||
<CopyToClipboard data={ depositAddress } />
|
||||
<span>{ depositAddress }</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ import React from 'react';
|
||||
|
||||
import AwaitingDepositStep from './';
|
||||
|
||||
const TEST_ADDRESS = '0x123456789123456789123456789123456789';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render () {
|
||||
component = shallow(
|
||||
@@ -29,6 +32,7 @@ function render () {
|
||||
price: { rate: 0.001, minimum: 0, limit: 1.999 }
|
||||
} } />
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
@@ -47,4 +51,61 @@ describe('modals/Shapeshift/AwaitingDepositStep', () => {
|
||||
render({ depositAddress: 'xyz' });
|
||||
expect(component.find('FormattedMessage').first().props().id).to.match(/awaitingDeposit/);
|
||||
});
|
||||
|
||||
describe('instance methods', () => {
|
||||
describe('renderAddress', () => {
|
||||
let address;
|
||||
|
||||
beforeEach(() => {
|
||||
address = shallow(instance.renderAddress(TEST_ADDRESS));
|
||||
});
|
||||
|
||||
it('renders the address', () => {
|
||||
expect(address.text()).to.contain(TEST_ADDRESS);
|
||||
});
|
||||
|
||||
describe('CopyToClipboard', () => {
|
||||
let copy;
|
||||
|
||||
beforeEach(() => {
|
||||
copy = address.find('Connect(CopyToClipboard)');
|
||||
});
|
||||
|
||||
it('renders the copy', () => {
|
||||
expect(copy.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('passes the address', () => {
|
||||
expect(copy.props().data).to.equal(TEST_ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QrCode', () => {
|
||||
let qr;
|
||||
|
||||
beforeEach(() => {
|
||||
qr = address.find('QrCode');
|
||||
});
|
||||
|
||||
it('renders the QrCode', () => {
|
||||
expect(qr.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('passed the address', () => {
|
||||
expect(qr.props().value).to.equal(TEST_ADDRESS);
|
||||
});
|
||||
|
||||
describe('protocol link', () => {
|
||||
it('does not render a protocol link (unlinked type)', () => {
|
||||
expect(address.find('a')).to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders protocol link for BTC', () => {
|
||||
address = shallow(instance.renderAddress(TEST_ADDRESS, 'BTC'));
|
||||
expect(address.find('a').props().href).to.equal(`bitcoin:${TEST_ADDRESS}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,9 +14,28 @@
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
.body {
|
||||
}
|
||||
|
||||
.addressInfo {
|
||||
text-align: center;
|
||||
|
||||
.address {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin: 0.75em 0;
|
||||
padding: 1em;
|
||||
|
||||
span {
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shapeshift {
|
||||
position: absolute;
|
||||
bottom: 0.5em;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -27,37 +27,22 @@ export default class Extras extends Component {
|
||||
dataError: PropTypes.string,
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
isEth: PropTypes.bool,
|
||||
minBlock: PropTypes.string,
|
||||
minBlockError: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
total: PropTypes.string,
|
||||
totalError: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { gasStore, minBlock, minBlockError, onChange } = this.props;
|
||||
const { gasStore, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{ this.renderData() }
|
||||
<Input
|
||||
error={ minBlockError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transferModal.minBlock.hint'
|
||||
defaultMessage='Only post the transaction after this block' />
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transferModal.minBlock.label'
|
||||
defaultMessage='BlockNumber to send from' />
|
||||
}
|
||||
value={ minBlock }
|
||||
onChange={ this.onEditMinBlock } />
|
||||
<div className={ styles.gaseditor }>
|
||||
<GasPriceEditor
|
||||
store={ gasStore }
|
||||
onChange={ onChange } />
|
||||
onChange={ onChange }
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
@@ -76,23 +61,22 @@ export default class Extras extends Component {
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transfer.advanced.data.hint'
|
||||
defaultMessage='the data to pass through with the transaction' />
|
||||
defaultMessage='the data to pass through with the transaction'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.advanced.data.label'
|
||||
defaultMessage='transaction data' />
|
||||
defaultMessage='transaction data'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditData }
|
||||
value={ data } />
|
||||
value={ data }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onEditData = (event) => {
|
||||
this.props.onChange('data', event.target.value);
|
||||
}
|
||||
|
||||
onEditMinBlock = (event) => {
|
||||
this.props.onChange('minBlock', event.target.value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
@@ -52,9 +52,6 @@ export default class TransferStore {
|
||||
@observable data = '';
|
||||
@observable dataError = null;
|
||||
|
||||
@observable minBlock = '0';
|
||||
@observable minBlockError = null;
|
||||
|
||||
@observable recipient = '';
|
||||
@observable recipientError = ERRORS.requireRecipient;
|
||||
|
||||
@@ -78,6 +75,30 @@ export default class TransferStore {
|
||||
|
||||
gasStore = null;
|
||||
|
||||
constructor (api, props) {
|
||||
this.api = api;
|
||||
|
||||
const { account, balance, gasLimit, senders, newError, sendersBalances } = props;
|
||||
|
||||
this.account = account;
|
||||
this.balance = balance;
|
||||
this.isWallet = account && account.wallet;
|
||||
this.newError = newError;
|
||||
|
||||
this.gasStore = new GasPriceStore(api, { gasLimit });
|
||||
|
||||
if (this.isWallet) {
|
||||
this.wallet = props.wallet;
|
||||
this.walletContract = new Contract(this.api, walletAbi);
|
||||
}
|
||||
|
||||
if (senders) {
|
||||
this.senders = senders;
|
||||
this.sendersBalances = sendersBalances;
|
||||
this.senderError = ERRORS.requireSender;
|
||||
}
|
||||
}
|
||||
|
||||
@computed get steps () {
|
||||
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
|
||||
|
||||
@@ -90,7 +111,7 @@ export default class TransferStore {
|
||||
|
||||
@computed get isValid () {
|
||||
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.minBlockError && !this.totalError;
|
||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.gasStore.conditionBlockError && !this.totalError;
|
||||
const verifyValid = !this.passwordError;
|
||||
|
||||
switch (this.stage) {
|
||||
@@ -111,29 +132,6 @@ export default class TransferStore {
|
||||
return this.balance.tokens.find((balance) => balance.token.tag === this.tag).token;
|
||||
}
|
||||
|
||||
constructor (api, props) {
|
||||
this.api = api;
|
||||
|
||||
const { account, balance, gasLimit, senders, newError, sendersBalances } = props;
|
||||
this.account = account;
|
||||
this.balance = balance;
|
||||
this.isWallet = account && account.wallet;
|
||||
this.newError = newError;
|
||||
|
||||
this.gasStore = new GasPriceStore(api, { gasLimit });
|
||||
|
||||
if (this.isWallet) {
|
||||
this.wallet = props.wallet;
|
||||
this.walletContract = new Contract(this.api, walletAbi);
|
||||
}
|
||||
|
||||
if (senders) {
|
||||
this.senders = senders;
|
||||
this.sendersBalances = sendersBalances;
|
||||
this.senderError = ERRORS.requireSender;
|
||||
}
|
||||
}
|
||||
|
||||
@action onNext = () => {
|
||||
this.stage += 1;
|
||||
}
|
||||
@@ -163,9 +161,6 @@ export default class TransferStore {
|
||||
case 'gasPrice':
|
||||
return this._onUpdateGasPrice(value);
|
||||
|
||||
case 'minBlock':
|
||||
return this._onUpdateMinBlock(value);
|
||||
|
||||
case 'recipient':
|
||||
return this._onUpdateRecipient(value);
|
||||
|
||||
@@ -283,14 +278,6 @@ export default class TransferStore {
|
||||
this.recalculate();
|
||||
}
|
||||
|
||||
@action _onUpdateMinBlock = (minBlock) => {
|
||||
console.log('minBlock', minBlock);
|
||||
transaction(() => {
|
||||
this.minBlock = minBlock;
|
||||
this.minBlockError = this._validatePositiveNumber(minBlock);
|
||||
});
|
||||
}
|
||||
|
||||
@action _onUpdateGasPrice = (gasPrice) => {
|
||||
this.recalculate();
|
||||
}
|
||||
@@ -588,7 +575,7 @@ export default class TransferStore {
|
||||
|
||||
send () {
|
||||
const { options, values } = this._getTransferParams();
|
||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
||||
|
||||
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
||||
|
||||
return this._getTransferMethod().postTransaction(options, values);
|
||||
@@ -596,6 +583,7 @@ export default class TransferStore {
|
||||
|
||||
_estimateGas (forceToken = false) {
|
||||
const { options, values } = this._getTransferParams(true, forceToken);
|
||||
|
||||
return this._getTransferMethod(true, forceToken).estimateGas(options, values);
|
||||
}
|
||||
|
||||
@@ -636,15 +624,12 @@ export default class TransferStore {
|
||||
const to = (isEth && !isWallet) ? this.recipient
|
||||
: (this.isWallet ? this.wallet.address : this.token.address);
|
||||
|
||||
const options = {
|
||||
const options = this.gasStore.overrideTransaction({
|
||||
from: this.sender || this.account.address,
|
||||
to
|
||||
};
|
||||
});
|
||||
|
||||
if (!gas) {
|
||||
options.gas = this.gasStore.gas;
|
||||
options.gasPrice = this.gasStore.price;
|
||||
} else {
|
||||
if (gas) {
|
||||
options.gas = MAX_GAS_ESTIMATION;
|
||||
}
|
||||
|
||||
@@ -681,6 +666,7 @@ export default class TransferStore {
|
||||
_validatePositiveNumber (num) {
|
||||
try {
|
||||
const v = new BigNumber(num);
|
||||
|
||||
if (v.lt(0)) {
|
||||
return ERRORS.invalidAmount;
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ class Transfer extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isEth, data, dataError, minBlock, minBlockError, total, totalError } = this.store;
|
||||
const { isEth, data, dataError, total, totalError } = this.store;
|
||||
|
||||
return (
|
||||
<Extras
|
||||
@@ -214,8 +214,6 @@ class Transfer extends Component {
|
||||
dataError={ dataError }
|
||||
gasStore={ this.store.gasStore }
|
||||
isEth={ isEth }
|
||||
minBlock={ minBlock }
|
||||
minBlockError={ minBlockError }
|
||||
onChange={ this.store.onUpdateDetails }
|
||||
total={ total }
|
||||
totalError={ totalError } />
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import AddAddress from './AddAddress';
|
||||
import AddContract from './AddContract';
|
||||
import AddDapps from './AddDapps';
|
||||
import CreateAccount from './CreateAccount';
|
||||
import CreateWallet from './CreateWallet';
|
||||
import DappPermissions from './DappPermissions';
|
||||
import DappsVisible from './AddDapps';
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import DeployContract from './DeployContract';
|
||||
import EditMeta from './EditMeta';
|
||||
@@ -37,10 +37,10 @@ import WalletSettings from './WalletSettings';
|
||||
export {
|
||||
AddAddress,
|
||||
AddContract,
|
||||
AddDapps,
|
||||
CreateAccount,
|
||||
CreateWallet,
|
||||
DappPermissions,
|
||||
DappsVisible,
|
||||
DeleteAccount,
|
||||
DeployContract,
|
||||
EditMeta,
|
||||
|
||||
Reference in New Issue
Block a user