Split all packages/* to external repos
This commit is contained in:
parent
b5f4c40406
commit
c509733a30
@ -1,54 +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/>.
|
||||
*/
|
||||
|
||||
.body {
|
||||
.hero {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nameinfo {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-transform: uppercase;
|
||||
font-size: 1.25em;
|
||||
padding-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.address {
|
||||
}
|
||||
|
||||
.description {
|
||||
padding-top: 1em;
|
||||
font-size: 0.75em;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.password {
|
||||
padding: 1em 5em;
|
||||
}
|
||||
}
|
@ -1,156 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { newError } from '@parity/shared/redux/actions';
|
||||
import { ConfirmDialog, IdentityIcon, IdentityName, Input } from '@parity/ui';
|
||||
|
||||
import styles from './deleteAccount.css';
|
||||
|
||||
class DeleteAccount extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
router: PropTypes.object
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
newError: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
state = {
|
||||
isBusy: false,
|
||||
password: ''
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { isBusy, password } = this.state;
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
busy={ isBusy }
|
||||
className={ styles.body }
|
||||
onConfirm={ this.onDeleteConfirmed }
|
||||
onDeny={ this.closeDeleteDialog }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='deleteAccount.title'
|
||||
defaultMessage='confirm removal'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={ styles.hero }>
|
||||
<FormattedMessage
|
||||
id='deleteAccount.question'
|
||||
defaultMessage='Are you sure you want to permanently delete the following account?'
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.info }>
|
||||
<IdentityIcon
|
||||
address={ account.address }
|
||||
className={ styles.icon }
|
||||
/>
|
||||
<div className={ styles.nameinfo }>
|
||||
<div className={ styles.header }>
|
||||
<IdentityName
|
||||
address={ account.address }
|
||||
unknown
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.address }>
|
||||
{ account.address }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.description }>
|
||||
{ account.meta.description }
|
||||
</div>
|
||||
<div className={ styles.password }>
|
||||
<Input
|
||||
autoFocus
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='deleteAccount.password.hint'
|
||||
defaultMessage='provide the account password to confirm the account deletion'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='deleteAccount.password.label'
|
||||
defaultMessage='account password'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onChangePassword }
|
||||
onDefaultAction={ this.onDeleteConfirmed }
|
||||
type='password'
|
||||
value={ password }
|
||||
/>
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
);
|
||||
}
|
||||
|
||||
onChangePassword = (event, password) => {
|
||||
this.setState({ password });
|
||||
}
|
||||
|
||||
onDeleteConfirmed = () => {
|
||||
const { api, router } = this.context;
|
||||
const { account, newError } = this.props;
|
||||
const { password } = this.state;
|
||||
|
||||
this.setState({ isBusy: true });
|
||||
|
||||
return api.parity
|
||||
.killAccount(account.address, password)
|
||||
.then((result) => {
|
||||
this.setState({ isBusy: true });
|
||||
|
||||
if (result === true) {
|
||||
router.push('/accounts');
|
||||
this.closeDeleteDialog();
|
||||
} else {
|
||||
newError(new Error('Deletion failed.'));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({ isBusy: false });
|
||||
console.error('onDeleteConfirmed', error);
|
||||
newError(new Error(`Deletion failed: ${error.message}`));
|
||||
});
|
||||
}
|
||||
|
||||
closeDeleteDialog = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({ newError }, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(DeleteAccount);
|
@ -1,139 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import DeleteAccount from './';
|
||||
|
||||
let api;
|
||||
let component;
|
||||
let instance;
|
||||
let onClose;
|
||||
let router;
|
||||
let store;
|
||||
|
||||
const TEST_ADDRESS = '0x123456789012345678901234567890';
|
||||
const TEST_PASSWORD = 'testPassword';
|
||||
|
||||
function createApi () {
|
||||
api = {
|
||||
parity: {
|
||||
killAccount: sinon.stub().resolves(true)
|
||||
}
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
function createRouter () {
|
||||
router = {
|
||||
push: sinon.stub()
|
||||
};
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
function createStore () {
|
||||
store = {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function render () {
|
||||
onClose = sinon.stub();
|
||||
component = shallow(
|
||||
<DeleteAccount
|
||||
account={ {
|
||||
address: TEST_ADDRESS,
|
||||
meta: {
|
||||
description: 'testDescription'
|
||||
}
|
||||
} }
|
||||
onClose={ onClose }
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
store: createStore()
|
||||
}
|
||||
}
|
||||
).find('DeleteAccount').shallow({
|
||||
context: {
|
||||
api: createApi(),
|
||||
router: createRouter()
|
||||
}
|
||||
});
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('modals/DeleteAccount', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('event handlers', () => {
|
||||
describe('onChangePassword', () => {
|
||||
it('sets the state with the new password', () => {
|
||||
instance.onChangePassword(null, TEST_PASSWORD);
|
||||
expect(instance.state.password).to.equal(TEST_PASSWORD);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closeDeleteDialog', () => {
|
||||
it('calls onClose', () => {
|
||||
instance.closeDeleteDialog();
|
||||
expect(onClose).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDeleteConfirmed', () => {
|
||||
beforeEach(() => {
|
||||
sinon.spy(instance, 'closeDeleteDialog');
|
||||
instance.onChangePassword(null, TEST_PASSWORD);
|
||||
return instance.onDeleteConfirmed();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.closeDeleteDialog.restore();
|
||||
});
|
||||
|
||||
it('calls parity_killAccount', () => {
|
||||
expect(api.parity.killAccount).to.have.been.calledWith(TEST_ADDRESS, TEST_PASSWORD);
|
||||
});
|
||||
|
||||
it('changes the route to /accounts', () => {
|
||||
expect(router.push).to.have.been.calledWith('/accounts');
|
||||
});
|
||||
|
||||
it('closes the dialog', () => {
|
||||
expect(instance.closeDeleteDialog).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './deleteAccount';
|
@ -1,214 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { newError } from '@parity/shared/redux/actions';
|
||||
import { Button, Form, Input, InputChip, Portal, VaultSelect } from '@parity/ui';
|
||||
import { CancelIcon, SaveIcon } from '@parity/ui/Icons';
|
||||
|
||||
import VaultStore from '@parity/dapp-vaults/store';
|
||||
|
||||
import Store from './store';
|
||||
|
||||
@observer
|
||||
class EditMeta extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object.isRequired,
|
||||
newError: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
store = new Store(this.context.api, this.props.account);
|
||||
vaultStore = VaultStore.get(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
this.vaultStore.loadVaults();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { description, isBusy, name, nameError, tags } = this.store;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
buttons={ this.renderActions() }
|
||||
busy={ isBusy }
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='editMeta.title'
|
||||
defaultMessage='edit metadata'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Form>
|
||||
<Input
|
||||
autoFocus
|
||||
error={ nameError }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='editMeta.name.label'
|
||||
defaultMessage='name'
|
||||
/>
|
||||
}
|
||||
onSubmit={ this.store.setName }
|
||||
value={ name }
|
||||
/>
|
||||
<Input
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='editMeta.description.hint'
|
||||
defaultMessage='description for this address'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='editMeta.description.label'
|
||||
defaultMessage='address description'
|
||||
/>
|
||||
}
|
||||
value={ description }
|
||||
onSubmit={ this.store.setDescription }
|
||||
/>
|
||||
{ this.renderAccountFields() }
|
||||
<InputChip
|
||||
addOnBlur
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='editMeta.tags.hint'
|
||||
defaultMessage='press <Enter> to add a tag'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='editMeta.tags.label'
|
||||
defaultMessage='(optional) tags'
|
||||
/>
|
||||
}
|
||||
onTokensChange={ this.store.setTags }
|
||||
tokens={ tags.slice() }
|
||||
/>
|
||||
{ this.renderVaultSelector() }
|
||||
</Form>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions () {
|
||||
const { hasError } = this.store;
|
||||
|
||||
return [
|
||||
<Button
|
||||
label='Cancel'
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancel'
|
||||
onClick={ this.onClose }
|
||||
/>,
|
||||
<Button
|
||||
disabled={ hasError }
|
||||
label='Save'
|
||||
icon={ <SaveIcon /> }
|
||||
key='save'
|
||||
onClick={ this.onSave }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
renderAccountFields () {
|
||||
const { isAccount, passwordHint } = this.store;
|
||||
|
||||
if (!isAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='editMeta.passwordHint.hint'
|
||||
defaultMessage='a hint to allow password recovery'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='editMeta.passwordHint.label'
|
||||
defaultMessage='(optional) password hint'
|
||||
/>
|
||||
}
|
||||
value={ passwordHint }
|
||||
onSubmit={ this.store.setPasswordHint }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderVaultSelector () {
|
||||
const { isAccount, vaultName } = this.store;
|
||||
|
||||
if (!isAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VaultSelect
|
||||
onSelect={ this.setVaultName }
|
||||
value={ vaultName }
|
||||
vaultStore={ this.vaultStore }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
onSave = () => {
|
||||
if (this.store.hasError) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.store
|
||||
.save(this.vaultStore)
|
||||
.then(this.onClose)
|
||||
.catch((error) => {
|
||||
this.props.newError(error);
|
||||
});
|
||||
}
|
||||
|
||||
setVaultName = (vaultName) => {
|
||||
this.store.setVaultName(vaultName);
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
newError
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(EditMeta);
|
@ -1,87 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import EditMeta from './';
|
||||
|
||||
import { ACCOUNT, createApi, createRedux } from './editMeta.test.js';
|
||||
|
||||
let api;
|
||||
let component;
|
||||
let instance;
|
||||
let onClose;
|
||||
let reduxStore;
|
||||
|
||||
function render (props) {
|
||||
api = createApi();
|
||||
onClose = sinon.stub();
|
||||
reduxStore = createRedux();
|
||||
|
||||
component = shallow(
|
||||
<EditMeta
|
||||
{ ...props }
|
||||
account={ ACCOUNT }
|
||||
onClose={ onClose }
|
||||
/>,
|
||||
{ context: { store: reduxStore } }
|
||||
).find('EditMeta').shallow({ context: { api } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('modals/EditMeta', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
describe('onSave', () => {
|
||||
it('calls store.save', () => {
|
||||
sinon.spy(instance.store, 'save');
|
||||
|
||||
return instance.onSave().then(() => {
|
||||
expect(instance.store.save).to.have.been.called;
|
||||
instance.store.save.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('closes the dialog on success', () => {
|
||||
return instance.onSave().then(() => {
|
||||
expect(onClose).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('adds newError on failure', () => {
|
||||
sinon.stub(instance.store, 'save').rejects('test');
|
||||
|
||||
return instance.onSave().then(() => {
|
||||
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||
instance.store.save.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,70 +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/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
const ACCOUNT = {
|
||||
address: '0x123456789a123456789a123456789a123456789a',
|
||||
meta: {
|
||||
description: 'Call me bob',
|
||||
passwordHint: 'some hint',
|
||||
tags: ['testing']
|
||||
},
|
||||
name: 'Bobby',
|
||||
uuid: '123-456'
|
||||
};
|
||||
|
||||
const ADDRESS = {
|
||||
address: '0x0123456789012345678901234567890123456789',
|
||||
meta: {
|
||||
description: 'Some address',
|
||||
extraMeta: {
|
||||
some: 'random',
|
||||
extra: {
|
||||
meta: 'data'
|
||||
}
|
||||
}
|
||||
},
|
||||
name: 'Random address'
|
||||
};
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
parity: {
|
||||
setAccountName: sinon.stub().resolves(),
|
||||
setAccountMeta: sinon.stub().resolves(),
|
||||
listVaults: sinon.stub().resolves([]),
|
||||
listOpenedVaults: sinon.stub().resolves([])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
ACCOUNT,
|
||||
ADDRESS,
|
||||
createApi,
|
||||
createRedux
|
||||
};
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './editMeta';
|
@ -1,121 +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/>.
|
||||
|
||||
import { action, computed, observable, transaction } from 'mobx';
|
||||
|
||||
import { validateName } from '@parity/shared/util/validation';
|
||||
|
||||
export default class Store {
|
||||
@observable address = null;
|
||||
@observable isAccount = false;
|
||||
@observable isBusy = false;
|
||||
@observable description = null;
|
||||
@observable meta = null;
|
||||
@observable name = null;
|
||||
@observable nameError = null;
|
||||
@observable passwordHint = null;
|
||||
@observable tags = null;
|
||||
@observable vaultName = null;
|
||||
|
||||
constructor (api, account) {
|
||||
const { address, name, meta, uuid } = account;
|
||||
|
||||
this._api = api;
|
||||
|
||||
transaction(() => {
|
||||
this.address = address;
|
||||
this.meta = meta || {};
|
||||
this.name = name || '';
|
||||
this.isAccount = !!uuid;
|
||||
|
||||
this.description = this.meta.description || '';
|
||||
this.passwordHint = this.meta.passwordHint || '';
|
||||
this.tags = this.meta.tags && this.meta.tags.slice() || [];
|
||||
this.vaultName = this.meta.vault;
|
||||
});
|
||||
}
|
||||
|
||||
@computed get hasError () {
|
||||
return !!(this.nameError);
|
||||
}
|
||||
|
||||
@action setDescription = (description) => {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@action setName = (_name) => {
|
||||
const { name, nameError } = validateName(_name);
|
||||
|
||||
transaction(() => {
|
||||
this.name = name;
|
||||
this.setNameError(nameError);
|
||||
});
|
||||
}
|
||||
|
||||
@action setNameError = (nameError) => {
|
||||
this.nameError = nameError;
|
||||
}
|
||||
|
||||
@action setPasswordHint = (passwordHint) => {
|
||||
this.passwordHint = passwordHint;
|
||||
}
|
||||
|
||||
@action setBusy = (isBusy) => {
|
||||
this.isBusy = isBusy;
|
||||
}
|
||||
|
||||
@action setTags = (tags) => {
|
||||
this.tags = tags.slice();
|
||||
}
|
||||
|
||||
@action setVaultName = (vaultName) => {
|
||||
this.vaultName = vaultName;
|
||||
}
|
||||
|
||||
save (vaultStore) {
|
||||
this.setBusy(true);
|
||||
|
||||
const meta = {
|
||||
description: this.description,
|
||||
tags: this.tags.peek()
|
||||
};
|
||||
|
||||
if (this.isAccount) {
|
||||
meta.passwordHint = this.passwordHint;
|
||||
}
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.setAccountName(this.address, this.name),
|
||||
this._api.parity.setAccountMeta(this.address, Object.assign({}, this.meta, meta))
|
||||
])
|
||||
.then(() => {
|
||||
if (vaultStore && this.isAccount && (this.meta.vault !== this.vaultName)) {
|
||||
return vaultStore.moveAccount(this.vaultName, this.address);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.then(() => {
|
||||
this.setBusy(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('onSave', error);
|
||||
this.setBusy(false);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,235 +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/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Store from './store';
|
||||
import { ACCOUNT, ADDRESS, createApi } from './editMeta.test.js';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
let vaultStore;
|
||||
|
||||
function createVaultStore () {
|
||||
return {
|
||||
moveAccount: sinon.stub().resolves(true)
|
||||
};
|
||||
}
|
||||
|
||||
function createStore (account) {
|
||||
api = createApi();
|
||||
vaultStore = createVaultStore();
|
||||
|
||||
store = new Store(api, account);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('modals/EditMeta/Store', () => {
|
||||
describe('constructor', () => {
|
||||
describe('accounts', () => {
|
||||
beforeEach(() => {
|
||||
createStore(ACCOUNT);
|
||||
});
|
||||
|
||||
it('flags it as an account', () => {
|
||||
expect(store.isAccount).to.be.true;
|
||||
});
|
||||
|
||||
it('extracts the address', () => {
|
||||
expect(store.address).to.equal(ACCOUNT.address);
|
||||
});
|
||||
|
||||
it('extracts the name', () => {
|
||||
expect(store.name).to.equal(ACCOUNT.name);
|
||||
});
|
||||
|
||||
it('extracts the tags', () => {
|
||||
expect(store.tags).to.deep.equal(ACCOUNT.meta.tags);
|
||||
});
|
||||
|
||||
describe('meta', () => {
|
||||
it('extracts the full meta', () => {
|
||||
expect(store.meta).to.deep.equal(ACCOUNT.meta);
|
||||
});
|
||||
|
||||
it('extracts the description', () => {
|
||||
expect(store.description).to.equal(ACCOUNT.meta.description);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addresses', () => {
|
||||
beforeEach(() => {
|
||||
createStore(ADDRESS);
|
||||
});
|
||||
|
||||
it('flags it as not an account', () => {
|
||||
expect(store.isAccount).to.be.false;
|
||||
});
|
||||
|
||||
it('extracts the address', () => {
|
||||
expect(store.address).to.equal(ADDRESS.address);
|
||||
});
|
||||
|
||||
it('extracts the name', () => {
|
||||
expect(store.name).to.equal(ADDRESS.name);
|
||||
});
|
||||
|
||||
it('extracts the tags (empty)', () => {
|
||||
expect(store.tags.peek()).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@computed', () => {
|
||||
beforeEach(() => {
|
||||
createStore(ADDRESS);
|
||||
});
|
||||
|
||||
describe('hasError', () => {
|
||||
it('is false when no nameError', () => {
|
||||
store.setNameError(null);
|
||||
expect(store.hasError).to.be.false;
|
||||
});
|
||||
|
||||
it('is false with a nameError', () => {
|
||||
store.setNameError('some error');
|
||||
expect(store.hasError).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('@actions', () => {
|
||||
beforeEach(() => {
|
||||
createStore(ADDRESS);
|
||||
});
|
||||
|
||||
describe('setBusy', () => {
|
||||
it('sets the isBusy flag', () => {
|
||||
store.setBusy('testing');
|
||||
expect(store.isBusy).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDescription', () => {
|
||||
it('sets the description', () => {
|
||||
store.setDescription('description');
|
||||
expect(store.description).to.equal('description');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setName', () => {
|
||||
it('sets the name', () => {
|
||||
store.setName('valid name');
|
||||
expect(store.name).to.equal('valid name');
|
||||
expect(store.nameError).to.be.null;
|
||||
});
|
||||
|
||||
it('sets name and error on invalid', () => {
|
||||
store.setName('');
|
||||
expect(store.name).to.equal('');
|
||||
expect(store.nameError).not.to.be.null;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPasswordHint', () => {
|
||||
it('sets the description', () => {
|
||||
store.setPasswordHint('passwordHint');
|
||||
expect(store.passwordHint).to.equal('passwordHint');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTags', () => {
|
||||
it('sets the tags', () => {
|
||||
store.setTags(['taga', 'tagb']);
|
||||
expect(store.tags.peek()).to.deep.equal(['taga', 'tagb']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setVaultName', () => {
|
||||
it('sets the name', () => {
|
||||
store.setVaultName('testing');
|
||||
expect(store.vaultName).to.equal('testing');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('save', () => {
|
||||
beforeEach(() => {
|
||||
createStore(ACCOUNT);
|
||||
sinon.spy(store, 'setBusy');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.setBusy.restore();
|
||||
});
|
||||
|
||||
it('sets the busy flag, clearing it when done', () => {
|
||||
return store.save().then(() => {
|
||||
expect(store.setBusy).to.have.been.calledWith(true);
|
||||
expect(store.setBusy).to.have.been.calledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls parity.setAccountName with the set value', () => {
|
||||
store.setName('test name');
|
||||
|
||||
return store.save().then(() => {
|
||||
expect(api.parity.setAccountName).to.be.calledWith(ACCOUNT.address, 'test name');
|
||||
});
|
||||
});
|
||||
|
||||
it('calls parity.setAccountMeta with the adjusted values', () => {
|
||||
store.setDescription('some new description');
|
||||
store.setPasswordHint('some new passwordhint');
|
||||
store.setTags(['taga']);
|
||||
|
||||
return store.save().then(() => {
|
||||
expect(api.parity.setAccountMeta).to.have.been.calledWith(
|
||||
ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
|
||||
description: 'some new description',
|
||||
passwordHint: 'some new passwordhint',
|
||||
tags: ['taga']
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('moves vault account when applicable', () => {
|
||||
store.setVaultName('testing');
|
||||
|
||||
return store.save(vaultStore).then(() => {
|
||||
expect(vaultStore.moveAccount).to.have.been.calledWith('testing', ACCOUNT.address);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls parity.setAccountMeta with the adjusted values', () => {
|
||||
store.setDescription('some new description');
|
||||
store.setPasswordHint('some new passwordhint');
|
||||
store.setTags(['taga']);
|
||||
store.save();
|
||||
|
||||
expect(api.parity.setAccountMeta).to.have.been.calledWith(ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
|
||||
description: 'some new description',
|
||||
passwordHint: 'some new passwordhint',
|
||||
tags: ['taga']
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,163 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { txLink } from '@parity/etherscan/links';
|
||||
import { Button, ModalBox, Portal, ShortenedHash } from '@parity/ui';
|
||||
import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '@parity/ui/Icons';
|
||||
|
||||
import Store from './store';
|
||||
|
||||
@observer
|
||||
export default class Faucet extends Component {
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
store = new Store(this.props.netVersion, this.props.address);
|
||||
|
||||
render () {
|
||||
const { error, isBusy, isCompleted } = this.store;
|
||||
|
||||
let icon = <DialIcon />;
|
||||
|
||||
if (isCompleted) {
|
||||
icon = error
|
||||
? <ErrorIcon />
|
||||
: <DoneIcon />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal
|
||||
buttons={ this.renderActions() }
|
||||
busy={ isBusy }
|
||||
isSmallModal
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='faucet.title'
|
||||
defaultMessage='Kovan ETH Faucet'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ModalBox
|
||||
icon={ icon }
|
||||
summary={
|
||||
isCompleted
|
||||
? this.renderSummaryDone()
|
||||
: this.renderSummaryRequest()
|
||||
}
|
||||
/>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions = () => {
|
||||
const { canTransact, isBusy, isCompleted } = this.store;
|
||||
|
||||
return isCompleted || isBusy
|
||||
? (
|
||||
<Button
|
||||
disabled={ isBusy }
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='faucet.buttons.done'
|
||||
defaultMessage='close'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>
|
||||
)
|
||||
: [
|
||||
<Button
|
||||
icon={ <CloseIcon /> }
|
||||
key='close'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='faucet.buttons.close'
|
||||
defaultMessage='close'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>,
|
||||
<Button
|
||||
disabled={ !canTransact }
|
||||
icon={ <SendIcon /> }
|
||||
key='request'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='faucet.buttons.request'
|
||||
defaultMessage='request'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onExecute }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
renderSummaryDone () {
|
||||
const { error, responseText, responseTxHash } = this.store;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='faucet.summary.done'
|
||||
defaultMessage='Your Kovan ETH has been requested from the faucet which responded with -'
|
||||
/>
|
||||
{
|
||||
error
|
||||
? (
|
||||
<p>{ error }</p>
|
||||
)
|
||||
: (
|
||||
<p>
|
||||
<span>{ responseText } </span>
|
||||
<a href={ txLink(responseTxHash, false, '42') } target='_blank'>
|
||||
<ShortenedHash data={ responseTxHash } />
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSummaryRequest () {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='faucet.summary.info'
|
||||
defaultMessage='To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet. Once executed the faucet will deposit Kovan ETH into the current account.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
onExecute = () => {
|
||||
return this.store.makeItRain();
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './faucet';
|
@ -1,127 +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/>.
|
||||
|
||||
import { action, computed, observable, transaction } from 'mobx';
|
||||
|
||||
import apiutil from '@parity/api/util';
|
||||
|
||||
const ENDPOINT = 'http://faucet.kovan.network/api/';
|
||||
|
||||
export default class Store {
|
||||
@observable addressReceive = null;
|
||||
@observable addressVerified = null;
|
||||
@observable error = null;
|
||||
@observable responseText = null;
|
||||
@observable responseTxHash = null;
|
||||
@observable isBusy = false;
|
||||
@observable isCompleted = false;
|
||||
@observable isDestination = false;
|
||||
@observable isDone = false;
|
||||
|
||||
constructor (netVersion, address) {
|
||||
transaction(() => {
|
||||
this.setDestination(netVersion === '42');
|
||||
|
||||
this.setAddressReceive(address);
|
||||
this.setAddressVerified(address);
|
||||
});
|
||||
}
|
||||
|
||||
@computed get canTransact () {
|
||||
return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
|
||||
}
|
||||
|
||||
@computed get addressReceiveValid () {
|
||||
return apiutil.isAddressValid(this.addressReceive);
|
||||
}
|
||||
|
||||
@computed get addressVerifiedValid () {
|
||||
return apiutil.isAddressValid(this.addressVerified);
|
||||
}
|
||||
|
||||
@action setAddressReceive = (address) => {
|
||||
this.addressReceive = address;
|
||||
}
|
||||
|
||||
@action setAddressVerified = (address) => {
|
||||
this.addressVerified = address;
|
||||
}
|
||||
|
||||
@action setBusy = (isBusy) => {
|
||||
this.isBusy = isBusy;
|
||||
}
|
||||
|
||||
@action setCompleted = (isCompleted) => {
|
||||
transaction(() => {
|
||||
this.setBusy(false);
|
||||
this.isCompleted = isCompleted;
|
||||
});
|
||||
}
|
||||
|
||||
@action setDestination = (isDestination) => {
|
||||
this.isDestination = isDestination;
|
||||
}
|
||||
|
||||
@action setError = (error) => {
|
||||
if (error.indexOf('not certified') !== -1) {
|
||||
this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
|
||||
} else {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
@action setResponse = (response) => {
|
||||
this.responseText = response.result;
|
||||
this.responseTxHash = response.tx;
|
||||
}
|
||||
|
||||
makeItRain = () => {
|
||||
this.setBusy(true);
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
mode: 'cors'
|
||||
};
|
||||
const url = `${ENDPOINT}${this.addressVerified}`;
|
||||
|
||||
return fetch(url, options)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.catch(() => {
|
||||
return null;
|
||||
})
|
||||
.then((response) => {
|
||||
transaction(() => {
|
||||
if (!response || response.error) {
|
||||
this.setError(
|
||||
response
|
||||
? response.error
|
||||
: 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
|
||||
);
|
||||
} else {
|
||||
this.setResponse(response);
|
||||
}
|
||||
|
||||
this.setCompleted(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,86 +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/>.
|
||||
*/
|
||||
|
||||
.editicon {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin: 0 156px 0 0;
|
||||
}
|
||||
|
||||
.identityIcon {
|
||||
float: left;
|
||||
margin-right: -100%;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
float: right;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.addressline,
|
||||
.infoline,
|
||||
.uuidline,
|
||||
.vault,
|
||||
.title {
|
||||
margin-left: 72px;
|
||||
}
|
||||
|
||||
.addressline,
|
||||
.infoline,
|
||||
.uuidline {
|
||||
line-height: 1.618em;
|
||||
|
||||
&.bigaddress {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.infoline,
|
||||
.uuidline {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.uuidline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.vault {
|
||||
line-height: 32px;
|
||||
|
||||
.text {
|
||||
display: inline-block;
|
||||
opacity: 0.25;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.addressline {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.address {
|
||||
display: inline-block;
|
||||
margin-left: 0.5em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tags {
|
||||
clear: both;
|
||||
}
|
@ -1,214 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Balance, Certifications, Container, CopyToClipboard, ContainerTitle, IdentityIcon, IdentityName, QrCode, Tags, VaultTag } from '@parity/ui';
|
||||
|
||||
import styles from './header.css';
|
||||
|
||||
export default class Header extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
hideName: PropTypes.bool,
|
||||
isContract: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
children: null,
|
||||
className: '',
|
||||
hideName: false,
|
||||
isContract: false
|
||||
};
|
||||
|
||||
state = {
|
||||
txCount: null
|
||||
};
|
||||
|
||||
txCountSubId = null;
|
||||
|
||||
componentWillMount () {
|
||||
if (this.props.account && !this.props.isContract) {
|
||||
this.subscribeTxCount();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unsubscribeTxCount();
|
||||
}
|
||||
|
||||
subscribeTxCount () {
|
||||
const { api } = this.context;
|
||||
|
||||
api
|
||||
.subscribe('eth_blockNumber', (error) => {
|
||||
if (error) {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
api.eth.getTransactionCount(this.props.account.address)
|
||||
.then((txCount) => this.setState({ txCount }));
|
||||
})
|
||||
.then((subscriptionId) => {
|
||||
this.txCountSubId = subscriptionId;
|
||||
});
|
||||
}
|
||||
|
||||
unsubscribeTxCount () {
|
||||
if (!this.txCountSubId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.api.unsubscribe(this.txCountSubId);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, children, className, disabled, hideName } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address } = account;
|
||||
const meta = account.meta || {};
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
<Container>
|
||||
<QrCode
|
||||
className={ styles.qrcode }
|
||||
value={ address }
|
||||
/>
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
className={ styles.identityIcon }
|
||||
disabled={ disabled }
|
||||
/>
|
||||
<div className={ styles.info }>
|
||||
{ this.renderName() }
|
||||
<div className={ [ hideName ? styles.bigaddress : '', styles.addressline ].join(' ') }>
|
||||
<CopyToClipboard data={ address } />
|
||||
<div className={ styles.address }>
|
||||
{ address }
|
||||
</div>
|
||||
</div>
|
||||
{ this.renderUuid() }
|
||||
<div className={ styles.infoline }>
|
||||
{ meta.description }
|
||||
</div>
|
||||
{ this.renderTxCount() }
|
||||
<div className={ styles.balances }>
|
||||
<Balance
|
||||
address={ address }
|
||||
/>
|
||||
<Certifications address={ address } />
|
||||
{ this.renderVault() }
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.tags }>
|
||||
<Tags tags={ meta.tags } />
|
||||
</div>
|
||||
{ children }
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderName () {
|
||||
const { hideName } = this.props;
|
||||
|
||||
if (hideName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address } = this.props.account;
|
||||
|
||||
return (
|
||||
<ContainerTitle
|
||||
className={ styles.title }
|
||||
title={
|
||||
<IdentityName
|
||||
address={ address }
|
||||
unknown
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderTxCount () {
|
||||
const { isContract } = this.props;
|
||||
const { txCount } = this.state;
|
||||
|
||||
if (!txCount || isContract) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.infoline }>
|
||||
<FormattedMessage
|
||||
id='account.header.outgoingTransactions'
|
||||
defaultMessage='{count} outgoing transactions'
|
||||
values={ {
|
||||
count: txCount.toFormat()
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUuid () {
|
||||
const { uuid } = this.props.account;
|
||||
|
||||
if (!uuid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.uuidline }>
|
||||
<FormattedMessage
|
||||
id='account.header.uuid'
|
||||
defaultMessage='uuid: {uuid}'
|
||||
values={ {
|
||||
uuid
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderVault () {
|
||||
const { account } = this.props;
|
||||
const { meta } = account;
|
||||
|
||||
if (!meta || !meta.vault) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<VaultTag vault={ meta.vault } />
|
||||
);
|
||||
}
|
||||
}
|
@ -1,247 +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/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { ETH_TOKEN } from '@parity/shared/util/tokens';
|
||||
|
||||
import Header from './';
|
||||
|
||||
const ACCOUNT = {
|
||||
address: '0x0123456789012345678901234567890123456789',
|
||||
meta: {
|
||||
description: 'the description',
|
||||
tags: ['taga', 'tagb']
|
||||
},
|
||||
uuid: '0xabcdef'
|
||||
};
|
||||
const subscriptions = {};
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
const api = {
|
||||
subscribe: (method, callback) => {
|
||||
subscriptions[method] = (subscriptions[method] || []).concat(callback);
|
||||
return Promise.resolve(0);
|
||||
},
|
||||
eth: {
|
||||
getTransactionCount: () => Promise.resolve(new BigNumber(1))
|
||||
}
|
||||
};
|
||||
|
||||
function reduxStore () {
|
||||
const getState = () => ({
|
||||
balances: {},
|
||||
tokens: {
|
||||
[ETH_TOKEN.id]: ETH_TOKEN
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
getState,
|
||||
dispatch: () => null,
|
||||
subscribe: () => null
|
||||
};
|
||||
}
|
||||
|
||||
function render (props = {}) {
|
||||
if (props && !props.account) {
|
||||
props.account = ACCOUNT;
|
||||
}
|
||||
|
||||
component = shallow(
|
||||
<Header { ...props } />,
|
||||
{ context: { api } }
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Header', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders null with no account', () => {
|
||||
expect(render(null).find('div')).to.have.length(0);
|
||||
});
|
||||
|
||||
it('renders when no account meta', () => {
|
||||
expect(render({ account: { address: ACCOUNT.address } })).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders when no account description', () => {
|
||||
expect(render({ account: { address: ACCOUNT.address, meta: { tags: [] } } })).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders when no account tags', () => {
|
||||
expect(render({ account: { address: ACCOUNT.address, meta: { description: 'something' } } })).to.be.ok;
|
||||
});
|
||||
|
||||
describe('sections', () => {
|
||||
describe('Balance', () => {
|
||||
let balance;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
balance = component.find('Connect(Balance)')
|
||||
.shallow({ context: { store: reduxStore() } });
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(balance).to.have.length(1);
|
||||
});
|
||||
|
||||
it('passes the account', () => {
|
||||
expect(balance.props().address).to.deep.equal(ACCOUNT.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Certifications', () => {
|
||||
let certs;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
certs = component.find('Connect(Certifications)');
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(certs).to.have.length(1);
|
||||
});
|
||||
|
||||
it('passes the address', () => {
|
||||
expect(certs.props().address).to.deep.equal(ACCOUNT.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('IdentityIcon', () => {
|
||||
let icon;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
icon = component.find('IdentityIcon');
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(icon).to.have.length(1);
|
||||
});
|
||||
|
||||
it('passes the address', () => {
|
||||
expect(icon.props().address).to.deep.equal(ACCOUNT.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QrCode', () => {
|
||||
let qr;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
qr = component.find('QrCode');
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(qr).to.have.length(1);
|
||||
});
|
||||
|
||||
it('passes the address', () => {
|
||||
expect(qr.props().value).to.deep.equal(ACCOUNT.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tags', () => {
|
||||
let tags;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
tags = component.find('Tags');
|
||||
});
|
||||
|
||||
it('renders', () => {
|
||||
expect(tags).to.have.length(1);
|
||||
});
|
||||
|
||||
it('passes the tags', () => {
|
||||
expect(tags.props().tags).to.deep.equal(ACCOUNT.meta.tags);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderName', () => {
|
||||
it('renders null with hideName', () => {
|
||||
render({ hideName: true });
|
||||
expect(instance.renderName()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the name', () => {
|
||||
render();
|
||||
expect(instance.renderName()).not.to.be.null;
|
||||
});
|
||||
|
||||
it('renders when no address specified', () => {
|
||||
render({ account: {} });
|
||||
expect(instance.renderName()).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderTxCount', () => {
|
||||
it('renders null when txCount is null', () => {
|
||||
render();
|
||||
expect(instance.renderTxCount()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders null when contract', () => {
|
||||
render({ isContract: true });
|
||||
|
||||
subscriptions['eth_blockNumber'].forEach((callback) => {
|
||||
callback();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(instance.renderTxCount()).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the tx count', () => {
|
||||
render();
|
||||
|
||||
subscriptions['eth_blockNumber'].forEach((callback) => {
|
||||
callback();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(instance.renderTxCount()).not.to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderUuid', () => {
|
||||
it('renders null with no uuid', () => {
|
||||
render({ account: Object.assign({}, ACCOUNT, { uuid: null }) });
|
||||
expect(instance.renderUuid()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the uuid', () => {
|
||||
render();
|
||||
expect(instance.renderUuid()).not.to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './header';
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './passwordManager.js';
|
@ -1,86 +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/>.
|
||||
*/
|
||||
|
||||
.accountContainer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.accountInfos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.accountInfos > * {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
.hintLabel {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.7rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.accountAddress {
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.accountName {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.passwords {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.password {
|
||||
flex: 0 1 50%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.passwordHint {
|
||||
font-size: 0.9rem;
|
||||
color: lightgrey;
|
||||
}
|
||||
|
||||
.message {
|
||||
border: 1px solid #ddd;
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
height: 2.5rem;
|
||||
text-align: center;
|
||||
line-height: 2.5rem;
|
||||
transition: height 350ms 0;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hideMessage {
|
||||
height: 0;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.form {
|
||||
box-sizing: border-box;
|
||||
margin-top: 0;
|
||||
padding: 0.75rem 1.5rem 1.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
@ -1,424 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { newError, openSnackbar } from '@parity/shared/redux/actions';
|
||||
import { Button, IdentityName, IdentityIcon, Portal, Tabs } from '@parity/ui';
|
||||
import PasswordStrength from '@parity/ui/Form/PasswordStrength';
|
||||
import Form, { Input } from '@parity/ui/Form';
|
||||
import { CancelIcon, CheckIcon, SendIcon } from '@parity/ui/Icons';
|
||||
|
||||
import Store from './store';
|
||||
import styles from './passwordManager.css';
|
||||
|
||||
const MSG_SUCCESS_STYLE = {
|
||||
backgroundColor: 'rgba(174, 213, 129, 0.75)'
|
||||
};
|
||||
const MSG_FAILURE_STYLE = {
|
||||
backgroundColor: 'rgba(229, 115, 115, 0.75)'
|
||||
};
|
||||
|
||||
@observer
|
||||
class PasswordManager extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.object.isRequired,
|
||||
openSnackbar: PropTypes.func.isRequired,
|
||||
newError: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func
|
||||
}
|
||||
|
||||
store = new Store(this.context.api, this.props.account);
|
||||
|
||||
render () {
|
||||
const { busy } = this.store;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
busy={ busy }
|
||||
buttons={ this.renderDialogActions() }
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='passwordChange.title'
|
||||
defaultMessage='Password Manager'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ this.renderAccount() }
|
||||
{ this.renderPage() }
|
||||
{ this.renderMessage() }
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderMessage () {
|
||||
const { infoMessage } = this.store;
|
||||
|
||||
if (!infoMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ styles.message }
|
||||
style={
|
||||
infoMessage.success
|
||||
? MSG_SUCCESS_STYLE
|
||||
: MSG_FAILURE_STYLE
|
||||
}
|
||||
>
|
||||
{ infoMessage.value }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAccount () {
|
||||
const { address, passwordHint } = this.store;
|
||||
|
||||
return (
|
||||
<div className={ styles.accountContainer }>
|
||||
<IdentityIcon address={ address } />
|
||||
<div className={ styles.accountInfos }>
|
||||
<IdentityName
|
||||
address={ address }
|
||||
className={ styles.accountName }
|
||||
unknown
|
||||
/>
|
||||
<span className={ styles.accountAddress }>
|
||||
{ address }
|
||||
</span>
|
||||
<span className={ styles.passwordHint }>
|
||||
<span className={ styles.hintLabel }>
|
||||
<FormattedMessage
|
||||
id='passwordChange.passwordHint.display'
|
||||
defaultMessage='Hint {hint}'
|
||||
values={ {
|
||||
hint: passwordHint || '-'
|
||||
} }
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPage () {
|
||||
const { activeTab } = this.store;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tabs
|
||||
activeTab={ activeTab }
|
||||
tabs={ [
|
||||
<FormattedMessage
|
||||
id='passwordChange.tabChange'
|
||||
defaultMessage='Change Password'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='passwordChange.tabTest'
|
||||
defaultMessage='TestPassword'
|
||||
/>
|
||||
] }
|
||||
onChange={ this.onChangeTab }
|
||||
/>
|
||||
{
|
||||
activeTab === 1
|
||||
? this.renderTabTest()
|
||||
: this.renderTabChange()
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeTab = (event, activeTab) => {
|
||||
this.store.setActiveTab(activeTab);
|
||||
}
|
||||
|
||||
renderTabTest () {
|
||||
const { busy } = this.store;
|
||||
|
||||
return (
|
||||
<Form className={ styles.form }>
|
||||
<div>
|
||||
<Input
|
||||
autoFocus
|
||||
disabled={ busy }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='passwordChange.testPassword.hint'
|
||||
defaultMessage='your account password'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.testPassword.label'
|
||||
defaultMessage='password'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditTestPassword }
|
||||
onSubmit={ this.testPassword }
|
||||
submitOnBlur={ false }
|
||||
type='password'
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
renderTabChange () {
|
||||
const { busy, isRepeatValid, newPassword, passwordHint } = this.store;
|
||||
|
||||
return (
|
||||
<Form className={ styles.form }>
|
||||
<div>
|
||||
<Input
|
||||
autoFocus
|
||||
disabled={ busy }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='passwordChange.currentPassword.hint'
|
||||
defaultMessage='your current password for this account'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.currentPassword.label'
|
||||
defaultMessage='current password'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditCurrentPassword }
|
||||
type='password'
|
||||
/>
|
||||
<Input
|
||||
disabled={ busy }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='passwordChange.passwordHint.hint'
|
||||
defaultMessage='hint for the new password'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.passwordHint.label'
|
||||
defaultMessage='(optional) new password hint'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditNewPasswordHint }
|
||||
value={ passwordHint }
|
||||
/>
|
||||
<div className={ styles.passwords }>
|
||||
<div className={ styles.password }>
|
||||
<Input
|
||||
disabled={ busy }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='passwordChange.newPassword.hint'
|
||||
defaultMessage='the new password for this account'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.newPassword.label'
|
||||
defaultMessage='new password'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditNewPassword }
|
||||
onSubmit={ this.changePassword }
|
||||
submitOnBlur={ false }
|
||||
type='password'
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.password }>
|
||||
<Input
|
||||
disabled={ busy }
|
||||
error={
|
||||
isRepeatValid
|
||||
? null
|
||||
: <FormattedMessage
|
||||
id='passwordChange.repeatPassword.error'
|
||||
defaultMessage='the supplied passwords do not match'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='passwordChange.repeatPassword.hint'
|
||||
defaultMessage='repeat the new password for this account'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.repeatPassword.label'
|
||||
defaultMessage='repeat new password'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditNewPasswordRepeat }
|
||||
onSubmit={ this.changePassword }
|
||||
submitOnBlur={ false }
|
||||
type='password'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PasswordStrength input={ newPassword } />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
renderDialogActions () {
|
||||
const { activeTab, busy, isRepeatValid } = this.store;
|
||||
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancel'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.button.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>
|
||||
);
|
||||
|
||||
if (busy) {
|
||||
return [
|
||||
cancelBtn,
|
||||
<Button
|
||||
disabled
|
||||
key='wait'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.button.wait'
|
||||
defaultMessage='Wait...'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
if (activeTab === 1) {
|
||||
return [
|
||||
cancelBtn,
|
||||
<Button
|
||||
icon={ <CheckIcon /> }
|
||||
key='test'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.button.test'
|
||||
defaultMessage='Test'
|
||||
/>
|
||||
}
|
||||
onClick={ this.testPassword }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
cancelBtn,
|
||||
<Button
|
||||
disabled={ !isRepeatValid }
|
||||
icon={ <SendIcon /> }
|
||||
key='change'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='passwordChange.button.change'
|
||||
defaultMessage='Change'
|
||||
/>
|
||||
}
|
||||
onClick={ this.changePassword }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
onEditCurrentPassword = (event, password) => {
|
||||
this.store.setPassword(password);
|
||||
}
|
||||
|
||||
onEditNewPassword = (event, password) => {
|
||||
this.store.setNewPassword(password);
|
||||
}
|
||||
|
||||
onEditNewPasswordHint = (event, passwordHint) => {
|
||||
this.store.setNewPasswordHint(passwordHint);
|
||||
}
|
||||
|
||||
onEditNewPasswordRepeat = (event, password) => {
|
||||
this.store.setNewPasswordRepeat(password);
|
||||
}
|
||||
|
||||
onEditTestPassword = (event, password) => {
|
||||
this.store.setValidatePassword(password);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
changePassword = () => {
|
||||
return this.store
|
||||
.changePassword()
|
||||
.then((result) => {
|
||||
if (result) {
|
||||
this.props.openSnackbar(
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='passwordChange.success'
|
||||
defaultMessage='Your password has been successfully changed'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
this.onClose();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.props.newError(error);
|
||||
});
|
||||
}
|
||||
|
||||
testPassword = () => {
|
||||
return this.store
|
||||
.testPassword()
|
||||
.catch((error) => {
|
||||
this.props.newError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
openSnackbar,
|
||||
newError
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispatchToProps
|
||||
)(PasswordManager);
|
@ -1,111 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import PasswordManager from './';
|
||||
|
||||
import { ACCOUNT, createApi, createRedux } from './passwordManager.test.js';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let onClose;
|
||||
let reduxStore;
|
||||
|
||||
function render (props) {
|
||||
onClose = sinon.stub();
|
||||
reduxStore = createRedux();
|
||||
|
||||
component = shallow(
|
||||
<PasswordManager
|
||||
{ ...props }
|
||||
account={ ACCOUNT }
|
||||
onClose={ onClose }
|
||||
/>,
|
||||
{ context: { store: reduxStore } }
|
||||
).find('PasswordManager').shallow({ context: { api: createApi() } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('modals/PasswordManager', () => {
|
||||
describe('rendering', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
describe('changePassword', () => {
|
||||
it('calls store.changePassword', () => {
|
||||
sinon.spy(instance.store, 'changePassword');
|
||||
|
||||
return instance.changePassword().then(() => {
|
||||
expect(instance.store.changePassword).to.have.been.called;
|
||||
instance.store.changePassword.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('closes the dialog on success', () => {
|
||||
return instance.changePassword().then(() => {
|
||||
expect(onClose).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
it('shows snackbar on success', () => {
|
||||
return instance.changePassword().then(() => {
|
||||
expect(reduxStore.dispatch).to.have.been.calledWithMatch({ type: 'openSnackbar' });
|
||||
});
|
||||
});
|
||||
|
||||
it('adds newError on failure', () => {
|
||||
sinon.stub(instance.store, 'changePassword').rejects('test');
|
||||
|
||||
return instance.changePassword().then(() => {
|
||||
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||
instance.store.changePassword.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('testPassword', () => {
|
||||
it('calls store.testPassword', () => {
|
||||
sinon.spy(instance.store, 'testPassword');
|
||||
|
||||
return instance.testPassword().then(() => {
|
||||
expect(instance.store.testPassword).to.have.been.called;
|
||||
instance.store.testPassword.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('adds newError on failure', () => {
|
||||
sinon.stub(instance.store, 'testPassword').rejects('test');
|
||||
|
||||
return instance.testPassword().then(() => {
|
||||
expect(reduxStore.dispatch).to.have.been.calledWith({ error: new Error('test'), type: 'newError' });
|
||||
instance.store.testPassword.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,54 +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/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
const ACCOUNT = {
|
||||
address: '0x123456789a123456789a123456789a123456789a',
|
||||
meta: {
|
||||
description: 'Call me bob',
|
||||
passwordHint: 'some hint',
|
||||
tags: ['testing']
|
||||
},
|
||||
name: 'Bobby',
|
||||
uuid: '123-456'
|
||||
};
|
||||
|
||||
function createApi (result = true) {
|
||||
return {
|
||||
parity: {
|
||||
changePassword: sinon.stub().resolves(result),
|
||||
setAccountMeta: sinon.stub().resolves(result),
|
||||
testPassword: sinon.stub().resolves(result)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
ACCOUNT,
|
||||
createApi,
|
||||
createRedux
|
||||
};
|
@ -1,153 +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/>.
|
||||
|
||||
import { action, computed, observable, transaction } from 'mobx';
|
||||
|
||||
export default class Store {
|
||||
@observable activeTab = 0;
|
||||
@observable address = null;
|
||||
@observable busy = false;
|
||||
@observable infoMessage = null;
|
||||
@observable meta = null;
|
||||
@observable newPassword = '';
|
||||
@observable newPasswordHint = '';
|
||||
@observable newPasswordRepeat = '';
|
||||
@observable password = '';
|
||||
@observable passwordHint = '';
|
||||
@observable validatePassword = '';
|
||||
|
||||
constructor (api, account) {
|
||||
this._api = api;
|
||||
|
||||
this.address = account.address;
|
||||
this.meta = account.meta || {};
|
||||
this.passwordHint = this.meta.passwordHint || '';
|
||||
}
|
||||
|
||||
@computed get isRepeatValid () {
|
||||
return this.newPasswordRepeat === this.newPassword;
|
||||
}
|
||||
|
||||
@action setActiveTab = (activeTab) => {
|
||||
transaction(() => {
|
||||
this.activeTab = activeTab;
|
||||
this.setInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
@action setBusy = (busy, message) => {
|
||||
transaction(() => {
|
||||
this.busy = busy;
|
||||
this.setInfoMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
@action setInfoMessage = (message = null) => {
|
||||
this.infoMessage = message;
|
||||
}
|
||||
|
||||
@action setPassword = (password) => {
|
||||
transaction(() => {
|
||||
this.password = password;
|
||||
this.setInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
@action setNewPassword = (password) => {
|
||||
transaction(() => {
|
||||
this.newPassword = password;
|
||||
this.setInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
@action setNewPasswordHint = (passwordHint) => {
|
||||
transaction(() => {
|
||||
this.newPasswordHint = passwordHint;
|
||||
this.setInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
@action setNewPasswordRepeat = (password) => {
|
||||
transaction(() => {
|
||||
this.newPasswordRepeat = password;
|
||||
this.setInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
@action setValidatePassword = (password) => {
|
||||
transaction(() => {
|
||||
this.validatePassword = password;
|
||||
this.setInfoMessage();
|
||||
});
|
||||
}
|
||||
|
||||
changePassword = () => {
|
||||
if (!this.isRepeatValid) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
this.setBusy(true);
|
||||
|
||||
return this
|
||||
.testPassword(this.password)
|
||||
.then((result) => {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const meta = Object.assign({}, this.meta, {
|
||||
passwordHint: this.newPasswordHint
|
||||
});
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.setAccountMeta(this.address, meta),
|
||||
this._api.parity.changePassword(this.address, this.password, this.newPassword)
|
||||
])
|
||||
.then(() => {
|
||||
this.setBusy(false);
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('changePassword', error);
|
||||
this.setBusy(false);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
testPassword = (password) => {
|
||||
this.setBusy(true);
|
||||
|
||||
return this._api.parity
|
||||
.testPassword(this.address, password || this.validatePassword)
|
||||
.then((success) => {
|
||||
this.setBusy(false, {
|
||||
success,
|
||||
value: success
|
||||
? 'This password is correct'
|
||||
: 'This password is not correct'
|
||||
});
|
||||
|
||||
return success;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('testPassword', error);
|
||||
this.setBusy(false);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,103 +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/>.
|
||||
|
||||
import Store from './store';
|
||||
import { ACCOUNT, createApi } from './passwordManager.test.js';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function createStore (account) {
|
||||
api = createApi();
|
||||
store = new Store(api, account);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
describe('modals/PasswordManager/Store', () => {
|
||||
beforeEach(() => {
|
||||
createStore(ACCOUNT);
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('extracts the address', () => {
|
||||
expect(store.address).to.equal(ACCOUNT.address);
|
||||
});
|
||||
|
||||
describe('meta', () => {
|
||||
it('extracts the full meta', () => {
|
||||
expect(store.meta).to.deep.equal(ACCOUNT.meta);
|
||||
});
|
||||
|
||||
it('extracts the passwordHint', () => {
|
||||
expect(store.passwordHint).to.equal(ACCOUNT.meta.passwordHint);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
const CUR_PASSWORD = 'aPassW0rd';
|
||||
const NEW_PASSWORD = 'br@ndNEW';
|
||||
const NEW_HINT = 'something new to test';
|
||||
|
||||
describe('changePassword', () => {
|
||||
beforeEach(() => {
|
||||
store.setPassword(CUR_PASSWORD);
|
||||
store.setNewPasswordHint(NEW_HINT);
|
||||
store.setNewPassword(NEW_PASSWORD);
|
||||
store.setNewPasswordRepeat(NEW_PASSWORD);
|
||||
});
|
||||
|
||||
it('calls parity.testPassword with current password', () => {
|
||||
return store.changePassword().then(() => {
|
||||
expect(api.parity.testPassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls parity.setAccountMeta with new hint', () => {
|
||||
return store.changePassword().then(() => {
|
||||
expect(api.parity.setAccountMeta).to.have.been.calledWith(ACCOUNT.address, Object.assign({}, ACCOUNT.meta, {
|
||||
passwordHint: NEW_HINT
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('calls parity.changePassword with the new password', () => {
|
||||
return store.changePassword().then(() => {
|
||||
expect(api.parity.changePassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD, NEW_PASSWORD);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('testPassword', () => {
|
||||
beforeEach(() => {
|
||||
store.setValidatePassword(CUR_PASSWORD);
|
||||
});
|
||||
|
||||
it('calls parity.testPassword', () => {
|
||||
return store.testPassword().then(() => {
|
||||
expect(api.parity.testPassword).to.have.been.calledWith(ACCOUNT.address, CUR_PASSWORD);
|
||||
});
|
||||
});
|
||||
|
||||
it('sets the infoMessage for success/failure', () => {
|
||||
return store.testPassword().then(() => {
|
||||
expect(store.infoMessage).not.to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,121 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { CopyToClipboard, QrCode } from '@parity/ui';
|
||||
|
||||
import Value from '../Value';
|
||||
import styles from '../shapeshift.css';
|
||||
|
||||
@observer
|
||||
export default class AwaitingDepositStep extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { coinSymbol, depositAddress, price } = this.props.store;
|
||||
const typeSymbol = (
|
||||
<div className={ styles.symbol }>
|
||||
{ coinSymbol }
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!depositAddress) {
|
||||
return (
|
||||
<div className={ styles.center }>
|
||||
<div className={ styles.busy }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.awaitingDepositStep.awaitingConfirmation'
|
||||
defaultMessage='Awaiting confirmation of the deposit address for your {typeSymbol} funds exchange'
|
||||
values={ { typeSymbol } }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.center }>
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.awaitingDepositStep.awaitingDeposit'
|
||||
defaultMessage='{shapeshiftLink} is awaiting a {typeSymbol} deposit. Send the funds from your {typeSymbol} network client to -'
|
||||
values={ {
|
||||
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>,
|
||||
typeSymbol
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
{ this.renderAddress(depositAddress, coinSymbol) }
|
||||
<div className={ styles.price }>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='shapeshift.awaitingDepositStep.minimumMaximum'
|
||||
defaultMessage='{minimum} minimum, {maximum} maximum'
|
||||
values={ {
|
||||
maximum: <Value amount={ price.limit } symbol={ coinSymbol } />,
|
||||
minimum: <Value amount={ price.minimum } symbol={ coinSymbol } />
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,112 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import AwaitingDepositStep from './';
|
||||
|
||||
const TEST_ADDRESS = '0x123456789123456789123456789123456789';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render () {
|
||||
component = shallow(
|
||||
<AwaitingDepositStep
|
||||
store={ {
|
||||
coinSymbol: 'BTC',
|
||||
price: { rate: 0.001, minimum: 0, limit: 1.999 }
|
||||
} }
|
||||
/>
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/AwaitingDepositStep', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
it('displays waiting for address with empty depositAddress', () => {
|
||||
render();
|
||||
expect(component.find('FormattedMessage').props().id).to.match(/awaitingConfirmation/);
|
||||
});
|
||||
|
||||
it('displays waiting for deposit with non-empty depositAddress', () => {
|
||||
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}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './awaitingDepositStep';
|
@ -1,58 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import Value from '../Value';
|
||||
import styles from '../shapeshift.css';
|
||||
|
||||
@observer
|
||||
export default class AwaitingExchangeStep extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { depositInfo } = this.props.store;
|
||||
const { incomingCoin, incomingType } = depositInfo;
|
||||
|
||||
return (
|
||||
<div className={ styles.center }>
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.awaitingExchangeStep.receivedInfo'
|
||||
defaultMessage='{shapeshiftLink} has received a deposit of -'
|
||||
values={ {
|
||||
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.hero }>
|
||||
<Value amount={ incomingCoin } symbol={ incomingType } />
|
||||
</div>
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.awaitingExchangeStep.awaitingCompletion'
|
||||
defaultMessage='Awaiting the completion of the funds exchange and transfer of funds to your Parity account.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import AwaitingExchangeStep from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render () {
|
||||
component = shallow(
|
||||
<AwaitingExchangeStep
|
||||
store={ {
|
||||
depositInfo: { incomingCoin: 0.01, incomingType: 'BTC' }
|
||||
} }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/AwaitingExchangeStep', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './awaitingExchangeStep';
|
@ -1,59 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import Value from '../Value';
|
||||
import styles from '../shapeshift.css';
|
||||
|
||||
@observer
|
||||
export default class CompletedStep extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { depositInfo, exchangeInfo } = this.props.store;
|
||||
const { incomingCoin, incomingType } = depositInfo;
|
||||
const { outgoingCoin, outgoingType } = exchangeInfo;
|
||||
|
||||
return (
|
||||
<div className={ styles.center }>
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.completedStep.completed'
|
||||
defaultMessage='{shapeshiftLink} has completed the funds exchange.'
|
||||
values={ {
|
||||
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.hero }>
|
||||
<Value amount={ incomingCoin } symbol={ incomingType } /> => <Value amount={ outgoingCoin } symbol={ outgoingType } />
|
||||
</div>
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.completedStep.parityFunds'
|
||||
defaultMessage='The change in funds will be reflected in your Parity account shortly.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,41 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import CompletedStep from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render () {
|
||||
component = shallow(
|
||||
<CompletedStep
|
||||
store={ {
|
||||
depositInfo: { incomingCoin: 0.01, incomingType: 'BTC' },
|
||||
exchangeInfo: { outgoingCoin: 0.1, outgoingType: 'ETH' }
|
||||
} }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/CompletedStep', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './completedStep';
|
@ -1,50 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import styles from '../shapeshift.css';
|
||||
|
||||
@observer
|
||||
export default class ErrorStep extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error } = this.props.store;
|
||||
|
||||
return (
|
||||
<div className={ styles.body }>
|
||||
<div className={ styles.info }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.errorStep.info'
|
||||
defaultMessage='The funds shifting via {shapeshiftLink} failed with a fatal error on the exchange. The error message received from the exchange is as follow:'
|
||||
values={ {
|
||||
shapeshiftLink: <a href='https://shapeshift.io' target='_blank'>ShapeShift.io</a>
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.error }>
|
||||
{ error.message }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import ErrorStep from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render () {
|
||||
component = shallow(
|
||||
<ErrorStep
|
||||
store={ {
|
||||
error: new Error('testing')
|
||||
} }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/ErrorStep', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './errorStep';
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './optionsStep';
|
@ -1,61 +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/>.
|
||||
*/
|
||||
.body {
|
||||
}
|
||||
|
||||
.accept {
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
.coinselector {
|
||||
}
|
||||
|
||||
.coinselector .coinselect {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
.coinselect {
|
||||
max-height: 36px;
|
||||
padding: 4px 0 0 0;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.coinimage {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.coindetails {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.coinsymbol {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.coinname {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #aaa;
|
||||
}
|
@ -1,140 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Checkbox, Dropdown, Form, Input, Warning } from '@parity/ui';
|
||||
|
||||
import Price from '../Price';
|
||||
import { WARNING_NO_PRICE } from '../store';
|
||||
import styles from './optionsStep.css';
|
||||
|
||||
const WARNING_LABELS = {
|
||||
[WARNING_NO_PRICE]: (
|
||||
<FormattedMessage
|
||||
id='shapeshift.warning.noPrice'
|
||||
defaultMessage='No price match was found for the selected type'
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
@observer
|
||||
export default class OptionsStep extends Component {
|
||||
static propTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
render () {
|
||||
const { coinSymbol, hasAcceptedTerms, price, refundAddress, warning } = this.props.store;
|
||||
let { coins } = this.props.store;
|
||||
|
||||
if (!coins.length) {
|
||||
return (
|
||||
<div className={ styles.empty }>
|
||||
<FormattedMessage
|
||||
id='shapeshift.optionsStep.noPairs'
|
||||
defaultMessage='There are currently no exchange pairs/coins available to fund with.'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
coins = coins.map(this.renderCoinSelectItem);
|
||||
|
||||
return (
|
||||
<div className={ styles.body }>
|
||||
<Form>
|
||||
<Dropdown
|
||||
className={ styles.coinselector }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='shapeshift.optionsStep.typeSelect.hint'
|
||||
defaultMessage='the type of crypto conversion to do'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='shapeshift.optionsStep.typeSelect.label'
|
||||
defaultMessage='fund account from'
|
||||
/>
|
||||
}
|
||||
options={ coins }
|
||||
onChange={ this.onSelectCoin }
|
||||
value={ coinSymbol }
|
||||
/>
|
||||
<Input
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='shapeshift.optionsStep.returnAddr.hint'
|
||||
defaultMessage='the return address for send failures'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='shapeshift.optionsStep.returnAddr.label'
|
||||
defaultMessage='(optional) {coinSymbol} return address'
|
||||
values={ { coinSymbol } }
|
||||
/>
|
||||
}
|
||||
onSubmit={ this.onChangeRefundAddress }
|
||||
value={ refundAddress }
|
||||
/>
|
||||
<Checkbox
|
||||
checked={ hasAcceptedTerms }
|
||||
className={ styles.accept }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='shapeshift.optionsStep.terms.label'
|
||||
defaultMessage='I understand that ShapeShift.io is a 3rd-party service and by using the service any transfer of information and/or funds is completely out of the control of Parity'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onToggleAcceptTerms }
|
||||
/>
|
||||
</Form>
|
||||
<Warning warning={ WARNING_LABELS[warning] } />
|
||||
<Price
|
||||
coinSymbol={ coinSymbol }
|
||||
price={ price }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderCoinSelectItem = (coin) => {
|
||||
const { image, name, symbol } = coin;
|
||||
|
||||
return {
|
||||
image,
|
||||
text: name,
|
||||
value: symbol
|
||||
};
|
||||
}
|
||||
|
||||
onChangeRefundAddress = (event, refundAddress) => {
|
||||
this.props.store.setRefundAddress(refundAddress);
|
||||
}
|
||||
|
||||
onSelectCoin = (event, value) => {
|
||||
this.props.store.setCoinSymbol(value);
|
||||
}
|
||||
|
||||
onToggleAcceptTerms = () => {
|
||||
this.props.store.toggleAcceptTerms();
|
||||
}
|
||||
}
|
@ -1,126 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Store, { WARNING_NO_PRICE } from '../store';
|
||||
|
||||
import OptionsStep from './';
|
||||
|
||||
const ADDRESS = '0x1234567890123456789012345678901234567890';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let store;
|
||||
|
||||
function render () {
|
||||
store = new Store(ADDRESS);
|
||||
component = shallow(
|
||||
<OptionsStep store={ store } />
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/OptionsStep', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
it('renders no coins when none available', () => {
|
||||
expect(component.find('FormattedMessage').props().id).to.equal('shapeshift.optionsStep.noPairs');
|
||||
});
|
||||
|
||||
describe('components', () => {
|
||||
beforeEach(() => {
|
||||
store.setCoins([{ symbol: 'BTC', name: 'Bitcoin' }]);
|
||||
store.toggleAcceptTerms();
|
||||
});
|
||||
|
||||
describe('terms Checkbox', () => {
|
||||
it('shows the state of store.hasAcceptedTerms', () => {
|
||||
expect(component.find('Checkbox').props().checked).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('warning', () => {
|
||||
let warning;
|
||||
|
||||
beforeEach(() => {
|
||||
store.setWarning(WARNING_NO_PRICE);
|
||||
warning = component.find('Warning');
|
||||
});
|
||||
|
||||
it('shows a warning message when available', () => {
|
||||
expect(warning.props().warning.props.id).to.equal('shapeshift.warning.noPrice');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
describe('onChangeRefundAddress', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store, 'setRefundAddress');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.setRefundAddress.restore();
|
||||
});
|
||||
|
||||
it('sets the refundAddress on the store', () => {
|
||||
instance.onChangeRefundAddress(null, 'refundAddress');
|
||||
expect(store.setRefundAddress).to.have.been.calledWith('refundAddress');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSelectCoin', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store, 'setCoinSymbol');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.setCoinSymbol.restore();
|
||||
});
|
||||
|
||||
it('sets the coinSymbol on the store', () => {
|
||||
instance.onSelectCoin(null, 'XMR');
|
||||
expect(store.setCoinSymbol).to.have.been.calledWith('XMR');
|
||||
});
|
||||
});
|
||||
|
||||
describe('onToggleAcceptTerms', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store, 'toggleAcceptTerms');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.toggleAcceptTerms.restore();
|
||||
});
|
||||
|
||||
it('toggles the terms on the store', () => {
|
||||
instance.onToggleAcceptTerms();
|
||||
expect(store.toggleAcceptTerms).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './price';
|
@ -1,59 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import Value from '../Value';
|
||||
import styles from '../shapeshift.css';
|
||||
|
||||
export default class Price extends Component {
|
||||
static propTypes = {
|
||||
coinSymbol: PropTypes.string.isRequired,
|
||||
price: PropTypes.shape({
|
||||
rate: PropTypes.number.isRequired,
|
||||
minimum: PropTypes.number.isRequired,
|
||||
limit: PropTypes.number.isRequired
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { coinSymbol, price } = this.props;
|
||||
|
||||
if (!price) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.price }>
|
||||
<div>
|
||||
<Value amount={ 1 } symbol={ coinSymbol } /> = <Value amount={ price.rate } />
|
||||
</div>
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='shapeshift.price.minMax'
|
||||
defaultMessage='({minimum} minimum, {maximum} maximum)'
|
||||
values={ {
|
||||
maximum: <Value amount={ price.limit } symbol={ coinSymbol } />,
|
||||
minimum: <Value amount={ price.minimum } symbol={ coinSymbol } />
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,41 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Price from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render (props = {}) {
|
||||
component = shallow(
|
||||
<Price
|
||||
coinSymbol='BTC'
|
||||
price={ { rate: 0.1, minimum: 0.1, limit: 0.9 } }
|
||||
error={ new Error('testing') }
|
||||
{ ...props }
|
||||
/>
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/Price', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './value';
|
@ -1,30 +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/>.
|
||||
*/
|
||||
.body {
|
||||
display: inline-block;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.amount {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
font-variant: small-caps;
|
||||
margin-left: 0.1rem;
|
||||
font-size: 0.8em;
|
||||
}
|
@ -1,48 +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/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import styles from './value.css';
|
||||
|
||||
export default class Value extends Component {
|
||||
static propTypes = {
|
||||
amount: PropTypes.oneOfType([
|
||||
PropTypes.number,
|
||||
PropTypes.string
|
||||
]),
|
||||
symbol: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { amount, symbol } = this.props;
|
||||
|
||||
let value = '';
|
||||
|
||||
if (amount) {
|
||||
value = new BigNumber(amount).toFormat(3);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.body }>
|
||||
<span>{ value } </span>
|
||||
<span className={ styles.symbol }>{ symbol || 'ETH' }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,36 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Value from './';
|
||||
|
||||
let component;
|
||||
|
||||
function render (props = {}) {
|
||||
component = shallow(
|
||||
<Value { ...props } />
|
||||
);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift/Value', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './shapeshift';
|
@ -1,84 +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/>.
|
||||
*/
|
||||
|
||||
.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 {
|
||||
cursor: pointer;
|
||||
left: 1.5em;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
|
||||
img {
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.info, .busy {
|
||||
}
|
||||
|
||||
.center {
|
||||
div {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
padding-top: 1.5em;
|
||||
color: #e44;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding-top: 0.75em;
|
||||
text-align: center;
|
||||
font-size: 1.75em;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
display: inline-block;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.price {
|
||||
div {
|
||||
text-align: center;
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: #aaa;
|
||||
}
|
@ -1,235 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Button, IdentityIcon, Portal } from '@parity/ui';
|
||||
import { CancelIcon, DoneIcon } from '@parity/ui/Icons';
|
||||
|
||||
import shapeshiftLogo from '@parity/shared/assets/images/shapeshift-logo.png';
|
||||
|
||||
import AwaitingDepositStep from './AwaitingDepositStep';
|
||||
import AwaitingExchangeStep from './AwaitingExchangeStep';
|
||||
import CompletedStep from './CompletedStep';
|
||||
import ErrorStep from './ErrorStep';
|
||||
import OptionsStep from './OptionsStep';
|
||||
import Store, { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE } from './store';
|
||||
|
||||
import styles from './shapeshift.css';
|
||||
|
||||
const STAGE_TITLES = [
|
||||
<FormattedMessage
|
||||
id='shapeshift.title.details'
|
||||
defaultMessage='details'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='shapeshift.title.deposit'
|
||||
defaultMessage='awaiting deposit'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='shapeshift.title.exchange'
|
||||
defaultMessage='awaiting exchange'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='shapeshift.title.completed'
|
||||
defaultMessage='completed'
|
||||
/>
|
||||
];
|
||||
const ERROR_TITLE = (
|
||||
<FormattedMessage
|
||||
id='shapeshift.title.error'
|
||||
defaultMessage='exchange failed'
|
||||
/>
|
||||
);
|
||||
|
||||
@observer
|
||||
export default class Shapeshift extends Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func
|
||||
}
|
||||
|
||||
store = new Store(this.props.address);
|
||||
|
||||
componentDidMount () {
|
||||
this.store.retrieveCoins();
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.store.unsubscribe();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error, stage } = this.store;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
activeStep={ stage }
|
||||
busySteps={ [
|
||||
STAGE_WAIT_DEPOSIT,
|
||||
STAGE_WAIT_EXCHANGE
|
||||
] }
|
||||
buttons={ this.renderDialogActions() }
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
steps={
|
||||
error
|
||||
? null
|
||||
: STAGE_TITLES
|
||||
}
|
||||
title={
|
||||
error
|
||||
? ERROR_TITLE
|
||||
: null
|
||||
}
|
||||
>
|
||||
{ this.renderPage() }
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderDialogActions () {
|
||||
const { address } = this.props;
|
||||
const { coins, error, hasAcceptedTerms, stage } = this.store;
|
||||
|
||||
const logo = (
|
||||
<a
|
||||
className={ styles.shapeshift }
|
||||
href='http://shapeshift.io'
|
||||
key='logo'
|
||||
target='_blank'
|
||||
>
|
||||
<img src={ shapeshiftLogo } />
|
||||
</a>
|
||||
);
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancel'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='shapeshift.button.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return [
|
||||
logo,
|
||||
cancelBtn
|
||||
];
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case STAGE_OPTIONS:
|
||||
return [
|
||||
logo,
|
||||
cancelBtn,
|
||||
<Button
|
||||
disabled={ !coins.length || !hasAcceptedTerms }
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ address }
|
||||
button
|
||||
/>
|
||||
}
|
||||
key='shift'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='shapeshift.button.shift'
|
||||
defaultMessage='Shift Funds'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onShift }
|
||||
/>
|
||||
];
|
||||
|
||||
case STAGE_WAIT_DEPOSIT:
|
||||
case STAGE_WAIT_EXCHANGE:
|
||||
return [
|
||||
logo,
|
||||
cancelBtn
|
||||
];
|
||||
|
||||
case STAGE_COMPLETED:
|
||||
return [
|
||||
logo,
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='shapeshift.button.done'
|
||||
defaultMessage='Close'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
renderPage () {
|
||||
const { error, stage } = this.store;
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<ErrorStep store={ this.store } />
|
||||
);
|
||||
}
|
||||
|
||||
switch (stage) {
|
||||
case STAGE_OPTIONS:
|
||||
return (
|
||||
<OptionsStep store={ this.store } />
|
||||
);
|
||||
|
||||
case STAGE_WAIT_DEPOSIT:
|
||||
return (
|
||||
<AwaitingDepositStep store={ this.store } />
|
||||
);
|
||||
|
||||
case STAGE_WAIT_EXCHANGE:
|
||||
return (
|
||||
<AwaitingExchangeStep store={ this.store } />
|
||||
);
|
||||
|
||||
case STAGE_COMPLETED:
|
||||
return (
|
||||
<CompletedStep store={ this.store } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.store.setStage(STAGE_OPTIONS);
|
||||
this.props.onClose && this.props.onClose();
|
||||
}
|
||||
|
||||
onShift = () => {
|
||||
return this.store.shift();
|
||||
}
|
||||
}
|
@ -1,160 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE } from './store';
|
||||
import Shapeshift from './';
|
||||
|
||||
const ADDRESS = '0x0123456789012345678901234567890123456789';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let onClose;
|
||||
|
||||
function render (props = {}) {
|
||||
onClose = sinon.stub();
|
||||
component = shallow(
|
||||
<Shapeshift
|
||||
address={ ADDRESS }
|
||||
onClose={ onClose }
|
||||
{ ...props }
|
||||
/>,
|
||||
{ context: { store: {} } }
|
||||
);
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Shapeshift', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
describe('componentDidMount', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
sinon.stub(instance.store, 'retrieveCoins');
|
||||
return instance.componentDidMount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.retrieveCoins.restore();
|
||||
});
|
||||
|
||||
it('retrieves the list of coins when mounting', () => {
|
||||
expect(instance.store.retrieveCoins).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('componentWillUnmount', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
sinon.stub(instance.store, 'unsubscribe');
|
||||
return instance.componentWillUnmount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.unsubscribe.restore();
|
||||
});
|
||||
|
||||
it('removes any subscriptions when unmounting', () => {
|
||||
expect(instance.store.unsubscribe).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderDialogActions', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
describe('shift button', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(instance.store, 'shift').resolves();
|
||||
|
||||
instance.store.setCoins(['BTC']);
|
||||
instance.store.toggleAcceptTerms();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.store.shift.restore();
|
||||
});
|
||||
|
||||
it('disabled shift button when not accepted', () => {
|
||||
instance.store.toggleAcceptTerms();
|
||||
expect(shallow(instance.renderDialogActions()[2]).props().disabled).to.be.true;
|
||||
});
|
||||
|
||||
it('shows shift button when accepted', () => {
|
||||
expect(shallow(instance.renderDialogActions()[2]).props().disabled).to.be.false;
|
||||
});
|
||||
|
||||
it('calls the shift on store when clicked', () => {
|
||||
shallow(instance.renderDialogActions()[2]).simulate('touchTap');
|
||||
expect(instance.store.shift).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderPage', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders ErrorStep on error, passing the store', () => {
|
||||
instance.store.setError('testError');
|
||||
const page = instance.renderPage();
|
||||
|
||||
expect(page.type).to.match(/ErrorStep/);
|
||||
expect(page.props.store).to.equal(instance.store);
|
||||
});
|
||||
|
||||
it('renders OptionsStep with STAGE_OPTIONS, passing the store', () => {
|
||||
instance.store.setStage(STAGE_OPTIONS);
|
||||
const page = instance.renderPage();
|
||||
|
||||
expect(page.type).to.match(/OptionsStep/);
|
||||
expect(page.props.store).to.equal(instance.store);
|
||||
});
|
||||
|
||||
it('renders AwaitingDepositStep with STAGE_WAIT_DEPOSIT, passing the store', () => {
|
||||
instance.store.setStage(STAGE_WAIT_DEPOSIT);
|
||||
const page = instance.renderPage();
|
||||
|
||||
expect(page.type).to.match(/AwaitingDepositStep/);
|
||||
expect(page.props.store).to.equal(instance.store);
|
||||
});
|
||||
|
||||
it('renders AwaitingExchangeStep with STAGE_WAIT_EXCHANGE, passing the store', () => {
|
||||
instance.store.setStage(STAGE_WAIT_EXCHANGE);
|
||||
const page = instance.renderPage();
|
||||
|
||||
expect(page.type).to.match(/AwaitingExchangeStep/);
|
||||
expect(page.props.store).to.equal(instance.store);
|
||||
});
|
||||
|
||||
it('renders CompletedStep with STAGE_COMPLETED, passing the store', () => {
|
||||
instance.store.setStage(STAGE_COMPLETED);
|
||||
const page = instance.renderPage();
|
||||
|
||||
expect(page.type).to.match(/CompletedStep/);
|
||||
expect(page.props.store).to.equal(instance.store);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,199 +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/>.
|
||||
|
||||
import { action, observable, transaction } from 'mobx';
|
||||
|
||||
import initShapeshift from '@parity/shapeshift/index';
|
||||
|
||||
const STAGE_OPTIONS = 0;
|
||||
const STAGE_WAIT_DEPOSIT = 1;
|
||||
const STAGE_WAIT_EXCHANGE = 2;
|
||||
const STAGE_COMPLETED = 3;
|
||||
|
||||
const WARNING_NONE = 0;
|
||||
const WARNING_NO_PRICE = -1;
|
||||
|
||||
export default class Store {
|
||||
@observable address = null;
|
||||
@observable coinPair = 'btc_eth';
|
||||
@observable coinSymbol = 'BTC';
|
||||
@observable coins = [];
|
||||
@observable depositAddress = '';
|
||||
@observable depositInfo = null;
|
||||
@observable exchangeInfo = null;
|
||||
@observable error = null;
|
||||
@observable hasAcceptedTerms = false;
|
||||
@observable price = null;
|
||||
@observable refundAddress = '';
|
||||
@observable stage = STAGE_OPTIONS;
|
||||
@observable warning = 0;
|
||||
|
||||
constructor (address) {
|
||||
this._shapeshiftApi = initShapeshift();
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@action setCoins = (coins) => {
|
||||
this.coins = coins;
|
||||
}
|
||||
|
||||
@action setCoinSymbol = (coinSymbol) => {
|
||||
transaction(() => {
|
||||
this.coinSymbol = coinSymbol;
|
||||
this.coinPair = `${coinSymbol.toLowerCase()}_eth`;
|
||||
this.price = null;
|
||||
});
|
||||
|
||||
return this.getCoinPrice();
|
||||
}
|
||||
|
||||
@action setDepositAddress = (depositAddress) => {
|
||||
this.depositAddress = depositAddress;
|
||||
}
|
||||
|
||||
@action setDepositInfo = (depositInfo) => {
|
||||
transaction(() => {
|
||||
this.depositInfo = depositInfo;
|
||||
this.setStage(STAGE_WAIT_EXCHANGE);
|
||||
});
|
||||
}
|
||||
|
||||
@action setError = (error) => {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
@action setExchangeInfo = (exchangeInfo) => {
|
||||
transaction(() => {
|
||||
this.exchangeInfo = exchangeInfo;
|
||||
this.setStage(STAGE_COMPLETED);
|
||||
});
|
||||
}
|
||||
|
||||
@action setPrice = (price) => {
|
||||
transaction(() => {
|
||||
this.price = price;
|
||||
this.setWarning();
|
||||
});
|
||||
}
|
||||
|
||||
@action setRefundAddress = (refundAddress) => {
|
||||
this.refundAddress = refundAddress;
|
||||
}
|
||||
|
||||
@action setStage = (stage) => {
|
||||
this.stage = stage;
|
||||
}
|
||||
|
||||
@action setWarning = (warning = WARNING_NONE) => {
|
||||
this.warning = warning;
|
||||
}
|
||||
|
||||
@action toggleAcceptTerms = () => {
|
||||
this.hasAcceptedTerms = !this.hasAcceptedTerms;
|
||||
}
|
||||
|
||||
getCoinPrice () {
|
||||
return this._shapeshiftApi
|
||||
.getMarketInfo(this.coinPair)
|
||||
.then((price) => {
|
||||
this.setPrice(price);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('getCoinPrice', error);
|
||||
|
||||
this.setWarning(WARNING_NO_PRICE);
|
||||
});
|
||||
}
|
||||
|
||||
retrieveCoins () {
|
||||
return this._shapeshiftApi
|
||||
.getCoins()
|
||||
.then((coins) => {
|
||||
this.setCoins(Object.values(coins).filter((coin) => coin.status === 'available'));
|
||||
|
||||
return this.getCoinPrice();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('retrieveCoins', error);
|
||||
const message = `Failed to retrieve available coins from ShapeShift.io: ${error.message}`;
|
||||
|
||||
this.setError(message);
|
||||
});
|
||||
}
|
||||
|
||||
shift () {
|
||||
this.setStage(STAGE_WAIT_DEPOSIT);
|
||||
|
||||
return this._shapeshiftApi
|
||||
.shift(this.address, this.refundAddress, this.coinPair)
|
||||
.then((result) => {
|
||||
console.log('onShift', result);
|
||||
|
||||
this.setDepositAddress(result.deposit);
|
||||
return this.subscribe();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('onShift', error);
|
||||
const message = `Failed to start exchange: ${error.message}`;
|
||||
|
||||
this.setError(new Error(message));
|
||||
});
|
||||
}
|
||||
|
||||
onExchangeInfo = (error, result) => {
|
||||
if (error) {
|
||||
console.error('onExchangeInfo', error);
|
||||
|
||||
if (error.fatal) {
|
||||
this.setError(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('onExchangeInfo', result.status, result);
|
||||
|
||||
switch (result.status) {
|
||||
case 'received':
|
||||
if (this.stage !== STAGE_WAIT_EXCHANGE) {
|
||||
this.setDepositInfo(result);
|
||||
}
|
||||
return;
|
||||
|
||||
case 'complete':
|
||||
if (this.stage !== STAGE_COMPLETED) {
|
||||
this.setExchangeInfo(result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
subscribe () {
|
||||
return this._shapeshiftApi.subscribe(this.depositAddress, this.onExchangeInfo);
|
||||
}
|
||||
|
||||
unsubscribe () {
|
||||
return this._shapeshiftApi.unsubscribe(this.depositAddress);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
STAGE_COMPLETED,
|
||||
STAGE_OPTIONS,
|
||||
STAGE_WAIT_DEPOSIT,
|
||||
STAGE_WAIT_EXCHANGE,
|
||||
WARNING_NONE,
|
||||
WARNING_NO_PRICE
|
||||
};
|
@ -1,355 +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/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Store, { STAGE_COMPLETED, STAGE_OPTIONS, STAGE_WAIT_DEPOSIT, STAGE_WAIT_EXCHANGE, WARNING_NONE, WARNING_NO_PRICE } from './store';
|
||||
|
||||
const ADDRESS = '0xabcdeffdecbaabcdeffdecbaabcdeffdecbaabcdeffdecba';
|
||||
|
||||
describe('modals/Shapeshift/Store', () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new Store(ADDRESS);
|
||||
});
|
||||
|
||||
it('stores the ETH address', () => {
|
||||
expect(store.address).to.equal(ADDRESS);
|
||||
});
|
||||
|
||||
it('defaults to BTC-ETH pair', () => {
|
||||
expect(store.coinSymbol).to.equal('BTC');
|
||||
expect(store.coinPair).to.equal('btc_eth');
|
||||
});
|
||||
|
||||
it('defaults to stage STAGE_OPTIONS', () => {
|
||||
expect(store.stage).to.equal(STAGE_OPTIONS);
|
||||
});
|
||||
|
||||
it('defaults to terms not accepted', () => {
|
||||
expect(store.hasAcceptedTerms).to.be.false;
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setCoins', () => {
|
||||
it('sets the available coins', () => {
|
||||
const coins = ['BTC', 'ETC', 'XMR'];
|
||||
|
||||
store.setCoins(coins);
|
||||
expect(store.coins.peek()).to.deep.equal(coins);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setCoinSymbol', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store, 'getCoinPrice');
|
||||
store.setCoinSymbol('XMR');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.getCoinPrice.restore();
|
||||
});
|
||||
|
||||
it('sets the coinSymbol', () => {
|
||||
expect(store.coinSymbol).to.equal('XMR');
|
||||
});
|
||||
|
||||
it('sets the coinPair', () => {
|
||||
expect(store.coinPair).to.equal('xmr_eth');
|
||||
});
|
||||
|
||||
it('resets the price retrieved', () => {
|
||||
expect(store.price).to.be.null;
|
||||
});
|
||||
|
||||
it('retrieves the pair price', () => {
|
||||
expect(store.getCoinPrice).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDepositAddress', () => {
|
||||
it('sets the depositAddress', () => {
|
||||
store.setDepositAddress('testing');
|
||||
expect(store.depositAddress).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDepositInfo', () => {
|
||||
beforeEach(() => {
|
||||
store.setDepositInfo('testing');
|
||||
});
|
||||
|
||||
it('sets the depositInfo', () => {
|
||||
expect(store.depositInfo).to.equal('testing');
|
||||
});
|
||||
|
||||
it('sets the stage to STAGE_WAIT_EXCHANGE', () => {
|
||||
expect(store.stage).to.equal(STAGE_WAIT_EXCHANGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setError', () => {
|
||||
it('sets the error', () => {
|
||||
store.setError(new Error('testing'));
|
||||
expect(store.error).to.match(/testing/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setExchangeInfo', () => {
|
||||
beforeEach(() => {
|
||||
store.setExchangeInfo('testing');
|
||||
});
|
||||
|
||||
it('sets the exchangeInfo', () => {
|
||||
expect(store.exchangeInfo).to.equal('testing');
|
||||
});
|
||||
|
||||
it('sets the stage to STAGE_COMPLETED', () => {
|
||||
expect(store.stage).to.equal(STAGE_COMPLETED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPrice', () => {
|
||||
it('sets the price', () => {
|
||||
store.setPrice('testing');
|
||||
expect(store.price).to.equal('testing');
|
||||
});
|
||||
|
||||
it('clears any warnings once set', () => {
|
||||
store.setWarning(-999);
|
||||
store.setPrice('testing');
|
||||
expect(store.warning).to.equal(WARNING_NONE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setRefundAddress', () => {
|
||||
it('sets the price', () => {
|
||||
store.setRefundAddress('testing');
|
||||
expect(store.refundAddress).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setStage', () => {
|
||||
it('sets the state', () => {
|
||||
store.setStage('testing');
|
||||
expect(store.stage).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setWarning', () => {
|
||||
it('sets the warning', () => {
|
||||
store.setWarning(-999);
|
||||
|
||||
expect(store.warning).to.equal(-999);
|
||||
});
|
||||
|
||||
it('clears the warning with no parameters', () => {
|
||||
store.setWarning(-999);
|
||||
store.setWarning();
|
||||
|
||||
expect(store.warning).to.equal(WARNING_NONE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleAcceptTerms', () => {
|
||||
it('changes state on hasAcceptedTerms', () => {
|
||||
store.toggleAcceptTerms();
|
||||
expect(store.hasAcceptedTerms).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('getCoinPrice', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store._shapeshiftApi, 'getMarketInfo').resolves('testPrice');
|
||||
return store.getCoinPrice();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._shapeshiftApi.getMarketInfo.restore();
|
||||
});
|
||||
|
||||
it('retrieves the market info from ShapeShift', () => {
|
||||
expect(store._shapeshiftApi.getMarketInfo).to.have.been.calledWith('btc_eth');
|
||||
});
|
||||
|
||||
it('stores the price retrieved', () => {
|
||||
expect(store.price).to.equal('testPrice');
|
||||
});
|
||||
|
||||
it('sets a warning on failure', () => {
|
||||
store._shapeshiftApi.getMarketInfo.restore();
|
||||
sinon.stub(store._shapeshiftApi, 'getMarketInfo').rejects('someError');
|
||||
|
||||
return store.getCoinPrice().then(() => {
|
||||
expect(store.warning).to.equal(WARNING_NO_PRICE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('retrieveCoins', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store._shapeshiftApi, 'getCoins').resolves({
|
||||
BTC: { symbol: 'BTC', status: 'available' },
|
||||
ETC: { symbol: 'ETC' },
|
||||
XMR: { symbol: 'XMR', status: 'available' }
|
||||
});
|
||||
sinon.stub(store, 'getCoinPrice');
|
||||
return store.retrieveCoins();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._shapeshiftApi.getCoins.restore();
|
||||
store.getCoinPrice.restore();
|
||||
});
|
||||
|
||||
it('retrieves the coins from ShapeShift', () => {
|
||||
expect(store._shapeshiftApi.getCoins).to.have.been.called;
|
||||
});
|
||||
|
||||
it('sets the available coins', () => {
|
||||
expect(store.coins.peek()).to.deep.equal([
|
||||
{ status: 'available', symbol: 'BTC' },
|
||||
{ status: 'available', symbol: 'XMR' }
|
||||
]);
|
||||
});
|
||||
|
||||
it('retrieves the price once resolved', () => {
|
||||
expect(store.getCoinPrice).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('shift', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store, 'subscribe').resolves();
|
||||
sinon.stub(store._shapeshiftApi, 'shift').resolves({ deposit: 'depositAddress' });
|
||||
store.setRefundAddress('refundAddress');
|
||||
|
||||
return store.shift();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.subscribe.restore();
|
||||
store._shapeshiftApi.shift.restore();
|
||||
});
|
||||
|
||||
it('moves to stage STAGE_WAIT_DEPOSIT', () => {
|
||||
expect(store.stage).to.equal(STAGE_WAIT_DEPOSIT);
|
||||
});
|
||||
|
||||
it('calls ShapeShift with the correct parameters', () => {
|
||||
expect(store._shapeshiftApi.shift).to.have.been.calledWith(ADDRESS, 'refundAddress', store.coinPair);
|
||||
});
|
||||
|
||||
it('sets the depositAddress', () => {
|
||||
expect(store.depositAddress).to.equal('depositAddress');
|
||||
});
|
||||
|
||||
it('subscribes to updates', () => {
|
||||
expect(store.subscribe).to.have.been.called;
|
||||
});
|
||||
|
||||
it('sets error when shift fails', () => {
|
||||
store._shapeshiftApi.shift.restore();
|
||||
sinon.stub(store._shapeshiftApi, 'shift').rejects({ message: 'testingError' });
|
||||
|
||||
return store.shift().then(() => {
|
||||
expect(store.error).to.match(/testingError/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('subscribe', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store._shapeshiftApi, 'subscribe');
|
||||
store.setDepositAddress('depositAddress');
|
||||
return store.subscribe();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._shapeshiftApi.subscribe.restore();
|
||||
});
|
||||
|
||||
it('calls into the ShapeShift subscribe', () => {
|
||||
expect(store._shapeshiftApi.subscribe).to.have.been.calledWith('depositAddress', store.onExchangeInfo);
|
||||
});
|
||||
|
||||
describe('onExchangeInfo', () => {
|
||||
it('sets the error when fatal error retrieved', () => {
|
||||
store.onExchangeInfo({ fatal: true, message: 'testing' });
|
||||
expect(store.error.message).to.equal('testing');
|
||||
});
|
||||
|
||||
it('does not set the error when non-fatal error retrieved', () => {
|
||||
store.onExchangeInfo({ message: 'testing' });
|
||||
expect(store.error).to.be.null;
|
||||
});
|
||||
|
||||
describe('status received', () => {
|
||||
const INFO = { status: 'received' };
|
||||
|
||||
beforeEach(() => {
|
||||
store.onExchangeInfo(null, INFO);
|
||||
});
|
||||
|
||||
it('sets the depositInfo', () => {
|
||||
expect(store.depositInfo).to.deep.equal(INFO);
|
||||
});
|
||||
|
||||
it('only advanced depositInfo once', () => {
|
||||
store.onExchangeInfo(null, Object.assign({}, INFO, { state: 'secondTime' }));
|
||||
expect(store.depositInfo).to.deep.equal(INFO);
|
||||
});
|
||||
});
|
||||
|
||||
describe('status completed', () => {
|
||||
const INFO = { status: 'complete' };
|
||||
|
||||
beforeEach(() => {
|
||||
store.onExchangeInfo(null, INFO);
|
||||
});
|
||||
|
||||
it('sets the depositInfo', () => {
|
||||
expect(store.exchangeInfo).to.deep.equal(INFO);
|
||||
});
|
||||
|
||||
it('only advanced depositInfo once', () => {
|
||||
store.onExchangeInfo(null, Object.assign({}, INFO, { state: 'secondTime' }));
|
||||
expect(store.exchangeInfo).to.deep.equal(INFO);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsubscribe', () => {
|
||||
beforeEach(() => {
|
||||
sinon.stub(store._shapeshiftApi, 'unsubscribe');
|
||||
store.setDepositAddress('depositAddress');
|
||||
return store.unsubscribe();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._shapeshiftApi.unsubscribe.restore();
|
||||
});
|
||||
|
||||
it('calls into the ShapeShift unsubscribe', () => {
|
||||
expect(store._shapeshiftApi.unsubscribe).to.have.been.calledWith('depositAddress');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './transactions';
|
@ -1,118 +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/>.
|
||||
|
||||
import { action, observable, transaction } from 'mobx';
|
||||
|
||||
import etherscan from '@parity/etherscan/index';
|
||||
|
||||
export default class Store {
|
||||
@observable address = null;
|
||||
@observable isLoading = false;
|
||||
@observable isTracing = false;
|
||||
@observable netVersion = '0';
|
||||
@observable txHashes = [];
|
||||
|
||||
constructor (api) {
|
||||
this._api = api;
|
||||
}
|
||||
|
||||
@action setHashes = (transactions) => {
|
||||
transaction(() => {
|
||||
this.setLoading(false);
|
||||
this.txHashes = transactions.map((transaction) => transaction.hash);
|
||||
});
|
||||
}
|
||||
|
||||
@action setAddress = (address) => {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@action setLoading = (isLoading) => {
|
||||
this.isLoading = isLoading;
|
||||
}
|
||||
|
||||
@action setNetVersion = (netVersion) => {
|
||||
this.netVersion = netVersion;
|
||||
}
|
||||
|
||||
@action setTracing = (isTracing) => {
|
||||
this.isTracing = isTracing;
|
||||
}
|
||||
|
||||
@action updateProps = (props) => {
|
||||
transaction(() => {
|
||||
this.setAddress(props.address);
|
||||
this.setNetVersion(props.netVersion);
|
||||
|
||||
// TODO: When tracing is enabled again, adjust to actually set
|
||||
this.setTracing(false && props.traceMode);
|
||||
});
|
||||
|
||||
return this.getTransactions();
|
||||
}
|
||||
|
||||
getTransactions () {
|
||||
if (this.netVersion === '0') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.setLoading(true);
|
||||
|
||||
// TODO: When supporting other chains (eg. ETC). call to be made to other endpoints
|
||||
return (
|
||||
this.isTracing
|
||||
? this.fetchTraceTransactions()
|
||||
: this.fetchEtherscanTransactions()
|
||||
)
|
||||
.then((transactions) => {
|
||||
this.setHashes(transactions);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('getTransactions', error);
|
||||
this.setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
fetchEtherscanTransactions () {
|
||||
return etherscan.account.transactions(this.address, 0, false, this.netVersion);
|
||||
}
|
||||
|
||||
fetchTraceTransactions () {
|
||||
return Promise
|
||||
.all([
|
||||
this._api.trace.filter({
|
||||
fromAddress: this.address,
|
||||
fromBlock: 0
|
||||
}),
|
||||
this._api.trace.filter({
|
||||
fromBlock: 0,
|
||||
toAddress: this.address
|
||||
})
|
||||
])
|
||||
.then(([fromTransactions, toTransactions]) => {
|
||||
return fromTransactions
|
||||
.concat(toTransactions)
|
||||
.map((transaction) => {
|
||||
return {
|
||||
blockNumber: transaction.blockNumber,
|
||||
from: transaction.action.from,
|
||||
hash: transaction.transactionHash,
|
||||
to: transaction.action.to
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,193 +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/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { mockget as mockEtherscan } from '@parity/etherscan/helpers.spec.js';
|
||||
import { ADDRESS, createApi } from './transactions.test.js';
|
||||
|
||||
import Store from './store';
|
||||
|
||||
let api;
|
||||
let store;
|
||||
|
||||
function createStore () {
|
||||
api = createApi();
|
||||
store = new Store(api);
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
function mockQuery () {
|
||||
mockEtherscan([{
|
||||
query: {
|
||||
module: 'account',
|
||||
action: 'txlist',
|
||||
address: ADDRESS,
|
||||
offset: 25,
|
||||
page: 1,
|
||||
sort: 'desc'
|
||||
},
|
||||
reply: [{ hash: '123' }]
|
||||
}], false, '42');
|
||||
}
|
||||
|
||||
describe('views/Account/Transactions/store', () => {
|
||||
beforeEach(() => {
|
||||
mockQuery();
|
||||
createStore();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('sets the api', () => {
|
||||
expect(store._api).to.deep.equals(api);
|
||||
});
|
||||
|
||||
it('starts with isLoading === false', () => {
|
||||
expect(store.isLoading).to.be.false;
|
||||
});
|
||||
|
||||
it('starts with isTracing === false', () => {
|
||||
expect(store.isTracing).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('@action', () => {
|
||||
describe('setHashes', () => {
|
||||
it('clears the loading state', () => {
|
||||
store.setLoading(true);
|
||||
store.setHashes([]);
|
||||
expect(store.isLoading).to.be.false;
|
||||
});
|
||||
|
||||
it('sets the hashes from the transactions', () => {
|
||||
store.setHashes([{ hash: '123' }, { hash: '456' }]);
|
||||
expect(store.txHashes.peek()).to.deep.equal(['123', '456']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAddress', () => {
|
||||
it('sets the address', () => {
|
||||
store.setAddress(ADDRESS);
|
||||
expect(store.address).to.equal(ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setLoading', () => {
|
||||
it('sets the isLoading flag', () => {
|
||||
store.setLoading(true);
|
||||
expect(store.isLoading).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('setNetVersion', () => {
|
||||
it('sets the netVersion', () => {
|
||||
store.setNetVersion('testing');
|
||||
expect(store.netVersion).to.equal('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTracing', () => {
|
||||
it('sets the isTracing flag', () => {
|
||||
store.setTracing(true);
|
||||
expect(store.isTracing).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateProps', () => {
|
||||
it('retrieves transactions once updated', () => {
|
||||
sinon.spy(store, 'getTransactions');
|
||||
store.updateProps({});
|
||||
|
||||
expect(store.getTransactions).to.have.been.called;
|
||||
store.getTransactions.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
describe('getTransactions', () => {
|
||||
it('retrieves the hashes via etherscan', () => {
|
||||
sinon.spy(store, 'fetchEtherscanTransactions');
|
||||
store.setAddress(ADDRESS);
|
||||
store.setNetVersion('42');
|
||||
store.setTracing(false);
|
||||
|
||||
return store.getTransactions().then(() => {
|
||||
expect(store.fetchEtherscanTransactions).to.have.been.called;
|
||||
expect(store.txHashes.peek()).to.deep.equal(['123']);
|
||||
store.fetchEtherscanTransactions.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('retrieves the hashes via tracing', () => {
|
||||
sinon.spy(store, 'fetchTraceTransactions');
|
||||
store.setAddress(ADDRESS);
|
||||
store.setNetVersion('42');
|
||||
store.setTracing(true);
|
||||
|
||||
return store.getTransactions().then(() => {
|
||||
expect(store.fetchTraceTransactions).to.have.been.called;
|
||||
expect(store.txHashes.peek()).to.deep.equal(['123', '098']);
|
||||
store.fetchTraceTransactions.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchEtherscanTransactions', () => {
|
||||
it('retrieves the transactions', () => {
|
||||
store.setAddress(ADDRESS);
|
||||
store.setNetVersion('42');
|
||||
|
||||
return store.fetchEtherscanTransactions().then((transactions) => {
|
||||
expect(transactions).to.deep.equal([{
|
||||
blockNumber: new BigNumber(0),
|
||||
from: '',
|
||||
hash: '123',
|
||||
timeStamp: undefined,
|
||||
to: '',
|
||||
value: undefined
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchTraceTransactions', () => {
|
||||
it('retrieves the transactions', () => {
|
||||
store.setAddress(ADDRESS);
|
||||
store.setNetVersion('42');
|
||||
|
||||
return store.fetchTraceTransactions().then((transactions) => {
|
||||
expect(transactions).to.deep.equal([
|
||||
{
|
||||
blockNumber: undefined,
|
||||
from: undefined,
|
||||
hash: '123',
|
||||
to: undefined
|
||||
},
|
||||
{
|
||||
blockNumber: undefined,
|
||||
from: undefined,
|
||||
hash: '098',
|
||||
to: undefined
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,27 +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/>.
|
||||
*/
|
||||
|
||||
.infonone {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.etherscan {
|
||||
text-align: right;
|
||||
padding-top: 1em;
|
||||
font-size: 0.75em;
|
||||
color: #aaa;
|
||||
}
|
@ -1,127 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Container, TxList, Loading } from '@parity/ui';
|
||||
|
||||
import Store from './store';
|
||||
import styles from './transactions.css';
|
||||
|
||||
@observer
|
||||
class Transactions extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
traceMode: PropTypes.bool
|
||||
}
|
||||
|
||||
store = new Store(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
this.store.updateProps(this.props);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
if (this.props.traceMode === undefined && newProps.traceMode !== undefined) {
|
||||
this.store.updateProps(newProps);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasChanged = ['address', 'netVersion']
|
||||
.map(key => newProps[key] !== this.props[key])
|
||||
.reduce((truth, keyTruth) => truth || keyTruth, false);
|
||||
|
||||
if (hasChanged) {
|
||||
this.store.updateProps(newProps);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Container
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='account.transactions.title'
|
||||
defaultMessage='transactions'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ this.renderTransactionList() }
|
||||
{ this.renderEtherscanFooter() }
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
renderTransactionList () {
|
||||
const { address, isLoading, txHashes } = this.store;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Loading />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TxList
|
||||
address={ address }
|
||||
hashes={ txHashes }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderEtherscanFooter () {
|
||||
const { isTracing } = this.store;
|
||||
|
||||
if (isTracing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.etherscan }>
|
||||
<FormattedMessage
|
||||
id='account.transactions.poweredBy'
|
||||
defaultMessage='Transaction list powered by {etherscan}'
|
||||
values={ {
|
||||
etherscan: <a href='https://etherscan.io/' target='_blank'>etherscan.io</a>
|
||||
} }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { netVersion, traceMode } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
netVersion,
|
||||
traceMode
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Transactions);
|
@ -1,56 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { ADDRESS, createApi, createRedux } from './transactions.test.js';
|
||||
|
||||
import Transactions from './';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
|
||||
function render (props) {
|
||||
component = shallow(
|
||||
<Transactions
|
||||
address={ ADDRESS }
|
||||
{ ...props }
|
||||
/>,
|
||||
{ context: { store: createRedux() } }
|
||||
).find('Transactions').shallow({ context: { api: createApi() } });
|
||||
instance = component.instance();
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account/Transactions', () => {
|
||||
it('renders defaults', () => {
|
||||
expect(render()).to.be.ok;
|
||||
});
|
||||
|
||||
describe('renderTransactionList', () => {
|
||||
it('renders Loading when isLoading === true', () => {
|
||||
instance.store.setLoading(true);
|
||||
expect(instance.renderTransactionList().type).to.match(/Loading/);
|
||||
});
|
||||
|
||||
it('renders TxList when isLoading === true', () => {
|
||||
instance.store.setLoading(false);
|
||||
expect(instance.renderTransactionList().type).to.match(/Connect/);
|
||||
});
|
||||
});
|
||||
});
|
@ -1,31 +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/>.
|
||||
|
||||
import { ADDRESS, createRedux } from '../account.test.js';
|
||||
|
||||
function createApi () {
|
||||
return {
|
||||
trace: {
|
||||
filter: (options) => Promise.resolve([{ transactionHash: options.fromAddress ? '123' : '098', action: {} }])
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
ADDRESS,
|
||||
createApi,
|
||||
createRedux
|
||||
};
|
@ -1,243 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { nullableProptype } from '@parity/shared/util/proptypes';
|
||||
import Form, { AddressSelect, Checkbox, Input, InputAddressSelect, Label } from '@parity/ui/Form';
|
||||
|
||||
import TokenSelect from './tokenSelect';
|
||||
import styles from '../transfer.css';
|
||||
|
||||
export const CHECK_STYLE = {
|
||||
position: 'absolute',
|
||||
top: '38px',
|
||||
left: '1em'
|
||||
};
|
||||
|
||||
export default class Details extends Component {
|
||||
static propTypes = {
|
||||
address: PropTypes.string,
|
||||
balance: PropTypes.object,
|
||||
all: PropTypes.bool,
|
||||
extras: PropTypes.bool,
|
||||
sender: PropTypes.string,
|
||||
senderError: PropTypes.string,
|
||||
recipient: PropTypes.string,
|
||||
recipientError: PropTypes.string,
|
||||
token: PropTypes.object,
|
||||
total: PropTypes.string,
|
||||
totalError: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
valueError: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
wallet: PropTypes.object,
|
||||
senders: nullableProptype(PropTypes.object)
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
wallet: null,
|
||||
senders: null
|
||||
};
|
||||
|
||||
render () {
|
||||
const { all, extras, token, total, totalError, value, valueError } = this.props;
|
||||
const label = (
|
||||
<FormattedMessage
|
||||
id='transfer.details.amount.label'
|
||||
defaultMessage='Amount to transfer (in {tag})'
|
||||
values={ {
|
||||
tag: token.tag
|
||||
} }
|
||||
/>
|
||||
);
|
||||
|
||||
let totalAmountStyle = { color: 'rgba(0,0,0,.87)' };
|
||||
|
||||
if (totalError) {
|
||||
totalAmountStyle = { color: '#9F3A38' };
|
||||
}
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{ this.renderTokenSelect() }
|
||||
{ this.renderFromAddress() }
|
||||
{ this.renderToAddress() }
|
||||
<div className={ styles.columns }>
|
||||
<div>
|
||||
<Input
|
||||
className={ styles.inputContainer }
|
||||
disabled={ all }
|
||||
label={ label }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transfer.details.amount.hint'
|
||||
defaultMessage='The amount to transfer to the recipient'
|
||||
/>
|
||||
}
|
||||
value={ value }
|
||||
error={ valueError }
|
||||
onChange={ this.onEditValue }
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={ all }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.details.fullBalance.label'
|
||||
defaultMessage='Full account balance'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onCheckAll }
|
||||
style={ CHECK_STYLE }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.columns }>
|
||||
<div className={ styles.totalTx }>
|
||||
<Label className={ styles.transferLabel }>
|
||||
<FormattedMessage
|
||||
id='transfer.details.total.label'
|
||||
defaultMessage='Total transaction amount'
|
||||
/>
|
||||
</Label>
|
||||
<div className={ styles.totalAmount } style={ totalAmountStyle }>
|
||||
<div>{ total }<small> ETH</small></div>
|
||||
<div>{ totalError }</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Checkbox
|
||||
checked={ extras }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.details.advanced.label'
|
||||
defaultMessage='Advanced sending options'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onCheckExtras }
|
||||
style={ CHECK_STYLE }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
renderFromAddress () {
|
||||
const { sender, senderError, senders } = this.props;
|
||||
|
||||
if (!senders) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ styles.address }>
|
||||
<AddressSelect
|
||||
accounts={ senders }
|
||||
error={ senderError }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.details.sender.label'
|
||||
defaultMessage='Sender address'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transfer.details.sender.hint'
|
||||
defaultMessage='The sender address'
|
||||
/>
|
||||
}
|
||||
value={ sender }
|
||||
onChange={ this.onEditSender }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderToAddress () {
|
||||
const { recipient, recipientError } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.address }>
|
||||
<InputAddressSelect
|
||||
className={ styles.inputContainer }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.details.recipient.label'
|
||||
defaultMessage='Recipient address'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transfer.details.recipient.hint'
|
||||
defaultMessage='The recipient address'
|
||||
/>
|
||||
}
|
||||
error={ recipientError }
|
||||
value={ recipient }
|
||||
onChange={ this.onEditRecipient }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderTokenSelect () {
|
||||
const { balance, token } = this.props;
|
||||
|
||||
return (
|
||||
<TokenSelect
|
||||
balance={ balance }
|
||||
onChange={ this.onChangeToken }
|
||||
value={ token.id }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onChangeToken = (event, token) => {
|
||||
this.props.onChange('token', token);
|
||||
}
|
||||
|
||||
onEditSender = (event, sender) => {
|
||||
this.props.onChange('sender', sender);
|
||||
}
|
||||
|
||||
onEditRecipient = (event, recipient) => {
|
||||
this.props.onChange('recipient', recipient);
|
||||
}
|
||||
|
||||
onEditValue = (event) => {
|
||||
this.props.onChange('value', event.target.value);
|
||||
}
|
||||
|
||||
onCheckAll = () => {
|
||||
this.props.onChange('all', !this.props.all);
|
||||
}
|
||||
|
||||
onCheckExtras = () => {
|
||||
this.props.onChange('extras', !this.props.extras);
|
||||
}
|
||||
|
||||
onContacts = () => {
|
||||
this.setState({
|
||||
showAddresses: true
|
||||
});
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './details';
|
@ -1,130 +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/>.
|
||||
|
||||
import BigNumber from 'bignumber.js';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { Dropdown, TokenImage } from '@parity/ui';
|
||||
|
||||
import styles from '../transfer.css';
|
||||
|
||||
class TokenSelect extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
balance: PropTypes.object.isRequired,
|
||||
tokens: PropTypes.object.isRequired,
|
||||
value: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.computeTokens();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const prevTokens = Object.keys(this.props.balance)
|
||||
.map((tokenId) => `${tokenId}_${this.props.balance[tokenId].toNumber()}`);
|
||||
const nextTokens = Object.keys(nextProps.balance)
|
||||
.map((tokenId) => `${tokenId}_${nextProps.balance[tokenId].toNumber()}`);
|
||||
|
||||
if (!isEqual(prevTokens, nextTokens)) {
|
||||
this.computeTokens(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
computeTokens (props = this.props) {
|
||||
const { api } = this.context;
|
||||
const { balance, tokens } = this.props;
|
||||
|
||||
const items = Object
|
||||
.keys(balance)
|
||||
.map((tokenId) => {
|
||||
const token = tokens[tokenId];
|
||||
const tokenValue = balance[tokenId];
|
||||
const isEth = token.native;
|
||||
|
||||
if (!isEth && tokenValue.eq(0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let value = 0;
|
||||
|
||||
if (isEth) {
|
||||
value = api.util.fromWei(tokenValue).toFormat(3);
|
||||
} else {
|
||||
const format = token.format || 1;
|
||||
const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10));
|
||||
|
||||
value = new BigNumber(tokenValue).div(format).toFormat(decimals);
|
||||
}
|
||||
|
||||
const label = (
|
||||
<div className={ styles.token }>
|
||||
<TokenImage token={ token } />
|
||||
<div className={ styles.tokenname }>
|
||||
{ token.name }
|
||||
</div>
|
||||
<div className={ styles.tokenbalance }>
|
||||
{ value }<small> { token.tag }</small>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
key: tokenId,
|
||||
text: token.name,
|
||||
value: token.id,
|
||||
content: label
|
||||
};
|
||||
})
|
||||
.filter((node) => node);
|
||||
|
||||
this.setState({ items });
|
||||
}
|
||||
|
||||
render () {
|
||||
const { onChange, value } = this.props;
|
||||
const { items } = this.state;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className={ styles.tokenSelect }
|
||||
label='type of token transfer'
|
||||
hint='type of token to transfer'
|
||||
value={ value }
|
||||
onChange={ onChange }
|
||||
options={ items }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { tokens } = state;
|
||||
|
||||
return { tokens };
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(TokenSelect);
|
@ -1,88 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { GasPriceEditor, Form, Input } from '@parity/ui';
|
||||
|
||||
import styles from '../transfer.css';
|
||||
|
||||
export default class Extras extends Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.string,
|
||||
dataError: PropTypes.string,
|
||||
hideData: PropTypes.bool,
|
||||
gasStore: PropTypes.object.isRequired,
|
||||
isEth: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
total: PropTypes.string,
|
||||
totalError: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
hideData: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { gasStore, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{ this.renderData() }
|
||||
<div className={ styles.gaseditor }>
|
||||
<GasPriceEditor
|
||||
store={ gasStore }
|
||||
onChange={ onChange }
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
renderData () {
|
||||
const { isEth, data, dataError, hideData } = this.props;
|
||||
|
||||
if (!isEth || hideData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
error={ dataError }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='transfer.advanced.data.hint'
|
||||
defaultMessage='the data to pass through with the transaction'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.advanced.data.label'
|
||||
defaultMessage='transaction data'
|
||||
/>
|
||||
}
|
||||
onChange={ this.onEditData }
|
||||
value={ data }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onEditData = (event) => {
|
||||
this.props.onChange('data', event.target.value);
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './extras';
|
@ -1,28 +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/>.
|
||||
|
||||
const ERRORS = {
|
||||
requireSender: 'A valid sender is required for the transaction',
|
||||
requireRecipient: 'A recipient network address is required for the transaction',
|
||||
invalidAddress: 'The supplied address is an invalid network address',
|
||||
invalidAmount: 'The supplied amount should be a valid positive number',
|
||||
invalidDecimals: 'The supplied amount exceeds the allowed decimals',
|
||||
largeAmount: 'The transaction total is higher than the available balance',
|
||||
gasException: 'The transaction will throw an exception with the current values',
|
||||
gasBlockLimit: 'The transaction execution will exceed the block gas limit'
|
||||
};
|
||||
|
||||
export default ERRORS;
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './transfer';
|
@ -1,586 +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/>.
|
||||
|
||||
import { noop } from 'lodash';
|
||||
import { observable, computed, action, transaction } from 'mobx';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import Contract from '@parity/api/contract';
|
||||
import { fromWei } from '@parity/api/util/wei';
|
||||
import { getLogger, LOG_KEYS } from '@parity/shared/config';
|
||||
import { eip20 as tokenAbi, wallet as walletAbi } from '@parity/shared/contracts/abi';
|
||||
import { DEFAULT_GAS, DEFAULT_GASPRICE, MAX_GAS_ESTIMATION } from '@parity/shared/util/constants';
|
||||
import { ETH_TOKEN } from '@parity/shared/util/tokens';
|
||||
import GasPriceStore from '@parity/ui/GasPriceEditor/store';
|
||||
|
||||
import ERRORS from './errors';
|
||||
|
||||
const log = getLogger(LOG_KEYS.TransferModalStore);
|
||||
|
||||
const TITLES = {
|
||||
transfer: 'transfer details',
|
||||
extras: 'extra information'
|
||||
};
|
||||
const STAGES_BASIC = [TITLES.transfer];
|
||||
const STAGES_EXTRA = [TITLES.transfer, TITLES.extras];
|
||||
|
||||
export const WALLET_WARNING_SPENT_TODAY_LIMIT = 'WALLET_WARNING_SPENT_TODAY_LIMIT';
|
||||
|
||||
export default class TransferStore {
|
||||
@observable stage = 0;
|
||||
@observable extras = false;
|
||||
@observable isEth = true;
|
||||
@observable valueAll = false;
|
||||
@observable sending = false;
|
||||
@observable token = ETH_TOKEN;
|
||||
|
||||
@observable data = '';
|
||||
@observable dataError = null;
|
||||
|
||||
@observable recipient = '';
|
||||
@observable recipientError = ERRORS.requireRecipient;
|
||||
|
||||
@observable sender = '';
|
||||
@observable senderError = null;
|
||||
@observable sendersBalances = {};
|
||||
|
||||
@observable total = '0.0';
|
||||
@observable totalError = null;
|
||||
|
||||
@observable value = '0.0';
|
||||
@observable valueError = null;
|
||||
|
||||
@observable walletWarning = null;
|
||||
|
||||
account = null;
|
||||
balance = null;
|
||||
|
||||
onClose = noop;
|
||||
senders = null;
|
||||
isWallet = false;
|
||||
tokenContract = null;
|
||||
tokens = {};
|
||||
wallet = null;
|
||||
|
||||
gasStore = null;
|
||||
|
||||
constructor (api, props) {
|
||||
this.api = api;
|
||||
|
||||
const { account, balance, gasLimit, onClose, senders, newError, sendersBalances, tokens } = props;
|
||||
|
||||
this.account = account;
|
||||
this.balance = balance;
|
||||
this.isWallet = account && account.wallet;
|
||||
this.newError = newError;
|
||||
this.tokens = tokens;
|
||||
|
||||
this.gasStore = new GasPriceStore(api, { gasLimit });
|
||||
this.tokenContract = api.newContract(tokenAbi, '');
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (onClose) {
|
||||
this.onClose = onClose;
|
||||
}
|
||||
}
|
||||
|
||||
@computed get steps () {
|
||||
const steps = [].concat(this.extras ? STAGES_EXTRA : STAGES_BASIC);
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
@computed get isValid () {
|
||||
const detailsValid = !this.recipientError && !this.valueError && !this.totalError && !this.senderError;
|
||||
const extrasValid = !this.gasStore.errorGas && !this.gasStore.errorPrice && !this.gasStore.conditionBlockError && !this.totalError;
|
||||
const verifyValid = !this.passwordError;
|
||||
|
||||
switch (this.stage) {
|
||||
case 0:
|
||||
return detailsValid;
|
||||
|
||||
case 1:
|
||||
return this.extras
|
||||
? extrasValid
|
||||
: verifyValid;
|
||||
|
||||
case 2:
|
||||
return verifyValid;
|
||||
}
|
||||
}
|
||||
|
||||
@action onNext = () => {
|
||||
this.stage += 1;
|
||||
}
|
||||
|
||||
@action onPrev = () => {
|
||||
this.stage -= 1;
|
||||
}
|
||||
|
||||
@action handleClose = () => {
|
||||
this.stage = 0;
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
@action onUpdateDetails = (type, value) => {
|
||||
switch (type) {
|
||||
case 'all':
|
||||
return this._onUpdateAll(value);
|
||||
|
||||
case 'extras':
|
||||
return this._onUpdateExtras(value);
|
||||
|
||||
case 'data':
|
||||
return this._onUpdateData(value);
|
||||
|
||||
case 'gas':
|
||||
return this._onUpdateGas(value);
|
||||
|
||||
case 'gasPrice':
|
||||
return this._onUpdateGasPrice(value);
|
||||
|
||||
case 'recipient':
|
||||
return this._onUpdateRecipient(value);
|
||||
|
||||
case 'sender':
|
||||
return this._onUpdateSender(value);
|
||||
|
||||
case 'token':
|
||||
return this._onUpdateToken(value);
|
||||
|
||||
case 'value':
|
||||
return this._onUpdateValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
@action onSend = () => {
|
||||
this.onNext();
|
||||
this.sending = true;
|
||||
|
||||
this
|
||||
.send()
|
||||
.catch((error) => {
|
||||
this.newError(error);
|
||||
})
|
||||
.then(() => {
|
||||
this.handleClose();
|
||||
});
|
||||
}
|
||||
|
||||
@action _onUpdateAll = (valueAll) => {
|
||||
this.valueAll = valueAll;
|
||||
this.recalculateGas();
|
||||
}
|
||||
|
||||
@action _onUpdateExtras = (extras) => {
|
||||
this.extras = extras;
|
||||
}
|
||||
|
||||
@action _onUpdateData = (data) => {
|
||||
this.data = data;
|
||||
this.recalculateGas();
|
||||
}
|
||||
|
||||
@action _onUpdateGas = (gas) => {
|
||||
this.recalculate();
|
||||
}
|
||||
|
||||
@action _onUpdateGasPrice = (gasPrice) => {
|
||||
this.recalculate();
|
||||
}
|
||||
|
||||
@action _onUpdateRecipient = (recipient) => {
|
||||
let recipientError = null;
|
||||
|
||||
if (!recipient || !recipient.length) {
|
||||
recipientError = ERRORS.requireRecipient;
|
||||
} else if (!this.api.util.isAddressValid(recipient)) {
|
||||
recipientError = ERRORS.invalidAddress;
|
||||
}
|
||||
|
||||
transaction(() => {
|
||||
this.recipient = recipient;
|
||||
this.recipientError = recipientError;
|
||||
|
||||
this.recalculateGas();
|
||||
});
|
||||
}
|
||||
|
||||
@action _onUpdateSender = (sender) => {
|
||||
let senderError = null;
|
||||
|
||||
if (!sender || !sender.length) {
|
||||
senderError = ERRORS.requireSender;
|
||||
} else if (!this.api.util.isAddressValid(sender)) {
|
||||
senderError = ERRORS.invalidAddress;
|
||||
}
|
||||
|
||||
transaction(() => {
|
||||
this.sender = sender;
|
||||
this.senderError = senderError;
|
||||
|
||||
this.recalculateGas();
|
||||
});
|
||||
}
|
||||
|
||||
@action _onUpdateToken = (tokenId) => {
|
||||
transaction(() => {
|
||||
this.token = { ...this.tokens[tokenId] };
|
||||
this.isEth = this.token.native;
|
||||
|
||||
this.recalculateGas();
|
||||
});
|
||||
}
|
||||
|
||||
@action _onUpdateValue = (value) => {
|
||||
let valueError = this._validatePositiveNumber(value);
|
||||
|
||||
if (!valueError) {
|
||||
valueError = this._validateDecimals(value);
|
||||
}
|
||||
|
||||
if (this.isWallet && !valueError) {
|
||||
const { last, limit, spent } = this.wallet.dailylimit;
|
||||
const remains = fromWei(limit.minus(spent));
|
||||
const today = Math.round(Date.now() / (24 * 3600 * 1000));
|
||||
const isResetable = last.lt(today);
|
||||
|
||||
if ((!isResetable && remains.lt(value)) || fromWei(limit).lt(value)) {
|
||||
// already spent too much today
|
||||
this.walletWarning = WALLET_WARNING_SPENT_TODAY_LIMIT;
|
||||
} else if (this.walletWarning) {
|
||||
// all ok
|
||||
this.walletWarning = null;
|
||||
}
|
||||
}
|
||||
|
||||
transaction(() => {
|
||||
this.value = value;
|
||||
this.valueError = valueError;
|
||||
|
||||
this.recalculateGas();
|
||||
});
|
||||
}
|
||||
|
||||
@action recalculateGas = (redo = true) => {
|
||||
if (!this.isValid) {
|
||||
return this.recalculate(redo);
|
||||
}
|
||||
|
||||
return this
|
||||
.estimateGas()
|
||||
.then((gasEst) => {
|
||||
let gas = gasEst;
|
||||
|
||||
if (gas.gt(DEFAULT_GAS)) {
|
||||
gas = gas.mul(1.2);
|
||||
}
|
||||
|
||||
transaction(() => {
|
||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
|
||||
this.recalculate(redo);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
this.gasStore.setEstimatedError();
|
||||
console.warn('etimateGas', error);
|
||||
this.recalculate(redo);
|
||||
});
|
||||
}
|
||||
|
||||
getBalance (forceSender = false) {
|
||||
if (this.isWallet && !forceSender) {
|
||||
return this.balance;
|
||||
}
|
||||
|
||||
const balance = this.senders
|
||||
? this.sendersBalances[this.sender]
|
||||
: this.balance;
|
||||
|
||||
return balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the balance of the selected token
|
||||
* (in WEI for ETH, without formating for other tokens)
|
||||
*/
|
||||
getTokenBalance (token = this.token, forceSender = false) {
|
||||
return new BigNumber(this.balance[token.id] || 0);
|
||||
}
|
||||
|
||||
getTokenValue (token = this.token, value = this.value, inverse = false) {
|
||||
let _value;
|
||||
|
||||
try {
|
||||
_value = new BigNumber(value || 0);
|
||||
} catch (error) {
|
||||
_value = new BigNumber(0);
|
||||
}
|
||||
|
||||
if (inverse) {
|
||||
return _value.div(token.format);
|
||||
}
|
||||
|
||||
return _value.mul(token.format);
|
||||
}
|
||||
|
||||
getValues (_gasTotal) {
|
||||
const gasTotal = new BigNumber(_gasTotal || 0);
|
||||
const { valueAll, isEth, isWallet } = this;
|
||||
|
||||
log.debug('@getValues', 'gas', gasTotal.toFormat());
|
||||
|
||||
if (!valueAll) {
|
||||
const value = this.getTokenValue();
|
||||
|
||||
// If it's a token or a wallet, eth is the estimated gas,
|
||||
// and value is the user input
|
||||
if (!isEth || isWallet) {
|
||||
return {
|
||||
eth: gasTotal,
|
||||
token: value
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, eth is the sum of the gas and the user input
|
||||
const totalEthValue = gasTotal.plus(value);
|
||||
|
||||
return {
|
||||
eth: totalEthValue,
|
||||
token: value
|
||||
};
|
||||
}
|
||||
|
||||
// If it's the total balance that needs to be sent, send the total balance
|
||||
// if it's not a proper ETH transfer
|
||||
if (!isEth || isWallet) {
|
||||
const tokenBalance = this.getTokenBalance();
|
||||
|
||||
return {
|
||||
eth: gasTotal,
|
||||
token: tokenBalance
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, substract the gas estimate
|
||||
const availableEth = this.getTokenBalance(ETH_TOKEN);
|
||||
const totalEthValue = availableEth.gt(gasTotal)
|
||||
? availableEth.minus(gasTotal)
|
||||
: new BigNumber(0);
|
||||
|
||||
return {
|
||||
eth: totalEthValue.plus(gasTotal),
|
||||
token: totalEthValue
|
||||
};
|
||||
}
|
||||
|
||||
getFormattedTokenValue (tokenValue) {
|
||||
return this.getTokenValue(this.token, tokenValue, true);
|
||||
}
|
||||
|
||||
@action recalculate = (redo = false) => {
|
||||
const { account } = this;
|
||||
|
||||
if (!account || !this.balance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const balance = this.getBalance();
|
||||
|
||||
if (!balance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gasTotal = new BigNumber(this.gasStore.price || 0).mul(new BigNumber(this.gasStore.gas || 0));
|
||||
|
||||
const ethBalance = this.getTokenBalance(ETH_TOKEN, true);
|
||||
const tokenBalance = this.getTokenBalance();
|
||||
const { eth, token } = this.getValues(gasTotal);
|
||||
|
||||
let totalError = null;
|
||||
let valueError = null;
|
||||
|
||||
if (eth.gt(ethBalance)) {
|
||||
totalError = ERRORS.largeAmount;
|
||||
}
|
||||
|
||||
if (token && token.gt(tokenBalance)) {
|
||||
valueError = ERRORS.largeAmount;
|
||||
}
|
||||
|
||||
log.debug('@recalculate', {
|
||||
eth: eth.toFormat(),
|
||||
token: token.toFormat(),
|
||||
ethBalance: ethBalance.toFormat(),
|
||||
tokenBalance: tokenBalance.toFormat(),
|
||||
gasTotal: gasTotal.toFormat()
|
||||
});
|
||||
|
||||
transaction(() => {
|
||||
this.totalError = totalError;
|
||||
this.valueError = valueError;
|
||||
this.gasStore.setErrorTotal(totalError);
|
||||
this.gasStore.setEthValue(eth.sub(gasTotal));
|
||||
|
||||
this.total = this.api.util.fromWei(eth).toFixed();
|
||||
|
||||
const nextValue = this.getFormattedTokenValue(token);
|
||||
let prevValue;
|
||||
|
||||
try {
|
||||
prevValue = new BigNumber(this.value || 0);
|
||||
} catch (error) {
|
||||
prevValue = new BigNumber(0);
|
||||
}
|
||||
|
||||
// Change the input only if necessary
|
||||
if (!nextValue.eq(prevValue)) {
|
||||
this.value = nextValue.toString();
|
||||
}
|
||||
|
||||
// Re Calculate gas once more to be sure
|
||||
if (redo) {
|
||||
return this.recalculateGas(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
send () {
|
||||
const { options, values } = this._getTransferParams();
|
||||
|
||||
log.debug('@send', 'transfer value', options.value && options.value.toFormat());
|
||||
|
||||
return this._getTransferMethod().postTransaction(options, values);
|
||||
}
|
||||
|
||||
_estimateGas (forceToken = false) {
|
||||
const { options, values } = this._getTransferParams(true, forceToken);
|
||||
|
||||
return this._getTransferMethod(true, forceToken).estimateGas(options, values);
|
||||
}
|
||||
|
||||
estimateGas () {
|
||||
return this._estimateGas();
|
||||
}
|
||||
|
||||
_getTransferMethod (gas = false, forceToken = false) {
|
||||
const { isEth, isWallet } = this;
|
||||
|
||||
if (isEth && !isWallet && !forceToken) {
|
||||
return gas ? this.api.eth : this.api.parity;
|
||||
}
|
||||
|
||||
if (isWallet && !forceToken) {
|
||||
return this.wallet.instance.execute;
|
||||
}
|
||||
|
||||
return this.tokenContract.at(this.token.address).instance.transfer;
|
||||
}
|
||||
|
||||
_getData (gas = false) {
|
||||
const { isEth, isWallet } = this;
|
||||
|
||||
if (!isWallet || isEth) {
|
||||
return this.data && this.data.length ? this.data : '';
|
||||
}
|
||||
|
||||
const func = this._getTransferMethod(gas, true);
|
||||
const { options, values } = this._getTransferParams(gas, true);
|
||||
|
||||
return this.tokenContract.at(this.token.address).getCallData(func, options, values);
|
||||
}
|
||||
|
||||
_getTransferParams (gas = false, forceToken = false) {
|
||||
const { isEth, isWallet } = this;
|
||||
|
||||
const to = (isEth && !isWallet) ? this.recipient
|
||||
: (this.isWallet ? this.wallet.address : this.token.address);
|
||||
|
||||
const options = this.gasStore.overrideTransaction({
|
||||
from: this.sender || this.account.address,
|
||||
to
|
||||
});
|
||||
|
||||
if (gas) {
|
||||
options.gas = MAX_GAS_ESTIMATION;
|
||||
}
|
||||
|
||||
const gasTotal = new BigNumber(options.gas || DEFAULT_GAS).mul(options.gasPrice || DEFAULT_GASPRICE);
|
||||
const { token } = this.getValues(gasTotal);
|
||||
|
||||
if (isEth && !isWallet && !forceToken) {
|
||||
options.value = token;
|
||||
options.data = this._getData(gas);
|
||||
|
||||
return { options, values: [] };
|
||||
}
|
||||
|
||||
if (isWallet && !forceToken) {
|
||||
const to = isEth ? this.recipient : this.token.address;
|
||||
const value = isEth ? token : new BigNumber(0);
|
||||
|
||||
const values = [
|
||||
to, value,
|
||||
this._getData(gas)
|
||||
];
|
||||
|
||||
return { options, values };
|
||||
}
|
||||
|
||||
const values = [
|
||||
this.recipient,
|
||||
token.toFixed(0)
|
||||
];
|
||||
|
||||
return { options, values };
|
||||
}
|
||||
|
||||
_validatePositiveNumber (num) {
|
||||
try {
|
||||
const v = new BigNumber(num);
|
||||
|
||||
if (v.lt(0)) {
|
||||
return ERRORS.invalidAmount;
|
||||
}
|
||||
} catch (e) {
|
||||
return ERRORS.invalidAmount;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_validateDecimals (num) {
|
||||
const s = new BigNumber(num).mul(this.token.format || 1).toFixed();
|
||||
|
||||
if (s.indexOf('.') !== -1) {
|
||||
return ERRORS.invalidDecimals;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,170 +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/>.
|
||||
*/
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&>div {
|
||||
flex: 0 1 50%;
|
||||
position: relative;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.gaseditor {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.info {
|
||||
line-height: 1.618em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floatbutton {
|
||||
float: right;
|
||||
margin-left: -100%;
|
||||
margin-top: 28px;
|
||||
text-align: right;
|
||||
|
||||
&>div {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.token {
|
||||
height: 32px;
|
||||
padding: 4px 0;
|
||||
|
||||
img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin: 0 16px 0 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
div {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.tokenSelect {
|
||||
.token {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.tokenbalance {
|
||||
display: inline-block;
|
||||
color: #aaa;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.tokenname {}
|
||||
|
||||
.address {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.from {
|
||||
padding: 25px 0 0 48px !important;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.fromaddress {
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.frombalance {
|
||||
display: inline-block;
|
||||
color: #aaa;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
}
|
||||
|
||||
.grayscale {
|
||||
-webkit-filter: grayscale(1);
|
||||
filter: grayscale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.hdraccount {
|
||||
padding: 1em 0 0 0;
|
||||
}
|
||||
|
||||
.hdrimage {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hdrdetails {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hdrname {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hdraddress {
|
||||
color: #aaa;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.totalAmount {
|
||||
padding-top: 6px;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.totalTx {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.contentTitle {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.warning {
|
||||
border-radius: 0.5em;
|
||||
background: #f80;
|
||||
color: white;
|
||||
font-size: 0.75em;
|
||||
margin-bottom: 1em;
|
||||
padding: 0.75em;
|
||||
text-align: center;
|
||||
}
|
@ -1,314 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import { nullableProptype } from '@parity/shared/util/proptypes';
|
||||
import { Button, IdentityIcon, Portal, Warning } from '@parity/ui';
|
||||
import { newError } from '@parity/ui/Errors/actions';
|
||||
import { CancelIcon, NextIcon, PrevIcon } from '@parity/ui/Icons';
|
||||
|
||||
import Details from './Details';
|
||||
import Extras from './Extras';
|
||||
|
||||
import TransferStore, { WALLET_WARNING_SPENT_TODAY_LIMIT } from './store';
|
||||
import styles from './transfer.css';
|
||||
|
||||
const STEP_DETAILS = 0;
|
||||
const STEP_EXTRA = 1;
|
||||
|
||||
@observer
|
||||
class Transfer extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
newError: PropTypes.func.isRequired,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
|
||||
account: PropTypes.object,
|
||||
balance: PropTypes.object,
|
||||
onClose: PropTypes.func,
|
||||
senders: nullableProptype(PropTypes.object),
|
||||
sendersBalances: nullableProptype(PropTypes.object),
|
||||
tokens: PropTypes.object,
|
||||
wallet: PropTypes.object
|
||||
}
|
||||
|
||||
store = new TransferStore(this.context.api, this.props);
|
||||
|
||||
render () {
|
||||
const { stage, steps } = this.store;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
activeStep={ stage }
|
||||
buttons={ this.renderDialogActions() }
|
||||
onClose={ this.handleClose }
|
||||
open
|
||||
steps={ steps }
|
||||
>
|
||||
{ this.renderExceptionWarning() }
|
||||
{ this.renderWalletWarning() }
|
||||
{ this.renderPage() }
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderExceptionWarning () {
|
||||
const { errorEstimated } = this.store.gasStore;
|
||||
|
||||
if (!errorEstimated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Warning warning={ errorEstimated } />
|
||||
);
|
||||
}
|
||||
|
||||
renderWalletWarning () {
|
||||
const { walletWarning } = this.store;
|
||||
|
||||
if (!walletWarning) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (walletWarning === WALLET_WARNING_SPENT_TODAY_LIMIT) {
|
||||
const warning = (
|
||||
<FormattedMessage
|
||||
id='transfer.warning.wallet_spent_limit'
|
||||
defaultMessage='This transaction value is above the remaining daily limit. It will need to be confirmed by other owners.'
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Warning warning={ warning } />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
renderAccount () {
|
||||
const { account } = this.props;
|
||||
|
||||
return (
|
||||
<div className={ styles.hdraccount }>
|
||||
<div className={ styles.hdrimage }>
|
||||
<IdentityIcon
|
||||
address={ account.address }
|
||||
center
|
||||
inline
|
||||
/>
|
||||
</div>
|
||||
<div className={ styles.hdrdetails }>
|
||||
<div className={ styles.hdrname }>
|
||||
{ account.name || 'Unnamed' }
|
||||
</div>
|
||||
<div className={ styles.hdraddress }>
|
||||
{ account.address }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderPage () {
|
||||
const { extras, stage } = this.store;
|
||||
|
||||
if (stage === STEP_DETAILS) {
|
||||
return this.renderDetailsPage();
|
||||
} else if (stage === STEP_EXTRA && extras) {
|
||||
return this.renderExtrasPage();
|
||||
}
|
||||
}
|
||||
|
||||
renderDetailsPage () {
|
||||
const { account, balance, senders } = this.props;
|
||||
const { recipient, recipientError, sender, senderError } = this.store;
|
||||
const { valueAll, extras, token, total, totalError, value, valueError } = this.store;
|
||||
|
||||
return (
|
||||
<Details
|
||||
address={ account.address }
|
||||
all={ valueAll }
|
||||
balance={ balance }
|
||||
extras={ extras }
|
||||
onChange={ this.store.onUpdateDetails }
|
||||
recipient={ recipient }
|
||||
recipientError={ recipientError }
|
||||
sender={ sender }
|
||||
senderError={ senderError }
|
||||
senders={ senders }
|
||||
token={ token }
|
||||
total={ total }
|
||||
totalError={ totalError }
|
||||
value={ value }
|
||||
valueError={ valueError }
|
||||
wallet={ account.wallet && this.props.wallet }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderExtrasPage () {
|
||||
if (!this.store.gasStore.histogram) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { isEth, data, dataError, total, totalError } = this.store;
|
||||
|
||||
return (
|
||||
<Extras
|
||||
data={ data }
|
||||
dataError={ dataError }
|
||||
gasStore={ this.store.gasStore }
|
||||
isEth={ isEth }
|
||||
onChange={ this.store.onUpdateDetails }
|
||||
total={ total }
|
||||
totalError={ totalError }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDialogActions () {
|
||||
const { account } = this.props;
|
||||
const { extras, sending, stage } = this.store;
|
||||
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancel'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.buttons.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
}
|
||||
onClick={ this.handleClose }
|
||||
/>
|
||||
);
|
||||
const nextBtn = (
|
||||
<Button
|
||||
disabled={ !this.store.isValid }
|
||||
icon={ <NextIcon /> }
|
||||
key='next'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.buttons.next'
|
||||
defaultMessage='Next'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.onNext }
|
||||
/>
|
||||
);
|
||||
const prevBtn = (
|
||||
<Button
|
||||
icon={ <PrevIcon /> }
|
||||
key='back'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.buttons.back'
|
||||
defaultMessage='Back'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.onPrev }
|
||||
/>
|
||||
);
|
||||
const sendBtn = (
|
||||
<Button
|
||||
disabled={ !this.store.isValid || sending }
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ account.address }
|
||||
button
|
||||
/>
|
||||
}
|
||||
key='send'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='transfer.buttons.send'
|
||||
defaultMessage='Send'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.onSend }
|
||||
/>
|
||||
);
|
||||
|
||||
switch (stage) {
|
||||
case 0:
|
||||
return extras
|
||||
? [cancelBtn, nextBtn]
|
||||
: [cancelBtn, sendBtn];
|
||||
case 1:
|
||||
return [cancelBtn, prevBtn, sendBtn];
|
||||
default:
|
||||
return [cancelBtn];
|
||||
}
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.store.handleClose();
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const { tokens } = initState;
|
||||
const { address } = initProps.account;
|
||||
|
||||
const isWallet = initProps.account && initProps.account.wallet;
|
||||
const wallet = isWallet
|
||||
? initState.wallet.wallets[address]
|
||||
: null;
|
||||
|
||||
const senders = isWallet
|
||||
? Object
|
||||
.values(initState.personal.accounts)
|
||||
.filter((account) => wallet.owners.includes(account.address))
|
||||
.reduce((accounts, account) => {
|
||||
accounts[account.address] = account;
|
||||
return accounts;
|
||||
}, {})
|
||||
: null;
|
||||
|
||||
return (state) => {
|
||||
const { gasLimit } = state.nodeStatus;
|
||||
const { balances } = state;
|
||||
|
||||
const balance = balances[address];
|
||||
const sendersBalances = senders ? pick(balances, Object.keys(senders)) : null;
|
||||
|
||||
return { balance, gasLimit, senders, sendersBalances, tokens, wallet };
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
newError
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Transfer);
|
@ -1,31 +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/>.
|
||||
*/
|
||||
.spacing {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-top: .5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: .5em;
|
||||
}
|
@ -1,38 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { DoneIcon } from '@parity/ui/Icons';
|
||||
|
||||
import styles from './done.css';
|
||||
|
||||
export default class Done extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<DoneIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='verification.done.message'
|
||||
defaultMessage='Congratulations, your account is verified!'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './done';
|
@ -1,49 +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/>.
|
||||
*/
|
||||
|
||||
.spacing {
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-top: .5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.message {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.terms {
|
||||
line-height: 1.3;
|
||||
opacity: .7;
|
||||
|
||||
ul {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: .2em;
|
||||
margin-bottom: .2em;
|
||||
}
|
||||
}
|
@ -1,291 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
import { fromWei } from '@parity/api/util/wei';
|
||||
import { Checkbox, Form, Input } from '@parity/ui';
|
||||
import { DoneIcon, ErrorIcon, InfoIcon } from '@parity/ui/Icons';
|
||||
import { nullableProptype } from '@parity/shared/util/proptypes';
|
||||
|
||||
import smsTermsOfService from '../sms-verification/terms-of-service';
|
||||
import emailTermsOfService from '../email-verification/terms-of-service';
|
||||
import { howSMSVerificationWorks, howEmailVerificationWorks } from '../how-it-works';
|
||||
|
||||
import styles from './gatherData.css';
|
||||
|
||||
const boolOfError = PropTypes.oneOfType([ PropTypes.bool, PropTypes.instanceOf(Error) ]);
|
||||
|
||||
export default class GatherData extends Component {
|
||||
static propTypes = {
|
||||
fee: React.PropTypes.instanceOf(BigNumber),
|
||||
fields: PropTypes.array.isRequired,
|
||||
accountHasRequested: nullableProptype(PropTypes.bool.isRequired),
|
||||
isServerRunning: nullableProptype(PropTypes.bool.isRequired),
|
||||
isAbleToRequest: nullableProptype(boolOfError.isRequired),
|
||||
accountIsVerified: nullableProptype(PropTypes.bool.isRequired),
|
||||
method: PropTypes.string.isRequired,
|
||||
setConsentGiven: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
render () {
|
||||
const { method, accountIsVerified } = this.props;
|
||||
const termsOfService = method === 'email' ? emailTermsOfService : smsTermsOfService;
|
||||
const howItWorks = method === 'email' ? howEmailVerificationWorks : howSMSVerificationWorks;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
{ howItWorks }
|
||||
{ this.renderServerRunning() }
|
||||
{ this.renderFee() }
|
||||
{ this.renderCertified() }
|
||||
{ this.renderRequested() }
|
||||
{ this.renderFields() }
|
||||
{ this.renderIfAbleToRequest() }
|
||||
<Checkbox
|
||||
className={ styles.spacing }
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.termsOfService'
|
||||
defaultMessage='I agree to the terms and conditions below.'
|
||||
/>
|
||||
}
|
||||
disabled={ accountIsVerified }
|
||||
onClick={ this.consentOnChange }
|
||||
/>
|
||||
<div className={ styles.terms }>{ termsOfService }</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
renderServerRunning () {
|
||||
const { isServerRunning } = this.props;
|
||||
|
||||
if (isServerRunning) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<DoneIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isServerRunning.true'
|
||||
defaultMessage='The verification server is running.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (isServerRunning === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isServerRunning.false'
|
||||
defaultMessage='The verification server is not running.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isServerRunning.pending'
|
||||
defaultMessage='Checking if the verification server is running…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderFee () {
|
||||
const { fee } = this.props;
|
||||
|
||||
if (!fee) {
|
||||
return (<p>Fetching the fee…</p>);
|
||||
}
|
||||
if (fee.eq(0)) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.nofee'
|
||||
defaultMessage='There is no additional fee.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.fee'
|
||||
defaultMessage='The additional fee is {amount} ETH.'
|
||||
values={ {
|
||||
amount: fromWei(fee).toFixed(3)
|
||||
} }
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderCertified () {
|
||||
const { accountIsVerified } = this.props;
|
||||
|
||||
if (accountIsVerified) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.accountIsVerified.true'
|
||||
defaultMessage='Your account is already verified.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (accountIsVerified === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<DoneIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.accountIsVerified.false'
|
||||
defaultMessage='Your account is not verified yet.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.accountIsVerified.pending'
|
||||
defaultMessage='Checking if your account is verified…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderRequested () {
|
||||
const { accountIsVerified, accountHasRequested } = this.props;
|
||||
|
||||
// If the account is verified, don't show that it has requested verification.
|
||||
if (accountIsVerified) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (accountHasRequested) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<InfoIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.accountHasRequested.true'
|
||||
defaultMessage='You already requested verification from this account.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if (accountHasRequested === false) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<DoneIcon />
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.accountHasRequested.false'
|
||||
defaultMessage='You did not request verification from this account yet.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.accountHasRequested.pending'
|
||||
defaultMessage='Checking if you requested verification…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderFields () {
|
||||
const { accountIsVerified, fields } = this.props;
|
||||
|
||||
const rendered = fields.map((field, index) => {
|
||||
const onChange = (_, v) => {
|
||||
field.onChange(v);
|
||||
};
|
||||
const onSubmit = field.onChange;
|
||||
|
||||
return (
|
||||
<Input
|
||||
autoFocus={ index === 0 }
|
||||
className={ styles.field }
|
||||
key={ field.key }
|
||||
label={ field.label }
|
||||
hint={ field.hint }
|
||||
error={ field.error }
|
||||
disabled={ accountIsVerified }
|
||||
onChange={ onChange }
|
||||
onSubmit={ onSubmit }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (<div>{rendered}</div>);
|
||||
}
|
||||
|
||||
renderIfAbleToRequest () {
|
||||
const { accountIsVerified, isAbleToRequest } = this.props;
|
||||
|
||||
// If the account is verified, don't show a warning.
|
||||
// If the client is able to send the request, don't show a warning
|
||||
if (accountIsVerified || isAbleToRequest === true) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAbleToRequest === null) {
|
||||
return (
|
||||
<p className={ styles.message }>
|
||||
<FormattedMessage
|
||||
id='ui.verification.gatherData.isAbleToRequest.pending'
|
||||
defaultMessage='Validating your input…'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
} else if (isAbleToRequest) {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<ErrorIcon />
|
||||
<p className={ styles.message }>
|
||||
{ isAbleToRequest.message }
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
consentOnChange = (_, consentGiven) => {
|
||||
this.props.setConsentGiven(consentGiven);
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './gatherData';
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './queryCode';
|
@ -1,88 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { nodeOrStringProptype } from '@parity/shared/util/proptypes';
|
||||
import { Form, Input } from '@parity/ui';
|
||||
|
||||
export default class QueryCode extends Component {
|
||||
static propTypes = {
|
||||
receiver: PropTypes.string.isRequired,
|
||||
hint: nodeOrStringProptype(),
|
||||
isCodeValid: PropTypes.bool.isRequired,
|
||||
setCode: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
hint: (
|
||||
<FormattedMessage
|
||||
id='verification.code.hint'
|
||||
defaultMessage='Enter the code you received.'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { receiver, hint, isCodeValid } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.code.sent'
|
||||
defaultMessage='The verification code has been sent to {receiver}.'
|
||||
values={ {
|
||||
receiver
|
||||
} }
|
||||
/>
|
||||
</p>
|
||||
<Input
|
||||
autoFocus
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='verification.code.label'
|
||||
defaultMessage='verification code'
|
||||
/>
|
||||
}
|
||||
hint={ hint }
|
||||
error={
|
||||
isCodeValid
|
||||
? null
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='verification.code.error'
|
||||
defaultMessage='invalid code'
|
||||
/>
|
||||
)
|
||||
}
|
||||
onChange={ this.onChange }
|
||||
onSubmit={ this.onSubmit }
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (_, code) => {
|
||||
this.props.setCode(code.trim());
|
||||
}
|
||||
|
||||
onSubmit = (code) => {
|
||||
this.props.setCode(code.trim());
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './sendConfirmation';
|
@ -1,20 +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/>.
|
||||
*/
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
@ -1,66 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { nullableProptype } from '@parity/shared/util/proptypes';
|
||||
import TxHash from '@parity/ui/TxHash';
|
||||
|
||||
import { POSTING_CONFIRMATION, POSTED_CONFIRMATION } from '../store';
|
||||
|
||||
import styles from './sendConfirmation.css';
|
||||
|
||||
export default class SendConfirmation extends Component {
|
||||
static propTypes = {
|
||||
step: PropTypes.any.isRequired,
|
||||
tx: nullableProptype(PropTypes.any.isRequired)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { step, tx } = this.props;
|
||||
|
||||
if (step === POSTING_CONFIRMATION) {
|
||||
return (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.confirmation.authorise'
|
||||
defaultMessage='The verification code will be sent to the contract. Please authorize this using the Parity Signer.'
|
||||
/>
|
||||
</p>);
|
||||
}
|
||||
|
||||
if (step === POSTED_CONFIRMATION) {
|
||||
return (
|
||||
<div className={ styles.centered }>
|
||||
<TxHash
|
||||
hash={ tx }
|
||||
maxConfirmations={ 2 }
|
||||
/>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.confirmation.windowOpen'
|
||||
defaultMessage='Please keep this window open.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './sendRequest';
|
@ -1,20 +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/>.
|
||||
*/
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
@ -1,78 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { nullableProptype } from '@parity/shared/util/proptypes';
|
||||
import TxHash from '@parity/ui/TxHash';
|
||||
|
||||
import { POSTING_REQUEST, POSTED_REQUEST, REQUESTING_CODE } from '../store';
|
||||
|
||||
import styles from './sendRequest.css';
|
||||
|
||||
export default class SendRequest extends Component {
|
||||
static propTypes = {
|
||||
step: PropTypes.any.isRequired,
|
||||
tx: nullableProptype(PropTypes.any.isRequired)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { step, tx } = this.props;
|
||||
|
||||
switch (step) {
|
||||
case POSTING_REQUEST:
|
||||
return (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.request.authorise'
|
||||
defaultMessage='A verification request will be sent to the contract. Please authorize this using the Parity Signer.'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
|
||||
case POSTED_REQUEST:
|
||||
return (
|
||||
<div className={ styles.centered }>
|
||||
<TxHash
|
||||
hash={ tx }
|
||||
maxConfirmations={ 1 }
|
||||
/>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.request.windowOpen'
|
||||
defaultMessage='Please keep this window open.'
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
case REQUESTING_CODE:
|
||||
return (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.request.requesting'
|
||||
defaultMessage='Requesting a code from the Parity server and waiting for the puzzle to be put into the contract.'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,131 +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/>.
|
||||
|
||||
import { observable, computed, action } from 'mobx';
|
||||
|
||||
import { bytesToHex } from '@parity/api/util/format';
|
||||
import { sha3 } from '@parity/api/util/sha3';
|
||||
import EmailVerificationABI from '@parity/shared/contracts/abi/email-verification.json';
|
||||
|
||||
import VerificationStore, {
|
||||
LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE
|
||||
} from './store';
|
||||
import { isServerRunning, hasReceivedCode, postToServer } from './email-verification';
|
||||
|
||||
const ZERO20 = '0x0000000000000000000000000000000000000000';
|
||||
|
||||
// name in the `BadgeReg.sol` contract
|
||||
const EMAIL_VERIFICATION = 'emailverification';
|
||||
|
||||
export default class EmailVerificationStore extends VerificationStore {
|
||||
@observable email = '';
|
||||
|
||||
@computed get isEmailValid () {
|
||||
// See https://davidcel.is/posts/stop-validating-email-addresses-with-regex/
|
||||
return this.email && this.email.indexOf('@') >= 0;
|
||||
}
|
||||
|
||||
@computed get isStepValid () {
|
||||
if (this.step === DONE) {
|
||||
return true;
|
||||
}
|
||||
if (this.error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this.step) {
|
||||
case LOADING:
|
||||
return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null;
|
||||
case QUERY_DATA:
|
||||
return this.isEmailValid && this.consentGiven && this.isAbleToRequest === true;
|
||||
case QUERY_CODE:
|
||||
return this.requestTx && this.isCodeValid === true;
|
||||
case POSTED_CONFIRMATION:
|
||||
return !!this.confirmationTx;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constructor (api, account, isTestnet) {
|
||||
super(api, EmailVerificationABI, EMAIL_VERIFICATION, account, isTestnet);
|
||||
}
|
||||
|
||||
isServerRunning = () => {
|
||||
return isServerRunning(this.isTestnet);
|
||||
}
|
||||
|
||||
checkIfReceivedCode = () => {
|
||||
return hasReceivedCode(this.email, this.account, this.isTestnet);
|
||||
}
|
||||
|
||||
// If the email has already been used for verification of another account,
|
||||
// we prevent the user from wasting ETH to request another verification.
|
||||
@action setIfAbleToRequest = () => {
|
||||
const { isEmailValid } = this;
|
||||
|
||||
if (!isEmailValid) {
|
||||
this.isAbleToRequest = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const { contract, email } = this;
|
||||
const emailHash = sha3.text(email);
|
||||
|
||||
this.isAbleToRequest = null;
|
||||
contract
|
||||
.instance.reverse
|
||||
.call({}, [ emailHash ])
|
||||
.then((address) => {
|
||||
if (address === ZERO20) {
|
||||
this.isAbleToRequest = true;
|
||||
} else {
|
||||
this.isAbleToRequest = new Error('Another account has been verified using this e-mail.');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to check if able to send request: ' + err.message;
|
||||
});
|
||||
}
|
||||
|
||||
// Determine the values relevant for checking if the last request contains
|
||||
// the same data as the current one.
|
||||
requestValues = () => [ sha3.text(this.email) ]
|
||||
|
||||
shallSkipRequest = (currentValues) => {
|
||||
const { accountHasRequested } = this;
|
||||
const lastRequest = this.lastRequestValues;
|
||||
|
||||
if (!accountHasRequested) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
// If the last email verification `request` for the selected address contains
|
||||
// the same email as the current one, don't send another request to save ETH.
|
||||
const skip = currentValues[0] === bytesToHex(lastRequest.emailHash.value);
|
||||
|
||||
return Promise.resolve(skip);
|
||||
}
|
||||
|
||||
@action setEmail = (email) => {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
requestCode = () => {
|
||||
const { email, account, isTestnet } = this;
|
||||
|
||||
return postToServer({ email, address: account }, isTestnet);
|
||||
}
|
||||
}
|
@ -1,68 +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/>.
|
||||
|
||||
import { stringify } from 'querystring';
|
||||
|
||||
export const isServerRunning = (isTestnet = false) => {
|
||||
const port = isTestnet ? 28443 : 18443;
|
||||
|
||||
return fetch(`https://email-verification.parity.io:${port}/health`, {
|
||||
mode: 'cors',
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.ok;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export const hasReceivedCode = (email, address, isTestnet = false) => {
|
||||
const port = isTestnet ? 28443 : 18443;
|
||||
const query = stringify({ email, address });
|
||||
|
||||
return fetch(`https://email-verification.parity.io:${port}/?${query}`, {
|
||||
mode: 'cors',
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.ok;
|
||||
})
|
||||
.catch(() => {
|
||||
return false; // todo: check for 404
|
||||
});
|
||||
};
|
||||
|
||||
export const postToServer = (query, isTestnet = false) => {
|
||||
const port = isTestnet ? 28443 : 18443;
|
||||
|
||||
query = stringify(query);
|
||||
|
||||
return fetch(`https://email-verification.parity.io:${port}/?${query}`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.json().then((data) => {
|
||||
if (res.ok) {
|
||||
return data.message;
|
||||
}
|
||||
throw new Error(data.message || 'unknown error');
|
||||
});
|
||||
});
|
||||
};
|
@ -1,27 +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/>.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default (
|
||||
<ul>
|
||||
<li>This privacy notice relates to your use of the Parity email verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
|
||||
<li>We collect your email address when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the email address to prevent duplicated accounts. The cryptographic hash of your email address is also stored on the blockchain which is public by design. You consent to this use.</li>
|
||||
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
|
||||
<li>Your email address is transmitted to a third party EU email verification service mailjet for the sole purpose of the email verification. You consent to this use. Mailjet's privacy policy is here: <a href='https://www.mailjet.com/privacy-policy'>https://www.mailjet.com/privacy-policy</a>.</li>
|
||||
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://parity.io/legal.html' }>https://parity.io/legal.html</a>.</li>
|
||||
</ul>
|
||||
);
|
@ -1,41 +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/>.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import styles from './verification.css';
|
||||
|
||||
export const howSMSVerificationWorks = (
|
||||
<div>
|
||||
<p>The following steps will let you prove that you control both an account and a phone number.</p>
|
||||
<ol className={ styles.list }>
|
||||
<li>You send a verification request to a specific contract.</li>
|
||||
<li>Our server puts a puzzle into this contract.</li>
|
||||
<li>The code you receive via SMS is the solution to this puzzle.</li>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const howEmailVerificationWorks = (
|
||||
<div>
|
||||
<p>The following steps will let you prove that you control both an account and an e-mail address.</p>
|
||||
<ol className={ styles.list }>
|
||||
<li>You send a verification request to a specific contract.</li>
|
||||
<li>Our server puts a puzzle into this contract.</li>
|
||||
<li>The code you receive via e-mail is the solution to this puzzle.</li>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
@ -1,17 +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/>.
|
||||
|
||||
export default from './verification';
|
@ -1,90 +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/>.
|
||||
|
||||
import { observable, computed, action } from 'mobx';
|
||||
import phone from 'phoneformat.js';
|
||||
|
||||
import SMSVerificationABI from '@parity/shared/contracts/abi/sms-verification.json';
|
||||
|
||||
import VerificationStore, { LOADING, QUERY_DATA, QUERY_CODE, POSTED_CONFIRMATION, DONE } from './store';
|
||||
import { isServerRunning, hasReceivedCode, postToServer } from './sms-verification';
|
||||
|
||||
// name in the `BadgeReg.sol` contract
|
||||
const SMS_VERIFICATION = 'smsverification';
|
||||
|
||||
export default class SMSVerificationStore extends VerificationStore {
|
||||
@observable number = '';
|
||||
|
||||
@computed get isNumberValid () {
|
||||
return phone.isValidNumber(this.number);
|
||||
}
|
||||
|
||||
@computed get isStepValid () {
|
||||
if (this.step === DONE) {
|
||||
return true;
|
||||
}
|
||||
if (this.error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (this.step) {
|
||||
case LOADING:
|
||||
return this.contract && this.fee && this.accountIsVerified !== null && this.accountHasRequested !== null;
|
||||
case QUERY_DATA:
|
||||
return this.isNumberValid && this.consentGiven;
|
||||
case QUERY_CODE:
|
||||
return this.requestTx && this.isCodeValid === true;
|
||||
case POSTED_CONFIRMATION:
|
||||
return !!this.confirmationTx;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constructor (api, account, isTestnet) {
|
||||
super(api, SMSVerificationABI, SMS_VERIFICATION, account, isTestnet);
|
||||
}
|
||||
|
||||
isServerRunning = () => {
|
||||
return isServerRunning(this.isTestnet);
|
||||
}
|
||||
|
||||
checkIfReceivedCode = () => {
|
||||
return hasReceivedCode(this.number, this.account, this.isTestnet);
|
||||
}
|
||||
|
||||
// SMS verification events don't contain the phone number, so we will have to
|
||||
// send a new request every single time. See below.
|
||||
@action setIfAbleToRequest = () => {
|
||||
this.isAbleToRequest = true;
|
||||
}
|
||||
|
||||
// SMS verification `request` & `confirm` transactions and events don't contain the
|
||||
// phone number, so we will have to send a new request every single time. This may
|
||||
// cost the user more money, but given that it fails otherwise, it seems like a
|
||||
// reasonable tradeoff.
|
||||
shallSkipRequest = () => Promise.resolve(false)
|
||||
|
||||
@action setNumber = (number) => {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
requestCode = () => {
|
||||
const { number, account, isTestnet } = this;
|
||||
|
||||
return postToServer({ number, address: account }, isTestnet);
|
||||
}
|
||||
}
|
@ -1,68 +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/>.
|
||||
|
||||
import { stringify } from 'querystring';
|
||||
|
||||
export const isServerRunning = (isTestnet = false) => {
|
||||
const port = isTestnet ? 8443 : 443;
|
||||
|
||||
return fetch(`https://sms-verification.parity.io:${port}/health`, {
|
||||
mode: 'cors',
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.ok;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export const hasReceivedCode = (number, address, isTestnet = false) => {
|
||||
const port = isTestnet ? 8443 : 443;
|
||||
const query = stringify({ number, address });
|
||||
|
||||
return fetch(`https://sms-verification.parity.io:${port}/?${query}`, {
|
||||
mode: 'cors',
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.ok;
|
||||
})
|
||||
.catch(() => {
|
||||
return false; // todo: check for 404
|
||||
});
|
||||
};
|
||||
|
||||
export const postToServer = (query, isTestnet = false) => {
|
||||
const port = isTestnet ? 8443 : 443;
|
||||
|
||||
query = stringify(query);
|
||||
|
||||
return fetch(`https://sms-verification.parity.io:${port}/?${query}`, {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
cache: 'no-store'
|
||||
})
|
||||
.then((res) => {
|
||||
return res.json().then((data) => {
|
||||
if (res.ok) {
|
||||
return data.message;
|
||||
}
|
||||
throw new Error(data.message || 'unknown error');
|
||||
});
|
||||
});
|
||||
};
|
@ -1,27 +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/>.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default (
|
||||
<ul>
|
||||
<li>This privacy notice relates to your use of the Parity SMS verification service. We take your privacy seriously and deal in an honest, direct and transparent way when it comes to your data.</li>
|
||||
<li>We collect your phone number when you use this service. This is temporarily kept in memory, and then encrypted and stored in our EU servers. We only retain the cryptographic hash of the number to prevent duplicated accounts. You consent to this use.</li>
|
||||
<li>You pay a fee for the cost of this service using the account you want to verify.</li>
|
||||
<li>Your phone number is transmitted to a third party US SMS verification service Twilio for the sole purpose of the SMS verification. You consent to this use. Twilio’s privacy policy is here: <a href={ 'https://www.twilio.com/legal/privacy/developer' }>https://www.twilio.com/legal/privacy/developer</a>.</li>
|
||||
<li><i>Parity Technology Limited</i> is registered in England and Wales under company number <code>09760015</code> and complies with the Data Protection Act 1998 (UK). You may contact us via email at <a href={ 'mailto:admin@parity.io' }>admin@parity.io</a>. Our general privacy policy can be found here: <a href={ 'https://parity.io/legal.html' }>https://parity.io/legal.html</a>.</li>
|
||||
</ul>
|
||||
);
|
@ -1,263 +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/>.
|
||||
|
||||
import { observable, autorun, action } from 'mobx';
|
||||
|
||||
import Contract from '@parity/api/contract';
|
||||
import { sha3 } from '@parity/api/util/sha3';
|
||||
import Contracts from '@parity/shared/contracts';
|
||||
import { checkIfVerified, findLastRequested, awaitPuzzle } from '@parity/shared/contracts/verification';
|
||||
import { checkIfTxFailed, waitForConfirmations } from '@parity/shared/util/tx';
|
||||
|
||||
export const LOADING = 'fetching-contract';
|
||||
export const QUERY_DATA = 'query-data';
|
||||
export const POSTING_REQUEST = 'posting-request';
|
||||
export const POSTED_REQUEST = 'posted-request';
|
||||
export const REQUESTING_CODE = 'requesting-code';
|
||||
export const QUERY_CODE = 'query-code';
|
||||
export const POSTING_CONFIRMATION = 'posting-confirmation';
|
||||
export const POSTED_CONFIRMATION = 'posted-confirmation';
|
||||
export const DONE = 'done';
|
||||
|
||||
export default class VerificationStore {
|
||||
@observable step = null;
|
||||
@observable error = null;
|
||||
|
||||
@observable contract = null;
|
||||
@observable fee = null;
|
||||
@observable accountIsVerified = null;
|
||||
@observable accountHasRequested = null;
|
||||
@observable isAbleToRequest = null;
|
||||
@observable lastRequestValues = null;
|
||||
@observable isServerRunning = null;
|
||||
@observable consentGiven = false;
|
||||
@observable requestTx = null;
|
||||
@observable code = '';
|
||||
@observable isCodeValid = false;
|
||||
@observable confirmationTx = null;
|
||||
|
||||
constructor (api, abi, certifierName, account, isTestnet) {
|
||||
this._api = api;
|
||||
this.account = account;
|
||||
this.isTestnet = isTestnet;
|
||||
|
||||
this.step = LOADING;
|
||||
Contracts.get(this._api).badgeReg.fetchCertifierByName(certifierName)
|
||||
.then(({ address }) => {
|
||||
this.contract = new Contract(api, abi).at(address);
|
||||
this.load();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('error', err);
|
||||
this.error = 'Failed to fetch the contract: ' + err.message;
|
||||
});
|
||||
|
||||
autorun(() => {
|
||||
if (this.error) {
|
||||
console.error('verification: ' + this.error);
|
||||
}
|
||||
});
|
||||
|
||||
autorun(() => {
|
||||
if (this.step !== QUERY_DATA) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setIfAbleToRequest();
|
||||
});
|
||||
}
|
||||
|
||||
@action load = () => {
|
||||
const { contract, account } = this;
|
||||
|
||||
this.step = LOADING;
|
||||
|
||||
const isServerRunning = this.isServerRunning()
|
||||
.then((isRunning) => {
|
||||
this.isServerRunning = isRunning;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to check if server is running: ' + err.message;
|
||||
});
|
||||
|
||||
const fee = contract.instance.fee.call()
|
||||
.then((fee) => {
|
||||
this.fee = fee;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to fetch the fee: ' + err.message;
|
||||
});
|
||||
|
||||
const accountIsVerified = checkIfVerified(contract, account)
|
||||
.then((accountIsVerified) => {
|
||||
this.accountIsVerified = accountIsVerified;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to check if verified: ' + err.message;
|
||||
});
|
||||
|
||||
const accountHasRequested = findLastRequested(contract, account)
|
||||
.then((log) => {
|
||||
this.accountHasRequested = !!log;
|
||||
if (log) {
|
||||
this.lastRequestValues = log.params;
|
||||
this.requestTx = log.transactionHash;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to check if requested: ' + err.message;
|
||||
});
|
||||
|
||||
Promise
|
||||
.all([ isServerRunning, fee, accountIsVerified, accountHasRequested ])
|
||||
.then(() => {
|
||||
this.step = QUERY_DATA;
|
||||
});
|
||||
}
|
||||
|
||||
@action setConsentGiven = (consentGiven) => {
|
||||
this.consentGiven = consentGiven;
|
||||
}
|
||||
|
||||
@action setCode = (code) => {
|
||||
const { contract, account } = this;
|
||||
|
||||
if (!contract || !account || code.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
|
||||
const options = { from: account };
|
||||
const values = [ sha3.text(code) ];
|
||||
|
||||
this.code = code;
|
||||
this.isCodeValid = false;
|
||||
confirm.estimateGas(options, values)
|
||||
.then((gas) => {
|
||||
options.gas = gas.mul(1.2).toFixed(0);
|
||||
return confirm.call(options, values);
|
||||
})
|
||||
.then((result) => {
|
||||
this.isCodeValid = result === true;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to check if the code is valid: ' + err.message;
|
||||
});
|
||||
}
|
||||
|
||||
requestValues = () => []
|
||||
|
||||
@action sendRequest = () => {
|
||||
const { api, account, contract, fee } = this;
|
||||
|
||||
const request = contract.functions.find((fn) => fn.name === 'request');
|
||||
const options = { from: account, value: fee.toString() };
|
||||
const values = this.requestValues();
|
||||
|
||||
this.shallSkipRequest(values)
|
||||
.then((skipRequest) => {
|
||||
if (skipRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.step = POSTING_REQUEST;
|
||||
return request.estimateGas(options, values)
|
||||
.then((gas) => {
|
||||
options.gas = gas.mul(1.2).toFixed(0);
|
||||
return request.postTransaction(options, values);
|
||||
})
|
||||
.then((handle) => {
|
||||
// The "request rejected" error doesn't have any property to distinguish
|
||||
// it from other errors, so we can't give a meaningful error here.
|
||||
return api.pollMethod('parity_checkRequest', handle);
|
||||
})
|
||||
.then((txHash) => {
|
||||
this.requestTx = txHash;
|
||||
return checkIfTxFailed(api, txHash, options.gas)
|
||||
.then((hasFailed) => {
|
||||
if (hasFailed) {
|
||||
throw new Error('Transaction failed, all gas used up.');
|
||||
}
|
||||
this.step = POSTED_REQUEST;
|
||||
return waitForConfirmations(api, txHash, 1);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(() => this.checkIfReceivedCode())
|
||||
.then((hasReceived) => {
|
||||
if (hasReceived) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.step = REQUESTING_CODE;
|
||||
return this
|
||||
.requestCode()
|
||||
.then(() => awaitPuzzle(api, contract, account));
|
||||
})
|
||||
.then(() => {
|
||||
this.step = QUERY_CODE;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to request a confirmation code: ' + err.message;
|
||||
});
|
||||
}
|
||||
|
||||
@action queryCode = () => {
|
||||
this.step = QUERY_CODE;
|
||||
}
|
||||
|
||||
@action sendConfirmation = () => {
|
||||
const { api, account, contract, code } = this;
|
||||
const token = sha3.text(code);
|
||||
|
||||
const confirm = contract.functions.find((fn) => fn.name === 'confirm');
|
||||
const options = { from: account };
|
||||
const values = [ token ];
|
||||
|
||||
this.step = POSTING_CONFIRMATION;
|
||||
confirm.estimateGas(options, values)
|
||||
.then((gas) => {
|
||||
options.gas = gas.mul(1.2).toFixed(0);
|
||||
return confirm.postTransaction(options, values);
|
||||
})
|
||||
.then((handle) => {
|
||||
// TODO: The "request rejected" error doesn't have any property to
|
||||
// distinguish it from other errors, so we can't give a meaningful error here.
|
||||
return api.pollMethod('parity_checkRequest', handle);
|
||||
})
|
||||
.then((txHash) => {
|
||||
this.confirmationTx = txHash;
|
||||
return checkIfTxFailed(api, txHash, options.gas)
|
||||
.then((hasFailed) => {
|
||||
if (hasFailed) {
|
||||
throw new Error('Transaction failed, all gas used up.');
|
||||
}
|
||||
this.step = POSTED_CONFIRMATION;
|
||||
return waitForConfirmations(api, txHash, 1);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
this.step = DONE;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error = 'Failed to send the verification code: ' + err.message;
|
||||
});
|
||||
}
|
||||
|
||||
@action done = () => {
|
||||
this.step = DONE;
|
||||
}
|
||||
}
|
@ -1,25 +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/>.
|
||||
*/
|
||||
|
||||
.noSpacing {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.list li {
|
||||
padding: .1em 0;
|
||||
}
|
@ -1,449 +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/>.
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { observer } from 'mobx-react';
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import { Button, IdentityIcon, Portal, RadioButtons } from '@parity/ui';
|
||||
import { CancelIcon, DoneIcon } from '@parity/ui/Icons';
|
||||
|
||||
import SMSVerificationStore from './sms-store';
|
||||
import EmailVerificationStore from './email-store';
|
||||
|
||||
import styles from './verification.css';
|
||||
|
||||
const METHODS = {
|
||||
sms: {
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='verification.types.sms.label'
|
||||
defaultMessage='SMS Verification'
|
||||
/>
|
||||
),
|
||||
key: 'sms',
|
||||
value: 'sms',
|
||||
description: (
|
||||
<p className={ styles.noSpacing }>
|
||||
<FormattedMessage
|
||||
id='verification.types.sms.description'
|
||||
defaultMessage='It will be stored on the blockchain that you control a phone number (not <em>which</em>).'
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
},
|
||||
email: {
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='verification.types.email.label'
|
||||
defaultMessage='E-mail Verification'
|
||||
/>
|
||||
),
|
||||
key: 'email',
|
||||
value: 'email',
|
||||
description: (
|
||||
<p className={ styles.noSpacing }>
|
||||
<FormattedMessage
|
||||
id='verification.types.email.description'
|
||||
defaultMessage='The hash of the e-mail address you prove control over will be stored on the blockchain.'
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
const STEPS = [
|
||||
<FormattedMessage
|
||||
id='verification.steps.method'
|
||||
defaultMessage='Method'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='verification.steps.data'
|
||||
defaultMessage='Enter Data'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='verification.steps.request'
|
||||
defaultMessage='Request'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='verification.steps.code'
|
||||
defaultMessage='Enter Code'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='verification.steps.confirm'
|
||||
defaultMessage='Confirm'
|
||||
/>,
|
||||
<FormattedMessage
|
||||
id='verification.steps.completed'
|
||||
defaultMessage='Completed'
|
||||
/>
|
||||
];
|
||||
|
||||
import {
|
||||
LOADING,
|
||||
QUERY_DATA,
|
||||
POSTING_REQUEST, POSTED_REQUEST,
|
||||
REQUESTING_CODE, QUERY_CODE,
|
||||
POSTING_CONFIRMATION, POSTED_CONFIRMATION,
|
||||
DONE
|
||||
} from './store';
|
||||
|
||||
import GatherData from './GatherData';
|
||||
import SendRequest from './SendRequest';
|
||||
import QueryCode from './QueryCode';
|
||||
import SendConfirmation from './SendConfirmation';
|
||||
import Done from './Done';
|
||||
|
||||
@observer
|
||||
class Verification extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
account: PropTypes.string.isRequired,
|
||||
isTest: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
static phases = { // mapping (store steps -> steps)
|
||||
[LOADING]: 1, [QUERY_DATA]: 1,
|
||||
[POSTING_REQUEST]: 2, [POSTED_REQUEST]: 2, [REQUESTING_CODE]: 2,
|
||||
[QUERY_CODE]: 3,
|
||||
[POSTING_CONFIRMATION]: 4, [POSTED_CONFIRMATION]: 4,
|
||||
[DONE]: 5
|
||||
}
|
||||
|
||||
state = {
|
||||
method: 'sms'
|
||||
};
|
||||
|
||||
@observable store = null;
|
||||
|
||||
render () {
|
||||
const { onClose } = this.props;
|
||||
const store = this.store;
|
||||
let phase = 0;
|
||||
let error = false;
|
||||
let isStepValid = true;
|
||||
|
||||
if (store) {
|
||||
phase = Verification.phases[store.step];
|
||||
error = store.error;
|
||||
isStepValid = store.isStepValid;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal
|
||||
activeStep={ phase }
|
||||
busySteps={
|
||||
error
|
||||
? []
|
||||
: [ 2, 4 ]
|
||||
}
|
||||
buttons={ this.renderDialogActions(phase, error, isStepValid) }
|
||||
onClose={ onClose }
|
||||
open
|
||||
steps={ STEPS }
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='verification.title'
|
||||
defaultMessage='verify your account'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ this.renderStep(phase, error) }
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderDialogActions (phase, error, isStepValid) {
|
||||
const { account, onClose } = this.props;
|
||||
const store = this.store;
|
||||
|
||||
const cancelButton = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancel'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='verification.button.cancel'
|
||||
defaultMessage='Cancel'
|
||||
/>
|
||||
}
|
||||
onClick={ onClose }
|
||||
/>
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return cancelButton;
|
||||
}
|
||||
|
||||
if (phase === 5) {
|
||||
return [
|
||||
cancelButton,
|
||||
<Button
|
||||
disabled={ !isStepValid }
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='verification.button.done'
|
||||
defaultMessage='Done'
|
||||
/>
|
||||
}
|
||||
onClick={ onClose }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
let action = () => {};
|
||||
|
||||
switch (phase) {
|
||||
case 0:
|
||||
action = () => {
|
||||
const { method } = this.state;
|
||||
|
||||
this.onSelectMethod(method);
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
action = store.sendRequest;
|
||||
break;
|
||||
case 2:
|
||||
action = store.queryCode;
|
||||
break;
|
||||
case 3:
|
||||
action = store.sendConfirmation;
|
||||
break;
|
||||
case 4:
|
||||
action = store.done;
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
cancelButton,
|
||||
<Button
|
||||
disabled={ !isStepValid }
|
||||
icon={
|
||||
<IdentityIcon
|
||||
address={ account }
|
||||
button
|
||||
/>
|
||||
}
|
||||
key='next'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='verification.button.next'
|
||||
defaultMessage='Next'
|
||||
/>
|
||||
}
|
||||
onClick={ action }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
renderStep (phase, error) {
|
||||
if (error) {
|
||||
return (
|
||||
<p>{ error }</p>
|
||||
);
|
||||
}
|
||||
|
||||
const { method } = this.state;
|
||||
|
||||
if (phase === 0) {
|
||||
return (
|
||||
<RadioButtons
|
||||
name='verificationType'
|
||||
onChange={ this.selectMethod }
|
||||
value={ method || 'sms' }
|
||||
values={ Object.values(METHODS) }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
step,
|
||||
isServerRunning, isAbleToRequest, fee, accountIsVerified, accountHasRequested,
|
||||
requestTx, isCodeValid, confirmationTx,
|
||||
setCode
|
||||
} = this.store;
|
||||
|
||||
switch (phase) {
|
||||
case 1:
|
||||
if (step === LOADING) {
|
||||
return (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='verification.loading'
|
||||
defaultMessage='Loading verification data.'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const { setConsentGiven } = this.store;
|
||||
const fields = [];
|
||||
|
||||
if (method === 'sms') {
|
||||
fields.push({
|
||||
key: 'number',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='verification.gatherData.phoneNumber.label'
|
||||
defaultMessage='phone number in international format'
|
||||
/>
|
||||
),
|
||||
hint: (
|
||||
<FormattedMessage
|
||||
id='verification.gatherData.phoneNumber.hint'
|
||||
defaultMessage='the SMS will be sent to this number'
|
||||
/>
|
||||
),
|
||||
error: this.store.isNumberValid
|
||||
? null
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='verification.gatherDate.phoneNumber.error'
|
||||
defaultMessage='invalid number'
|
||||
/>
|
||||
),
|
||||
onChange: this.store.setNumber
|
||||
});
|
||||
} else if (method === 'email') {
|
||||
fields.push({
|
||||
key: 'email',
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='verification.gatherData.email.label'
|
||||
defaultMessage='e-mail address'
|
||||
/>
|
||||
),
|
||||
hint: (
|
||||
<FormattedMessage
|
||||
id='verification.gatherData.email.hint'
|
||||
defaultMessage='the code will be sent to this address'
|
||||
/>
|
||||
),
|
||||
error: this.store.isEmailValid
|
||||
? null
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='verification.gatherDate.email.error'
|
||||
defaultMessage='invalid e-mail'
|
||||
/>
|
||||
),
|
||||
onChange: this.store.setEmail
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<GatherData
|
||||
fee={ fee }
|
||||
accountHasRequested={ accountHasRequested }
|
||||
isServerRunning={ isServerRunning }
|
||||
isAbleToRequest={ isAbleToRequest }
|
||||
accountIsVerified={ accountIsVerified }
|
||||
method={ method }
|
||||
fields={ fields }
|
||||
setConsentGiven={ setConsentGiven }
|
||||
/>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<SendRequest
|
||||
step={ step }
|
||||
tx={ requestTx }
|
||||
/>
|
||||
);
|
||||
|
||||
case 3:
|
||||
let receiver;
|
||||
let hint;
|
||||
|
||||
if (method === 'sms') {
|
||||
receiver = this.store.number;
|
||||
hint = (
|
||||
<FormattedMessage
|
||||
id='verification.sms.enterCode'
|
||||
defaultMessage='Enter the code you received via SMS.'
|
||||
/>
|
||||
);
|
||||
} else if (method === 'email') {
|
||||
receiver = this.store.email;
|
||||
hint = (
|
||||
<FormattedMessage
|
||||
id='verification.email.enterCode'
|
||||
defaultMessage='Enter the code you received via e-mail.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<QueryCode
|
||||
hint={ hint }
|
||||
isCodeValid={ isCodeValid }
|
||||
receiver={ receiver }
|
||||
setCode={ setCode }
|
||||
/>
|
||||
);
|
||||
|
||||
case 4:
|
||||
return (
|
||||
<SendConfirmation
|
||||
step={ step }
|
||||
tx={ confirmationTx }
|
||||
/>
|
||||
);
|
||||
|
||||
case 5:
|
||||
return (
|
||||
<Done />
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
onSelectMethod = (name) => {
|
||||
const { api } = this.context;
|
||||
const { account, isTest } = this.props;
|
||||
|
||||
if (name === 'sms') {
|
||||
this.store = new SMSVerificationStore(api, account, isTest);
|
||||
} else if (name === 'email') {
|
||||
this.store = new EmailVerificationStore(api, account, isTest);
|
||||
}
|
||||
}
|
||||
|
||||
selectMethod = (event, method) => {
|
||||
this.setState({ method });
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isTest: state.nodeStatus.isTest
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
null
|
||||
)(Verification);
|
@ -1,21 +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/>.
|
||||
*/
|
||||
|
||||
.textbox {
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
@ -1,552 +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/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import HardwareStore from '@parity/shared/mobx/hardwareStore';
|
||||
import HistoryStore from '@parity/shared/mobx/historyStore';
|
||||
import { newError } from '@parity/shared/redux/actions';
|
||||
import { setVisibleAccounts } from '@parity/shared/redux/providers/personalActions';
|
||||
import { fetchCertifiers, fetchCertifications } from '@parity/shared/redux/providers/certifications/actions';
|
||||
import { Actionbar, Button, ConfirmDialog, Input, Page, Portal } from '@parity/ui';
|
||||
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon, FileDownloadIcon } from '@parity/ui/Icons';
|
||||
|
||||
import shapeshiftBtn from '@parity/shared/assets/images/shapeshift-btn.png';
|
||||
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import EditMeta from './EditMeta';
|
||||
import DeleteAddress from '@parity/dapp-address/Delete';
|
||||
import ExportStore from '@parity/dapp-accounts/ExportAccount/exportStore';
|
||||
import Faucet from './Faucet';
|
||||
import PasswordManager from './PasswordManager';
|
||||
import Shapeshift from './Shapeshift';
|
||||
import Transfer from './Transfer';
|
||||
import Verification from './Verification';
|
||||
|
||||
import { AccountCard } from 'parity-reactive-ui';
|
||||
|
||||
import Store from './store';
|
||||
import Transactions from './Transactions';
|
||||
import styles from './account.css';
|
||||
|
||||
const accountsHistory = HistoryStore.get('accounts');
|
||||
|
||||
@observer
|
||||
class Account extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
fetchCertifiers: PropTypes.func.isRequired,
|
||||
fetchCertifications: PropTypes.func.isRequired,
|
||||
setVisibleAccounts: PropTypes.func.isRequired,
|
||||
|
||||
account: PropTypes.object,
|
||||
certifications: PropTypes.object,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
newError: PropTypes.func,
|
||||
params: PropTypes.object
|
||||
}
|
||||
|
||||
store = new Store();
|
||||
hwstore = HardwareStore.get(this.context.api);
|
||||
|
||||
componentWillMount () {
|
||||
const { accounts, newError, params } = this.props;
|
||||
const { address } = params;
|
||||
|
||||
this.exportStore = new ExportStore(this.context.api, accounts, newError, address);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { params } = this.props;
|
||||
|
||||
if (params.address) {
|
||||
accountsHistory.add(params.address, 'wallet');
|
||||
}
|
||||
|
||||
this.props.fetchCertifiers();
|
||||
this.setVisibleAccounts();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const prevAddress = this.props.params.address;
|
||||
const nextAddress = nextProps.params.address;
|
||||
const { accounts } = nextProps;
|
||||
|
||||
if (prevAddress !== nextAddress) {
|
||||
this.setVisibleAccounts(nextProps);
|
||||
}
|
||||
|
||||
if (!Object.keys(this.exportStore.accounts).length) {
|
||||
this.exportStore.setAccounts(accounts);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.props.setVisibleAccounts([]);
|
||||
}
|
||||
|
||||
setVisibleAccounts (props = this.props) {
|
||||
const { params, setVisibleAccounts, fetchCertifications } = props;
|
||||
const addresses = [params.address];
|
||||
|
||||
setVisibleAccounts(addresses);
|
||||
fetchCertifications(params.address);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { address } = this.props.params;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isAvailable = !account.hardware || this.hwstore.isConnected(address);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ this.renderDeleteDialog(account) }
|
||||
{ this.renderEditDialog(account) }
|
||||
{ this.renderExportDialog() }
|
||||
{ this.renderFaucetDialog() }
|
||||
{ this.renderFundDialog() }
|
||||
{ this.renderPasswordDialog(account) }
|
||||
{ this.renderTransferDialog(account) }
|
||||
{ this.renderVerificationDialog() }
|
||||
{ this.renderActionbar(account) }
|
||||
<Page padded>
|
||||
<AccountCard account={ account } disabled={ !isAvailable } />
|
||||
<Transactions
|
||||
address={ address }
|
||||
/>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
isKovan = (netVersion) => {
|
||||
return netVersion === '42';
|
||||
}
|
||||
|
||||
isMainnet = (netVersion) => {
|
||||
return netVersion === '1';
|
||||
}
|
||||
|
||||
isFaucettable = (netVersion, certifications, address) => {
|
||||
return this.isKovan(netVersion) || (
|
||||
this.isMainnet(netVersion) &&
|
||||
this.isSmsCertified(certifications, address)
|
||||
);
|
||||
}
|
||||
|
||||
isSmsCertified = (_certifications, address) => {
|
||||
const certifications = _certifications && _certifications[address]
|
||||
? _certifications[address].filter((cert) => cert.name.indexOf('smsverification') === 0)
|
||||
: [];
|
||||
|
||||
return certifications.length !== 0;
|
||||
}
|
||||
|
||||
renderActionbar (account) {
|
||||
const { certifications, netVersion } = this.props;
|
||||
const { address } = this.props.params;
|
||||
const isVerifiable = this.isMainnet(netVersion);
|
||||
const isFaucettable = this.isFaucettable(netVersion, certifications, address);
|
||||
|
||||
const buttons = [
|
||||
<Button
|
||||
icon={ <SendIcon /> }
|
||||
key='transferFunds'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.transfer'
|
||||
defaultMessage='transfer'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.toggleTransferDialog }
|
||||
/>,
|
||||
<Button
|
||||
icon={
|
||||
<img
|
||||
className='icon'
|
||||
src={ shapeshiftBtn }
|
||||
/>
|
||||
}
|
||||
key='shapeshift'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.shapeshift'
|
||||
defaultMessage='shapeshift'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.toggleFundDialog }
|
||||
/>,
|
||||
isVerifiable
|
||||
? (
|
||||
<Button
|
||||
icon={ <VerifyIcon /> }
|
||||
key='verification'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.verify'
|
||||
defaultMessage='verify'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.toggleVerificationDialog }
|
||||
/>
|
||||
)
|
||||
: null,
|
||||
isFaucettable
|
||||
? (
|
||||
<Button
|
||||
icon={ <DialIcon /> }
|
||||
key='faucet'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.faucet'
|
||||
defaultMessage='Kovan ETH'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.toggleFaucetDialog }
|
||||
/>
|
||||
)
|
||||
: null,
|
||||
<Button
|
||||
icon={ <EditIcon /> }
|
||||
key='editmeta'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.edit'
|
||||
defaultMessage='edit'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.toggleEditDialog }
|
||||
/>,
|
||||
<Button
|
||||
icon={ <FileDownloadIcon /> }
|
||||
key='exportmeta'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.export'
|
||||
defaultMessage='export'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.toggleExportDialog }
|
||||
/>,
|
||||
!(account.external || account.hardware) && (
|
||||
<Button
|
||||
icon={ <LockedIcon /> }
|
||||
key='passwordManager'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='account.button.password'
|
||||
defaultMessage='password'
|
||||
/>
|
||||
}
|
||||
onClick={ this.store.togglePasswordDialog }
|
||||
/>
|
||||
),
|
||||
<Button
|
||||
icon={ <DeleteIcon /> }
|
||||
key='delete'
|
||||
label={
|
||||
account.external || account.hardware
|
||||
? (
|
||||
<FormattedMessage
|
||||
id='account.button.forget'
|
||||
defaultMessage='forget'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<FormattedMessage
|
||||
id='account.button.delete'
|
||||
defaultMessage='delete'
|
||||
/>
|
||||
)
|
||||
}
|
||||
onClick={ this.store.toggleDeleteDialog }
|
||||
/>
|
||||
];
|
||||
|
||||
return (
|
||||
<Actionbar
|
||||
buttons={ buttons }
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='account.title'
|
||||
defaultMessage='Account Management'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDeleteDialog (account) {
|
||||
if (!this.store.isDeleteVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (account.hardware) {
|
||||
return (
|
||||
<DeleteAddress
|
||||
account={ account }
|
||||
confirmMessage={
|
||||
<FormattedMessage
|
||||
id='account.hardware.confirmDelete'
|
||||
defaultMessage='Are you sure you want to remove the following hardware address from your account list?'
|
||||
/>
|
||||
}
|
||||
visible
|
||||
route='/accounts'
|
||||
onClose={ this.store.toggleDeleteDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (account.external) {
|
||||
return (
|
||||
<DeleteAddress
|
||||
account={ account }
|
||||
confirmMessage={
|
||||
<FormattedMessage
|
||||
id='account.external.confirmDelete'
|
||||
defaultMessage='Are you sure you want to remove the following external address from your account list?'
|
||||
/>
|
||||
}
|
||||
visible
|
||||
route='/accounts'
|
||||
onClose={ this.store.toggleDeleteDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DeleteAccount
|
||||
account={ account }
|
||||
onClose={ this.store.toggleDeleteDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditDialog (account) {
|
||||
if (!this.store.isEditVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EditMeta
|
||||
account={ account }
|
||||
onClose={ this.store.toggleEditDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderExportDialog () {
|
||||
const { changePassword, accountValue } = this.exportStore;
|
||||
|
||||
if (!this.store.isExportVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal
|
||||
open
|
||||
isSmallModal
|
||||
onClose={ this.exportClose }
|
||||
>
|
||||
<ConfirmDialog
|
||||
open
|
||||
disabledConfirm={ false }
|
||||
labelConfirm='Export'
|
||||
labelDeny='Cancel'
|
||||
onConfirm={ this.onExport }
|
||||
onDeny={ this.exportClose }
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='export.account.title'
|
||||
defaultMessage='Export Account'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={ styles.textbox }>
|
||||
<FormattedMessage
|
||||
id='export.account.info'
|
||||
defaultMessage='Export your account as a JSON file. Please enter the password linked with this account.'
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className={ styles.textbox }
|
||||
onKeyDown={ this.onEnter }
|
||||
autoFocus
|
||||
type='password'
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='export.account.password.hint'
|
||||
defaultMessage='The password specified when creating this account'
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='export.account.password.label'
|
||||
defaultMessage='Account password'
|
||||
/>
|
||||
}
|
||||
onChange={ changePassword }
|
||||
value={ accountValue }
|
||||
/>
|
||||
</ConfirmDialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderFaucetDialog () {
|
||||
const { netVersion } = this.props;
|
||||
|
||||
if (!this.store.isFaucetVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address } = this.props.params;
|
||||
|
||||
return (
|
||||
<Faucet
|
||||
address={ address }
|
||||
netVersion={ netVersion }
|
||||
onClose={ this.store.toggleFaucetDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderFundDialog () {
|
||||
if (!this.store.isFundVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address } = this.props.params;
|
||||
|
||||
return (
|
||||
<Shapeshift
|
||||
address={ address }
|
||||
onClose={ this.store.toggleFundDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPasswordDialog (account) {
|
||||
if (!this.store.isPasswordVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PasswordManager
|
||||
account={ account }
|
||||
onClose={ this.store.togglePasswordDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderTransferDialog (account) {
|
||||
if (!this.store.isTransferVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Transfer
|
||||
account={ account }
|
||||
onClose={ this.store.toggleTransferDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderVerificationDialog () {
|
||||
if (!this.store.isVerificationVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address } = this.props.params;
|
||||
|
||||
return (
|
||||
<Verification
|
||||
account={ address }
|
||||
onClose={ this.store.toggleVerificationDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onEnter = (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
this.onExport();
|
||||
}
|
||||
}
|
||||
|
||||
exportClose = () => {
|
||||
const { toggleExportDialog } = this.store;
|
||||
const { resetAccountValue } = this.exportStore;
|
||||
|
||||
resetAccountValue();
|
||||
toggleExportDialog();
|
||||
}
|
||||
|
||||
onExport = () => {
|
||||
const { onExport } = this.exportStore;
|
||||
|
||||
onExport(this.hideExport);
|
||||
}
|
||||
|
||||
hideExport = () => {
|
||||
this.store.toggleExportDialog();
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state, props) {
|
||||
const { address } = props.params;
|
||||
|
||||
const { accounts } = state.personal;
|
||||
const certifications = state.certifications;
|
||||
const { netVersion } = state.nodeStatus;
|
||||
|
||||
const account = (accounts || {})[address];
|
||||
|
||||
return {
|
||||
account,
|
||||
accounts,
|
||||
certifications,
|
||||
netVersion
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({
|
||||
fetchCertifiers,
|
||||
fetchCertifications,
|
||||
newError,
|
||||
setVisibleAccounts
|
||||
}, dispatch);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Account);
|
@ -1,191 +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/>.
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { ACCOUNTS, ADDRESS, createRedux } from './account.test.js';
|
||||
|
||||
import Account from './account';
|
||||
|
||||
let component;
|
||||
let instance;
|
||||
let store;
|
||||
|
||||
function render (props) {
|
||||
component = shallow(
|
||||
<Account
|
||||
accounts={ ACCOUNTS }
|
||||
params={ { address: ADDRESS } }
|
||||
{ ...props }
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
store: createRedux()
|
||||
}
|
||||
}
|
||||
).find('Account').shallow();
|
||||
instance = component.instance();
|
||||
store = instance.store;
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('views/Account', () => {
|
||||
describe('rendering', () => {
|
||||
beforeEach(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
it('renders defaults', () => {
|
||||
expect(component).to.be.ok;
|
||||
});
|
||||
|
||||
describe('sections', () => {
|
||||
it('renders the Actionbar', () => {
|
||||
expect(component.find('Actionbar')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders the Page', () => {
|
||||
expect(component.find('Page')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders the Header', () => {
|
||||
expect(component.find('Header')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders the Transactions', () => {
|
||||
expect(component.find('Connect(Transactions)')).to.have.length(1);
|
||||
});
|
||||
|
||||
it('renders no other sections', () => {
|
||||
expect(component.find('div').children()).to.have.length(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sub-renderers', () => {
|
||||
describe('renderActionBar', () => {
|
||||
let bar;
|
||||
|
||||
beforeEach(() => {
|
||||
render();
|
||||
|
||||
bar = instance.renderActionbar({ tokens: {} });
|
||||
});
|
||||
|
||||
it('renders the bar', () => {
|
||||
expect(bar.type).to.match(/Actionbar/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderDeleteDialog', () => {
|
||||
it('renders null when not visible', () => {
|
||||
render();
|
||||
|
||||
expect(store.isDeleteVisible).to.be.false;
|
||||
expect(instance.renderDeleteDialog(ACCOUNTS[ADDRESS])).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the modal when visible', () => {
|
||||
render();
|
||||
|
||||
store.toggleDeleteDialog();
|
||||
expect(instance.renderDeleteDialog(ACCOUNTS[ADDRESS]).type).to.match(/Connect/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderEditDialog', () => {
|
||||
it('renders null when not visible', () => {
|
||||
render();
|
||||
|
||||
expect(store.isEditVisible).to.be.false;
|
||||
expect(instance.renderEditDialog(ACCOUNTS[ADDRESS])).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the modal when visible', () => {
|
||||
render();
|
||||
|
||||
store.toggleEditDialog();
|
||||
expect(instance.renderEditDialog(ACCOUNTS[ADDRESS]).type).to.match(/Connect/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderFundDialog', () => {
|
||||
it('renders null when not visible', () => {
|
||||
render();
|
||||
|
||||
expect(store.isFundVisible).to.be.false;
|
||||
expect(instance.renderFundDialog()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the modal when visible', () => {
|
||||
render();
|
||||
|
||||
store.toggleFundDialog();
|
||||
expect(instance.renderFundDialog().type).to.match(/Shapeshift/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderPasswordDialog', () => {
|
||||
it('renders null when not visible', () => {
|
||||
render();
|
||||
|
||||
expect(store.isPasswordVisible).to.be.false;
|
||||
expect(instance.renderPasswordDialog()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the modal when visible', () => {
|
||||
render();
|
||||
|
||||
store.togglePasswordDialog();
|
||||
expect(instance.renderPasswordDialog({ address: ADDRESS }).type).to.match(/Connect/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderTransferDialog', () => {
|
||||
it('renders null when not visible', () => {
|
||||
render();
|
||||
|
||||
expect(store.isTransferVisible).to.be.false;
|
||||
expect(instance.renderTransferDialog()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the modal when visible', () => {
|
||||
render();
|
||||
|
||||
store.toggleTransferDialog();
|
||||
expect(instance.renderTransferDialog().type).to.match(/Connect/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderVerificationDialog', () => {
|
||||
it('renders null when not visible', () => {
|
||||
render();
|
||||
|
||||
expect(store.isVerificationVisible).to.be.false;
|
||||
expect(instance.renderVerificationDialog()).to.be.null;
|
||||
});
|
||||
|
||||
it('renders the modal when visible', () => {
|
||||
render();
|
||||
|
||||
store.toggleVerificationDialog();
|
||||
expect(instance.renderVerificationDialog().type).to.match(/Connect/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,57 +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/>.
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
const ADDRESS = '0x0123456789012345678901234567890123456789';
|
||||
const ACCOUNTS = {
|
||||
[ADDRESS]: {
|
||||
address: ADDRESS
|
||||
}
|
||||
};
|
||||
|
||||
function createRedux () {
|
||||
return {
|
||||
dispatch: sinon.stub(),
|
||||
subscribe: sinon.stub(),
|
||||
getState: () => {
|
||||
return {
|
||||
balances: {
|
||||
balances: {
|
||||
[ADDRESS]: {}
|
||||
}
|
||||
},
|
||||
nodeStatus: {
|
||||
netVersion: '1',
|
||||
traceMode: false
|
||||
},
|
||||
personal: {
|
||||
accounts: {
|
||||
[ADDRESS]: {
|
||||
address: ADDRESS
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
ACCOUNTS,
|
||||
ADDRESS,
|
||||
createRedux
|
||||
};
|
@ -1,25 +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/>.
|
||||
|
||||
import Api from '@parity/api';
|
||||
|
||||
const ethereumProvider = window.ethereum || window.parent.ethereum;
|
||||
|
||||
if (!ethereumProvider) {
|
||||
throw new Error('Unable to locate EthereumProvider, object not attached');
|
||||
}
|
||||
|
||||
export default new Api(ethereumProvider);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user