diff options
-rw-r--r-- | app/_locales/en/messages.json | 9 | ||||
-rw-r--r-- | app/images/connect-icon.svg | 11 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 66 | ||||
-rw-r--r-- | package-lock.json | 63 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | ui/app/actions.js | 42 | ||||
-rw-r--r-- | ui/app/components/account-menu/index.js | 9 | ||||
-rw-r--r-- | ui/app/components/pages/create-account/connect-hardware.js | 250 | ||||
-rw-r--r-- | ui/app/components/pages/create-account/index.js | 25 | ||||
-rw-r--r-- | ui/app/components/pages/create-account/new-account.js | 2 | ||||
-rw-r--r-- | ui/app/css/itcss/components/new-account.scss | 105 | ||||
-rw-r--r-- | ui/app/routes.js | 2 |
12 files changed, 576 insertions, 11 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 457c3c3b1..339dd8da2 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -119,6 +119,12 @@ "confirmTransaction": { "message": "Confirm Transaction" }, + "connectHardware": { + "message": "Connect Hardware" + }, + "connect": { + "message": "Connect" + }, "continue": { "message": "Continue" }, @@ -930,6 +936,9 @@ "unknownNetworkId": { "message": "Unknown network ID" }, + "unlock": { + "message": "Unlock" + }, "unlockMessage": { "message": "The decentralized web awaits" }, diff --git a/app/images/connect-icon.svg b/app/images/connect-icon.svg new file mode 100644 index 000000000..84540999a --- /dev/null +++ b/app/images/connect-icon.svg @@ -0,0 +1,11 @@ +<svg width="288" height="288" xmlns="http://www.w3.org/2000/svg"> + + <g> + <title>background</title> + <rect fill="none" id="canvas_background" height="402" width="582" y="-1" x="-1"/> + </g> + <g> + <title>Layer 1</title> + <path fill="#ffffff" id="svg_1" d="m122,25l15,-21c4,-5 10,-5 14,0l16,22c4,5 2,10 -5,10l-12,0l0,118c0,3 3,3 5,1l25,-25c4,-4 6,-10 6,-16l0,-24c-7,0 -12,-5 -12,-12l0,-12c0,-6 5,-12 12,-12l12,0c7,0 12,5 12,12l0,12c0,7 -5,12 -12,12l0,24c0,10 -3,18 -10,25l-31,31c-4,4 -7,6 -7,16l0,49c12,3 21,13 21,26c0,15 -12,27 -27,27s-27,-12 -27,-27c0,-13 9,-23 21,-26l0,-13c0,-10 -3,-13 -7,-17l-31,-31c-6,-6 -10,-14 -10,-24l0,-25c-7,-2 -12,-9 -12,-17c0,-10 8,-18 18,-18s18,8 18,18c0,8 -5,15 -12,17l0,25c0,7 3,12 7,16l25,25c2,2 4,2 4,-1l0,-154l-12,0c-7,0 -9,-5 -4,-11z"/> + </g> +</svg>
\ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e444180cc..6c380fd71 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -48,6 +48,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') +const TrezorKeyring = require('eth-trezor-keyring') module.exports = class MetamaskController extends EventEmitter { @@ -130,9 +131,11 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, }) - + // key mgmt + const additionalKeyrings = [TrezorKeyring] this.keyringController = new KeyringController({ + keyringTypes: additionalKeyrings, initState: initState.KeyringController, getNetwork: this.networkController.getNetworkState.bind(this.networkController), encryptor: opts.encryptor || undefined, @@ -363,6 +366,10 @@ module.exports = class MetamaskController extends EventEmitter { resetAccount: nodeify(this.resetAccount, this), importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), + // trezor + connectHardware: nodeify(this.connectHardware, this), + unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this), + // vault management submitPassword: nodeify(this.submitPassword, this), @@ -520,6 +527,63 @@ module.exports = class MetamaskController extends EventEmitter { } // + // Hardware + // + + /** + * Fetch account list from a trezor device. + * + * @returns [] accounts + */ + async connectHardware (deviceName, page) { + + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware' + )[0] + if (!keyring) { + throw new Error('MetamaskController - No Trezor Hardware Keyring found') + } + + const accounts = await keyring.getPage(page) + + this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) + + return accounts + } + + /** + * Imports an account from a trezor device. + * + * @returns {} keyState + */ + async unlockTrezorAccount (index) { + const keyringController = this.keyringController + const keyring = await keyringController.getKeyringsByType( + 'Trezor Hardware' + )[0] + if (!keyring) { + throw new Error('MetamaskController - No Trezor Hardware Keyring found') + } + + keyring.setAccountToUnlock(index) + const oldAccounts = await keyringController.getAccounts() + const keyState = await keyringController.addNewAccount(keyring) + const newAccounts = await keyringController.getAccounts() + + this.preferencesController.setAddresses(newAccounts) + newAccounts.forEach(address => { + if (!oldAccounts.includes(address)) { + this.preferencesController.setSelectedAddress(address) + } + }) + + const { identities } = this.preferencesController.store.getState() + return { ...keyState, identities } + } + + + // // Account Management // diff --git a/package-lock.json b/package-lock.json index 88047cc86..4d2bee8bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8169,9 +8169,8 @@ } }, "eth-keyring-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz", - "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==", + "version": "github:brunobar79/KeyringController#e447cb7cc93d1b7bf082710a6d5e21150588571e", + "from": "github:brunobar79/KeyringController#e447cb7cc93d1b7bf082710a6d5e21150588571e", "dev": true, "requires": { "bip39": "^2.4.0", @@ -8262,6 +8261,7 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#4ea2fdfed09e8f99117d9362d17c6b01b64a2bcf", "ethereumjs-util": "^5.1.1" }, "dependencies": { @@ -8488,6 +8488,62 @@ } } }, + "eth-trezor-keyring": { + "version": "github:brunobar79/eth-trezor-keyring#34fec28417c9203dc6ad7d7c53fa46a7f422aa4e", + "from": "github:brunobar79/eth-trezor-keyring#34fec28417c9203dc6ad7d7c53fa46a7f422aa4e", + "requires": { + "eth-sig-util": "^1.4.2", + "ethereumjs-tx": "^1.3.4", + "ethereumjs-util": "^5.1.5", + "events": "^2.0.0", + "hdkey": "0.8.0" + }, + "dependencies": { + "ethereum-common": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=" + }, + "ethereumjs-tx": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.4.tgz", + "integrity": "sha512-kOgUd5jC+0tgV7t52UDECMMz9Uf+Lro+6fSpCvzWemtXfMEcwI3EOxf5mVPMRbTFkMMhuERokNNVF3jItAjidg==", + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + }, + "hdkey": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/hdkey/-/hdkey-0.8.0.tgz", + "integrity": "sha512-oYsdlK22eobT68N5faWI3776f6tOLyqxLLYwxMx+TP0rkWzuCs0oiOm2VbLWcxdpHFP4LtiRR8udaIX8VkEaZQ==", + "requires": { + "coinstring": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + } + } + }, "eth-tx-summary": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.1.tgz", @@ -31331,6 +31387,7 @@ "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz", "integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=", "requires": { + "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2": "*", diff --git a/package.json b/package.json index d9e0bbec5..ce28d484a 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "eth-query": "^2.1.2", "eth-sig-util": "^1.4.2", "eth-token-tracker": "^1.1.4", + "eth-trezor-keyring": "github:brunobar79/eth-trezor-keyring#34fec28417c9203dc6ad7d7c53fa46a7f422aa4e", "ethereumjs-abi": "^0.6.4", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", @@ -230,7 +231,7 @@ "eslint-plugin-mocha": "^5.0.0", "eslint-plugin-react": "^7.4.0", "eth-json-rpc-middleware": "^1.6.0", - "eth-keyring-controller": "^3.1.4", + "eth-keyring-controller": "github:brunobar79/KeyringController#e447cb7cc93d1b7bf082710a6d5e21150588571e", "file-loader": "^1.1.11", "fs-promise": "^2.0.3", "ganache-cli": "^6.1.0", 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..152a4f275 --- /dev/null +++ b/ui/app/components/pages/create-account/connect-hardware.js @@ -0,0 +1,250 @@ +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') +const { formatBalance } = require('../../../util') + +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(1) + } + + getPage (page) { + 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, + }) + } + + getBalance (address) { + // Get the balance + log.debug('getBalance : ', address) + const { accounts } = this.props + const balanceValue = accounts && accounts[address] ? accounts[address].balance : '' + log.debug('balanceValue : ', balanceValue) + const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' + log.debug('formattedBalance : ', formattedBalance) + return formattedBalance + } + + 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', `${this.getBalance(a.address)}`), + 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(1), + }, + '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, + accounts: PropTypes.object, +} + +const mapStateToProps = state => { + const { + metamask: { network, selectedAddress, identities = {}, accounts = [] }, + } = state + const numberOfExistingAccounts = Object.keys(identities).length + + return { + network, + accounts, + 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, |