aboutsummaryrefslogtreecommitdiffstats
path: root/ui/app
diff options
context:
space:
mode:
authorBruno <brunobar79@gmail.com>2018-06-10 15:52:32 +0800
committerBruno <brunobar79@gmail.com>2018-06-10 15:52:32 +0800
commit8c4d58aa4508e3d54c3f69847347e78d09c63b97 (patch)
treee9206c28499d7f4d66d7d407851c5efc05f615de /ui/app
parentc0d2dab28b4083ee3ef65b6b561e28c811c6773d (diff)
downloadtangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.tar
tangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.tar.gz
tangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.tar.bz2
tangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.tar.lz
tangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.tar.xz
tangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.tar.zst
tangerine-wallet-browser-8c4d58aa4508e3d54c3f69847347e78d09c63b97.zip
initial trezor support
Diffstat (limited to 'ui/app')
-rw-r--r--ui/app/actions.js42
-rw-r--r--ui/app/components/account-menu/index.js9
-rw-r--r--ui/app/components/pages/create-account/connect-hardware.js234
-rw-r--r--ui/app/components/pages/create-account/index.js25
-rw-r--r--ui/app/components/pages/create-account/new-account.js2
-rw-r--r--ui/app/css/itcss/components/new-account.scss105
-rw-r--r--ui/app/routes.js2
7 files changed, 413 insertions, 6 deletions
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 1edf692b6..758696203 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -79,6 +79,8 @@ var actions = {
addNewKeyring,
importNewAccount,
addNewAccount,
+ connectHardware,
+ unlockTrezorAccount,
NEW_ACCOUNT_SCREEN: 'NEW_ACCOUNT_SCREEN',
navigateToNewAccountScreen,
resetAccount,
@@ -596,6 +598,46 @@ function addNewAccount () {
}
}
+function connectHardware (deviceName, page) {
+ log.debug(`background.connectHardware`, deviceName, page)
+ return (dispatch, getState) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.connectHardware(deviceName, page, (err, accounts) => {
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.hideLoadingIndication())
+
+ forceUpdateMetamaskState(dispatch)
+ return resolve(accounts)
+ })
+ })
+ }
+}
+
+function unlockTrezorAccount (index) {
+ log.debug(`background.unlockTrezorAccount`, index)
+ return (dispatch, getState) => {
+ dispatch(actions.showLoadingIndication())
+ return new Promise((resolve, reject) => {
+ background.unlockTrezorAccount(index, (err, accounts) => {
+ if (err) {
+ log.error(err)
+ dispatch(actions.displayWarning(err.message))
+ return reject(err)
+ }
+
+ dispatch(actions.hideLoadingIndication())
+ return resolve()
+ })
+ })
+ }
+}
+
function showInfoPage () {
return {
type: actions.SHOW_INFO_PAGE,
diff --git a/ui/app/components/account-menu/index.js b/ui/app/components/account-menu/index.js
index f34631ca8..629669dac 100644
--- a/ui/app/components/account-menu/index.js
+++ b/ui/app/components/account-menu/index.js
@@ -14,6 +14,7 @@ const {
INFO_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
DEFAULT_ROUTE,
} = require('../../routes')
@@ -106,6 +107,14 @@ AccountMenu.prototype.render = function () {
icon: h('img.account-menu__item-icon', { src: 'images/import-account.svg' }),
text: this.context.t('importAccount'),
}),
+ h(Item, {
+ onClick: () => {
+ toggleAccountMenu()
+ history.push(CONNECT_HARDWARE_ROUTE)
+ },
+ icon: h('img.account-menu__item-icon', { src: 'images/connect-icon.svg' }),
+ text: this.context.t('connectHardware'),
+ }),
h(Divider),
h(Item, {
onClick: () => {
diff --git a/ui/app/components/pages/create-account/connect-hardware.js b/ui/app/components/pages/create-account/connect-hardware.js
new file mode 100644
index 000000000..37b7414b3
--- /dev/null
+++ b/ui/app/components/pages/create-account/connect-hardware.js
@@ -0,0 +1,234 @@
+const { Component } = require('react')
+const PropTypes = require('prop-types')
+const h = require('react-hyperscript')
+const connect = require('react-redux').connect
+const actions = require('../../../actions')
+const genAccountLink = require('../../../../lib/account-link.js')
+const log = require('loglevel')
+const { DEFAULT_ROUTE } = require('../../../routes')
+
+class ConnectHardwareForm extends Component {
+ constructor (props, context) {
+ super(props)
+ this.state = {
+ error: null,
+ response: null,
+ btnText: 'Connect to Trezor', // Test
+ selectedAccount: '',
+ accounts: [],
+ }
+ }
+
+ connectToTrezor () {
+ if (this.state.accounts.length) {
+ return null
+ }
+ this.setState({ btnText: 'Connecting...' })
+ this.getPage()
+ }
+
+ getPage (page = 1) {
+ this.props
+ .connectHardware('trezor', page)
+ .then(accounts => {
+ if (accounts.length) {
+ this.setState({ accounts: accounts })
+ }
+ })
+ .catch(e => {
+ this.setState({ btnText: 'Connect to Trezor' })
+ })
+ }
+
+ unlockAccount () {
+ if (this.state.selectedAccount === '') {
+ return Promise.reject({ error: 'You need to select an account!' })
+ }
+ log.debug('should unlock account ', this.state.selectedAccount)
+ return this.props.unlockTrezorAccount(this.state.selectedAccount)
+ }
+
+ handleRadioChange = e => {
+ log.debug('Selected account with index ', e.target.value)
+
+ this.setState({
+ selectedAccount: e.target.value,
+ error: null,
+ })
+ }
+
+ renderAccounts () {
+ if (!this.state.accounts.length) {
+ return null
+ }
+ log.debug('ACCOUNTS : ', this.state.accounts)
+ log.debug('SELECTED?', this.state.selectedAccount)
+
+ return h('div.hw-account-list', [
+ h('div.hw-account-list__title_wrapper', [
+ h('div.hw-account-list__title', {}, ['Select an Address']),
+ h('div.hw-account-list__device', {}, ['Trezor - ETH']),
+ ]),
+ this.state.accounts.map((a, i) => {
+ return h('div.hw-account-list__item', { key: a.address }, [
+ h('span.hw-account-list__item__index', a.index + 1),
+ h('div.hw-account-list__item__radio', [
+ h('input', {
+ type: 'radio',
+ name: 'selectedAccount',
+ id: `address-${i}`,
+ value: a.index,
+ onChange: this.handleRadioChange,
+ }),
+ h(
+ 'label.hw-account-list__item__label',
+ {
+ htmlFor: `address-${i}`,
+ },
+ `${a.address.slice(0, 4)}...${a.address.slice(-4)}`
+ ),
+ ]),
+ h('span.hw-account-list__item__balance', `${a.balance} ETH`),
+ h(
+ 'a.hw-account-list__item__link',
+ {
+ href: genAccountLink(a.address, this.props.network),
+ target: '_blank',
+ title: this.context.t('etherscanView'),
+ },
+ h('img', { src: 'images/popout.svg' })
+ ),
+ ])
+ }),
+ ])
+ }
+
+ renderPagination () {
+ if (!this.state.accounts.length) {
+ return null
+ }
+ return h('div.hw-list-pagination', [
+ h(
+ 'button.btn-primary.hw-list-pagination__button',
+ {
+ onClick: () => this.getPage(-1),
+ },
+ '< Prev'
+ ),
+
+ h(
+ 'button.btn-primary.hw-list-pagination__button',
+ {
+ onClick: () => this.getPage(),
+ },
+ 'Next >'
+ ),
+ ])
+ }
+
+ renderButtons () {
+ if (!this.state.accounts.length) {
+ return null
+ }
+ const { history } = this.props
+
+ return h('div.new-account-create-form__buttons', {}, [
+ h(
+ 'button.btn-default.btn--large.new-account-create-form__button',
+ {
+ onClick: () => history.push(DEFAULT_ROUTE),
+ },
+ [this.context.t('cancel')]
+ ),
+
+ h(
+ 'button.btn-primary.btn--large.new-account-create-form__button',
+ {
+ onClick: () => {
+ this.unlockAccount(this.state.selectedAccount)
+ .then(() => history.push(DEFAULT_ROUTE))
+ .catch(e => {
+ this.setState({ error: e.error })
+ })
+ },
+ },
+ [this.context.t('unlock')]
+ ),
+ ])
+ }
+
+ renderError () {
+ return this.state.error
+ ? h('span.error', { style: { marginBottom: 40 } }, this.state.error)
+ : null
+ }
+
+ renderConnectButton () {
+ return !this.state.accounts.length
+ ? h(
+ 'button.btn-primary.btn--large',
+ { onClick: () => this.connectToTrezor(), style: { margin: 12 } },
+ this.state.btnText
+ )
+ : null
+ }
+
+ render () {
+ return h('div.new-account-create-form', [
+ this.renderError(),
+ this.renderConnectButton(),
+ this.renderAccounts(),
+ this.renderPagination(),
+ this.renderButtons(),
+ ])
+ }
+}
+
+ConnectHardwareForm.propTypes = {
+ hideModal: PropTypes.func,
+ showImportPage: PropTypes.func,
+ showConnectPage: PropTypes.func,
+ connectHardware: PropTypes.func,
+ unlockTrezorAccount: PropTypes.func,
+ numberOfExistingAccounts: PropTypes.number,
+ history: PropTypes.object,
+ t: PropTypes.func,
+ network: PropTypes.string,
+}
+
+const mapStateToProps = state => {
+ const {
+ metamask: { network, selectedAddress, identities = {} },
+ } = state
+ const numberOfExistingAccounts = Object.keys(identities).length
+
+ return {
+ network,
+ address: selectedAddress,
+ numberOfExistingAccounts,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ toCoinbase: address =>
+ dispatch(actions.buyEth({ network: '1', address, amount: 0 })),
+ hideModal: () => dispatch(actions.hideModal()),
+ connectHardware: (deviceName, page) => {
+ return dispatch(actions.connectHardware(deviceName, page))
+ },
+ unlockTrezorAccount: index => {
+ return dispatch(actions.unlockTrezorAccount(index))
+ },
+ showImportPage: () => dispatch(actions.showImportPage()),
+ showConnectPage: () => dispatch(actions.showConnectPage()),
+ }
+}
+
+ConnectHardwareForm.contextTypes = {
+ t: PropTypes.func,
+}
+
+module.exports = connect(mapStateToProps, mapDispatchToProps)(
+ ConnectHardwareForm
+)
diff --git a/ui/app/components/pages/create-account/index.js b/ui/app/components/pages/create-account/index.js
index 6e3b93742..69a4db80a 100644
--- a/ui/app/components/pages/create-account/index.js
+++ b/ui/app/components/pages/create-account/index.js
@@ -8,7 +8,12 @@ const { getCurrentViewContext } = require('../../../selectors')
const classnames = require('classnames')
const NewAccountCreateForm = require('./new-account')
const NewAccountImportForm = require('./import-account')
-const { NEW_ACCOUNT_ROUTE, IMPORT_ACCOUNT_ROUTE } = require('../../../routes')
+const ConnectHardwareForm = require('./connect-hardware')
+const {
+ NEW_ACCOUNT_ROUTE,
+ IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
+} = require('../../../routes')
class CreateAccountPage extends Component {
renderTabs () {
@@ -36,6 +41,19 @@ class CreateAccountPage extends Component {
}, [
this.context.t('import'),
]),
+ h(
+ 'div.new-account__tabs__tab',
+ {
+ className: classnames('new-account__tabs__tab', {
+ 'new-account__tabs__selected': matchPath(location.pathname, {
+ path: CONNECT_HARDWARE_ROUTE,
+ exact: true,
+ }),
+ }),
+ onClick: () => history.push(CONNECT_HARDWARE_ROUTE),
+ },
+ this.context.t('connect')
+ ),
])
}
@@ -57,6 +75,11 @@ class CreateAccountPage extends Component {
path: IMPORT_ACCOUNT_ROUTE,
component: NewAccountImportForm,
}),
+ h(Route, {
+ exact: true,
+ path: CONNECT_HARDWARE_ROUTE,
+ component: ConnectHardwareForm,
+ }),
]),
]),
])
diff --git a/ui/app/components/pages/create-account/new-account.js b/ui/app/components/pages/create-account/new-account.js
index 9c94990e0..402b8f03b 100644
--- a/ui/app/components/pages/create-account/new-account.js
+++ b/ui/app/components/pages/create-account/new-account.js
@@ -62,6 +62,7 @@ class NewAccountCreateForm extends Component {
NewAccountCreateForm.propTypes = {
hideModal: PropTypes.func,
showImportPage: PropTypes.func,
+ showConnectPage: PropTypes.func,
createAccount: PropTypes.func,
numberOfExistingAccounts: PropTypes.number,
history: PropTypes.object,
@@ -92,6 +93,7 @@ const mapDispatchToProps = dispatch => {
})
},
showImportPage: () => dispatch(actions.showImportPage()),
+ showConnectPage: () => dispatch(actions.showConnectPage()),
}
}
diff --git a/ui/app/css/itcss/components/new-account.scss b/ui/app/css/itcss/components/new-account.scss
index 293579058..2ecc157f1 100644
--- a/ui/app/css/itcss/components/new-account.scss
+++ b/ui/app/css/itcss/components/new-account.scss
@@ -28,7 +28,6 @@
&__tab {
height: 54px;
- width: 75px;
padding: 15px 10px;
color: $dusty-gray;
font-family: Roboto;
@@ -38,10 +37,6 @@
cursor: pointer;
}
- &__tab:first-of-type {
- margin-right: 20px;
- }
-
&__tab:hover {
color: $black;
border-bottom: none;
@@ -158,6 +153,106 @@
}
}
+.hw-account-list {
+ display: flex;
+ flex: 1;
+ flex-flow: column;
+ width: 100%;
+
+ &__title_wrapper {
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+ }
+
+ &__title {
+ margin-bottom: 23px;
+ align-self: flex-start;
+ color: $scorpion;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 21px;
+ font-weight: bold;
+ display: flex;
+ flex: 1;
+ }
+
+ &__device {
+ margin-bottom: 23px;
+ align-self: flex-end;
+ color: $scorpion;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 21px;
+ font-weight: normal;
+ display: flex;
+ }
+
+ &__item {
+ font-size: 15px;
+ flex-direction: row;
+ display: flex;
+ padding: 10px;
+ }
+
+ &__item:nth-of-type(even) {
+ background-color: #fbfbfb;
+ }
+
+ &__item:nth-of-type(odd) {
+ background: rgba(0, 0, 0, 0.03);
+ }
+
+ &__item:hover {
+ background-color: rgba(0, 0, 0, 0.06);
+ }
+
+ &__item__index {
+ display: flex;
+ margin-right: 20px;
+ }
+
+ &__item__radio {
+ display: flex;
+ flex: 1;
+ }
+
+ &__item__label {
+ margin-left: 10px;
+ display: flex;
+ flex: 1;
+ }
+
+ &__item__balance {
+ display: flex;
+ flex: 1;
+ justify-content: center;
+ }
+
+ &__item__link {
+ display: flex;
+ }
+
+ &__item__link img {
+ width: 15px;
+ height: 15px;
+ }
+}
+
+.hw-list-pagination {
+ display: flex;
+ align-self: flex-end;
+ margin-top: 10px;
+
+ &__button {
+ height: 25px;
+ flex: initial;
+ min-width: 90px;
+ font-size: 12px;
+ }
+}
+
+
.new-account-create-form {
display: flex;
flex-flow: column;
diff --git a/ui/app/routes.js b/ui/app/routes.js
index 0ff3f644d..04f71165f 100644
--- a/ui/app/routes.js
+++ b/ui/app/routes.js
@@ -9,6 +9,7 @@ const ADD_TOKEN_ROUTE = '/add-token'
const CONFIRM_ADD_TOKEN_ROUTE = '/confirm-add-token'
const NEW_ACCOUNT_ROUTE = '/new-account'
const IMPORT_ACCOUNT_ROUTE = '/new-account/import'
+const CONNECT_HARDWARE_ROUTE = '/new-account/connect'
const SEND_ROUTE = '/send'
const CONFIRM_TRANSACTION_ROUTE = '/confirm-transaction'
const SIGNATURE_REQUEST_ROUTE = '/confirm-transaction/signature-request'
@@ -35,6 +36,7 @@ module.exports = {
CONFIRM_ADD_TOKEN_ROUTE,
NEW_ACCOUNT_ROUTE,
IMPORT_ACCOUNT_ROUTE,
+ CONNECT_HARDWARE_ROUTE,
SEND_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
NOTICE_ROUTE,