Split all packages/* to external repos

This commit is contained in:
Jaco Greeff 2017-08-01 12:51:41 +02:00
parent b5f4c40406
commit c509733a30
821 changed files with 2 additions and 68251 deletions

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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;
});
});
});
});

View File

@ -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';

View File

@ -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);

View File

@ -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();
});
});
});
});
});

View File

@ -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
};

View File

@ -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';

View File

@ -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;
});
}
}

View File

@ -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']
}));
});
});
});
});

View File

@ -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 }&nbsp;</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();
}
}

View File

@ -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';

View File

@ -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);
});
});
}
}

View File

@ -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;
}

View File

@ -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 } />
);
}
}

View File

@ -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;
});
});
});

View File

@ -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';

View File

@ -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';

View File

@ -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);
}

View File

@ -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);

View File

@ -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();
});
});
});
});
});

View File

@ -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
};

View File

@ -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;
});
}
}

View File

@ -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;
});
});
});
});
});

View File

@ -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>
);
}
}

View File

@ -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}`);
});
});
});
});
});
});

View File

@ -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';

View File

@ -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>
);
}
}

View File

@ -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;
});
});

View File

@ -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';

View File

@ -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>
);
}
}

View File

@ -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;
});
});

View File

@ -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';

View File

@ -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>
);
}
}

View File

@ -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;
});
});

View File

@ -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';

View File

@ -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';

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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;
});
});
});
});

View File

@ -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';

View File

@ -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>
);
}
}

View File

@ -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;
});
});

View File

@ -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';

View File

@ -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;
}

View File

@ -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>
);
}
}

View File

@ -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;
});
});

View File

@ -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';

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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);
});
});
});

View File

@ -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
};

View File

@ -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');
});
});
});
});

View File

@ -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';

View File

@ -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
};
});
});
}
}

View File

@ -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
}
]);
});
});
});
});
});

View File

@ -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;
}

View File

@ -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);

View File

@ -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/);
});
});
});

View File

@ -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
};

View File

@ -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
});
}
}

View File

@ -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';

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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';

View File

@ -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;

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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>
);
}
}

View File

@ -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';

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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';

View File

@ -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';

View File

@ -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());
}
}

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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');
});
});
};

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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';

View File

@ -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);
}
}

View File

@ -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');
});
});
};

View File

@ -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. Twilios 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>
);

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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/);
});
});
});
});

View File

@ -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
};

View File

@ -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