hw accounts list page
This commit is contained in:
parent
db9701d45c
commit
c6d30a01bc
|
@ -0,0 +1,200 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import ethNetProps from 'eth-net-props'
|
||||||
|
import { default as Select } from 'react-select'
|
||||||
|
import Button from '../../../../ui/app/components/button'
|
||||||
|
|
||||||
|
class AccountList extends Component {
|
||||||
|
constructor (props, context) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHdPaths () {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: `Ledger Live`,
|
||||||
|
value: `m/44'/60'/0'/0/0`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: `Legacy (MEW / MyCrypto)`,
|
||||||
|
value: `m/44'/60'/0'`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
goToNextPage = () => {
|
||||||
|
// If we have < 5 accounts, it's restricted by BIP-44
|
||||||
|
if (this.props.accounts.length === 5) {
|
||||||
|
this.props.getPage(this.props.device, 1, this.props.selectedPath)
|
||||||
|
} else {
|
||||||
|
this.props.onAccountRestriction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goToPreviousPage = () => {
|
||||||
|
this.props.getPage(this.props.device, -1, this.props.selectedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHdPathSelector () {
|
||||||
|
const { onPathChange, selectedPath } = this.props
|
||||||
|
|
||||||
|
const options = this.getHdPaths()
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3 className='hw-connect__hdPath__title'>this.context.t('selectHdPath')</h3>
|
||||||
|
<p className='hw-connect__msg'>this.context.t('selectPathHelp')</p>
|
||||||
|
<div className='hw-connect__hdPath'>
|
||||||
|
<Select
|
||||||
|
className='hw-connect__hdPath__select'
|
||||||
|
name='hd-path-select'
|
||||||
|
clearable={false}
|
||||||
|
value={selectedPath}
|
||||||
|
options
|
||||||
|
onChange={(opt) => {
|
||||||
|
onPathChange(opt.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
capitalizeDevice (device) {
|
||||||
|
return device.slice(0, 1).toUpperCase() + device.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHeader () {
|
||||||
|
const { device } = this.props
|
||||||
|
return (
|
||||||
|
<div className='hw-connect'>
|
||||||
|
<h3 className='hw-connect'>
|
||||||
|
<h3 className='hw-connect__unlock-title'>`${this.context.t('unlock')} ${this.capitalizeDevice(device)}`</h3>
|
||||||
|
{device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null}
|
||||||
|
<h3 className='hw-connect__hdPath__title'>{this.context.t('selectAnAccount')}</h3>
|
||||||
|
<p className='hw-connect__msg'>{this.context.t('selectAnAccountHelp')}</p>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAccounts () {
|
||||||
|
const rows = []
|
||||||
|
this.props.accounts.map((a, i) => {
|
||||||
|
rows.push(
|
||||||
|
<div className='hw-account-list__item' key={a.address}>
|
||||||
|
<div className='hw-account-list__item__radio'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
name='selectedAccount'
|
||||||
|
id={`address-${i}`}
|
||||||
|
value={a.index}
|
||||||
|
onChange={(e) => this.props.onAccountChange(e.target.value)}
|
||||||
|
checked={this.props.selectedAccount === a.index.toString()}
|
||||||
|
/>
|
||||||
|
<label className='hw-account-list__item__label' htmlFor={`address-${i}`}>
|
||||||
|
<span className='hw-account-list__item__index'>{a.index + 1}</span>
|
||||||
|
{`${a.address.slice(0, 4)}...${a.address.slice(-4)}`}
|
||||||
|
<span className='hw-account-list__item__balance'>{`${a.balance}`}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
className='hw-account-list__item__link'
|
||||||
|
href={ethNetProps.explorerLinks.getExplorerAccountLinkFor(a.address, this.props.network)}
|
||||||
|
target='_blank'
|
||||||
|
title={this.context.t('etherscanView')}
|
||||||
|
/>
|
||||||
|
<img src='images/popout.svg' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='hw-account-list'>{rows}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPagination () {
|
||||||
|
return (
|
||||||
|
<div className='hw-list-pagination'>
|
||||||
|
<button
|
||||||
|
className='hw-list-pagination__button'
|
||||||
|
onClick={this.goToPreviousPage}
|
||||||
|
>{`< ${this.context.t('prev')}`}</button>
|
||||||
|
<button
|
||||||
|
className='hw-list-pagination__button'
|
||||||
|
onClick={this.goToNextPage}
|
||||||
|
>{`${this.context.t('next')} >`}</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
const disabled = this.props.selectedAccount === null
|
||||||
|
const buttonProps = {}
|
||||||
|
if (disabled) {
|
||||||
|
buttonProps.disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='new-account-connect-form__buttons'>
|
||||||
|
<Button
|
||||||
|
type='default'
|
||||||
|
large={true}
|
||||||
|
className='new-account-connect-form__button'
|
||||||
|
onClick={this.props.onCancel.bind(this)}
|
||||||
|
>{this.context.t('cancel')}</Button>
|
||||||
|
<Button
|
||||||
|
type='primary'
|
||||||
|
large={true}
|
||||||
|
className='new-account-connect-form__button unlock'
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={this.props.onUnlockAccount.bind(this, this.props.device)}
|
||||||
|
>{this.context.t('unlock')}</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderForgetDevice () {
|
||||||
|
return (
|
||||||
|
<div className='hw-forget-device-container'>
|
||||||
|
<a onClick={this.props.onForgetDevice.bind(this, this.props.device)}>{this.context.t('forgetDevice')}</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div className='new-account-connect-form.account-list'>
|
||||||
|
{this.renderHeader()}
|
||||||
|
{this.renderAccounts()}
|
||||||
|
{this.renderPagination()}
|
||||||
|
{this.renderButtons()}
|
||||||
|
{this.renderForgetDevice()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AccountList.propTypes = {
|
||||||
|
onPathChange: PropTypes.func.isRequired,
|
||||||
|
selectedPath: PropTypes.string.isRequired,
|
||||||
|
device: PropTypes.string.isRequired,
|
||||||
|
accounts: PropTypes.array.isRequired,
|
||||||
|
onAccountChange: PropTypes.func.isRequired,
|
||||||
|
onForgetDevice: PropTypes.func.isRequired,
|
||||||
|
getPage: PropTypes.func.isRequired,
|
||||||
|
network: PropTypes.string,
|
||||||
|
selectedAccount: PropTypes.string,
|
||||||
|
history: PropTypes.object,
|
||||||
|
onUnlockAccount: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
onAccountRestriction: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountList.contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AccountList
|
|
@ -0,0 +1,135 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Button from '../../../../ui/app/components/button'
|
||||||
|
|
||||||
|
class ConnectScreen extends Component {
|
||||||
|
constructor (props, context) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
selectedDevice: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect = () => {
|
||||||
|
if (this.state.selectedDevice) {
|
||||||
|
this.props.connectToHardwareWallet(this.state.selectedDevice)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConnectToTrezorButton () {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`hw-connect__btn${this.state.selectedDevice === 'trezor' ? ' selected' : ''}`}
|
||||||
|
onClick={_ => this.setState({selectedDevice: 'trezor'})}
|
||||||
|
>
|
||||||
|
<img className="hw-connect__btn__img" src="images/trezor-logo.svg"/>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConnectToLedgerButton () {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`hw-connect__btn${this.state.selectedDevice === 'ledger' ? ' selected' : ''}`}
|
||||||
|
onClick={_ => this.setState({selectedDevice: 'ledger'})}
|
||||||
|
>
|
||||||
|
<img className="hw-connect__btn__img" src="images/ledger-logo.svg"/>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderButtons () {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="hw-connect__btn-wrapper">
|
||||||
|
{this.renderConnectToLedgerButton()}
|
||||||
|
{this.renderConnectToTrezorButton()}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={`hw-connect__connect-btn${!this.state.selectedDevice ? ' disabled' : ''}`}
|
||||||
|
onClick={this.connect}
|
||||||
|
>Connect</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderUnsupportedBrowser () {
|
||||||
|
return (
|
||||||
|
<div className="new-account-connect-form unsupported-browser">
|
||||||
|
<div className="hw-connect">
|
||||||
|
<h3 className="hw-connect__title">Your Browser is not supported...</h3>
|
||||||
|
<p className="hw-connect__msg">You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet.</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
large={true}
|
||||||
|
onClick={() => global.platform.openWindow({
|
||||||
|
url: 'https://google.com/chrome',
|
||||||
|
})}
|
||||||
|
>Download Google Chrome</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHeader () {
|
||||||
|
return (
|
||||||
|
<div className="hw-connect__header">
|
||||||
|
<p className="hw-connect__header__msg">{`Select a hardware wallet you'd like to use with MetaMask`}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAffiliateLinks () {
|
||||||
|
const links = {
|
||||||
|
trezor: `<a class='hw-connect__get-hw__link' href='https://shop.trezor.io/?a=metamask' target='_blank'>Trezor</a>`,
|
||||||
|
ledger: `<a class='hw-connect__get-hw__link' href='https://www.ledger.com/products/ledger-nano-s?r=17c4991a03fa&tracker=MY_TRACKER' target='_blank'>Ledger</a>`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = 'Order a Trezor or Ledger and keep your funds in cold storage'
|
||||||
|
const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="hw-connect__get-hw__msg" dangerouslySetInnerHTML={{ __html: response }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTrezorAffiliateLink () {
|
||||||
|
return (
|
||||||
|
<div className="hw-connect__get-hw">
|
||||||
|
<p className="hw-connect__get-hw__msg">Don’t have a hardware wallet?</p>
|
||||||
|
{this.getAffiliateLinks()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
scrollToTutorial = (e) => {
|
||||||
|
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConnectScreen () {
|
||||||
|
return (
|
||||||
|
<div className="new-account-connect-form">
|
||||||
|
{this.renderHeader()}
|
||||||
|
{this.renderButtons()}
|
||||||
|
{this.renderTrezorAffiliateLink()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
if (this.props.browserSupported) {
|
||||||
|
return this.renderConnectScreen()
|
||||||
|
}
|
||||||
|
return this.renderUnsupportedBrowser()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectScreen.propTypes = {
|
||||||
|
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||||
|
browserSupported: PropTypes.bool.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ConnectScreen
|
||||||
|
|
|
@ -0,0 +1,290 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import actions from '../../../../ui/app/actions'
|
||||||
|
import ConnectScreen from './connect-screen'
|
||||||
|
import AccountList from './account-list'
|
||||||
|
import { DEFAULT_ROUTE } from '../../../../ui/app/routes'
|
||||||
|
import { formatBalance } from '../../../../ui/app/util'
|
||||||
|
import { getPlatform } from '../../../../app/scripts/lib/util'
|
||||||
|
import { PLATFORM_FIREFOX } from '../../../../app/scripts/lib/enums'
|
||||||
|
|
||||||
|
class ConnectHardwareForm extends Component {
|
||||||
|
constructor (props, context) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
error: null,
|
||||||
|
selectedAccount: null,
|
||||||
|
accounts: [],
|
||||||
|
browserSupported: true,
|
||||||
|
unlocked: false,
|
||||||
|
device: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
const { accounts } = nextProps
|
||||||
|
const newAccounts = this.state.accounts.map(a => {
|
||||||
|
const normalizedAddress = a.address.toLowerCase()
|
||||||
|
const balanceValue = accounts[normalizedAddress] && accounts[normalizedAddress].balance || null
|
||||||
|
a.balance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||||
|
return a
|
||||||
|
})
|
||||||
|
this.setState({accounts: newAccounts})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.checkIfUnlocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkIfUnlocked () {
|
||||||
|
['trezor', 'ledger'].forEach(async device => {
|
||||||
|
const unlocked = await this.props.checkHardwareStatus(device, this.props.defaultHdPaths[device])
|
||||||
|
if (unlocked) {
|
||||||
|
this.setState({unlocked: true})
|
||||||
|
this.getPage(device, 0, this.props.defaultHdPaths[device])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToHardwareWallet = (device) => {
|
||||||
|
// None of the hardware wallets are supported
|
||||||
|
// At least for now
|
||||||
|
if (getPlatform() === PLATFORM_FIREFOX) {
|
||||||
|
this.setState({ browserSupported: false, error: null})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.accounts.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
this.getPage(device, 0, this.props.defaultHdPaths[device])
|
||||||
|
}
|
||||||
|
|
||||||
|
onPathChange = (path) => {
|
||||||
|
this.props.setHardwareWalletDefaultHdPath({device: this.state.device, path})
|
||||||
|
this.getPage(this.state.device, 0, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccountChange = (account) => {
|
||||||
|
this.setState({selectedAccount: account.toString(), error: null})
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccountRestriction = () => {
|
||||||
|
this.setState({error: 'You need to make use your last account before you can add a new one.' })
|
||||||
|
}
|
||||||
|
|
||||||
|
showTemporaryAlert () {
|
||||||
|
this.props.showAlert('Hardware wallet connected')
|
||||||
|
// Autohide the alert after 5 seconds
|
||||||
|
setTimeout(_ => {
|
||||||
|
this.props.hideAlert()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPage = (device, page, hdPath) => {
|
||||||
|
this.props
|
||||||
|
.connectHardware(device, page, hdPath)
|
||||||
|
.then(accounts => {
|
||||||
|
if (accounts.length) {
|
||||||
|
|
||||||
|
// If we just loaded the accounts for the first time
|
||||||
|
// (device previously locked) show the global alert
|
||||||
|
if (this.state.accounts.length === 0 && !this.state.unlocked) {
|
||||||
|
this.showTemporaryAlert()
|
||||||
|
}
|
||||||
|
|
||||||
|
const newState = { unlocked: true, device, error: null }
|
||||||
|
// Default to the first account
|
||||||
|
if (this.state.selectedAccount === null) {
|
||||||
|
accounts.forEach((a, i) => {
|
||||||
|
if (a.address.toLowerCase() === this.props.address) {
|
||||||
|
newState.selectedAccount = a.index.toString()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// If the page doesn't contain the selected account, let's deselect it
|
||||||
|
} else if (!accounts.filter(a => a.index.toString() === this.state.selectedAccount).length) {
|
||||||
|
newState.selectedAccount = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Map accounts with balances
|
||||||
|
newState.accounts = accounts.map(account => {
|
||||||
|
const normalizedAddress = account.address.toLowerCase()
|
||||||
|
const balanceValue = this.props.accounts[normalizedAddress] && this.props.accounts[normalizedAddress].balance || null
|
||||||
|
account.balance = balanceValue ? formatBalance(balanceValue, 6) : '...'
|
||||||
|
return account
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState(newState)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
if (e === 'Window blocked') {
|
||||||
|
this.setState({ browserSupported: false, error: null})
|
||||||
|
} else if (e !== 'Window closed') {
|
||||||
|
this.setState({ error: e.toString() })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onForgetDevice = (device) => {
|
||||||
|
this.props.forgetDevice(device)
|
||||||
|
.then(_ => {
|
||||||
|
this.setState({
|
||||||
|
error: null,
|
||||||
|
selectedAccount: null,
|
||||||
|
accounts: [],
|
||||||
|
unlocked: false,
|
||||||
|
})
|
||||||
|
}).catch(e => {
|
||||||
|
this.setState({ error: e.toString() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnlockAccount = (device) => {
|
||||||
|
|
||||||
|
if (this.state.selectedAccount === null) {
|
||||||
|
this.setState({ error: 'You need to select an account!' })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.unlockHardwareWalletAccount(this.state.selectedAccount, device)
|
||||||
|
.then(_ => {
|
||||||
|
this.props.history.push(DEFAULT_ROUTE)
|
||||||
|
}).catch(e => {
|
||||||
|
this.setState({ error: e.toString() })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel = () => {
|
||||||
|
this.props.history.push(DEFAULT_ROUTE)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderError () {
|
||||||
|
return this.state.error
|
||||||
|
? <span className="error" style={{ display: 'block', textAlign: 'center' }}>{this.state.error}</span>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
renderContent () {
|
||||||
|
if (!this.state.accounts.length) {
|
||||||
|
return (
|
||||||
|
<ConnectScreen
|
||||||
|
connectToHardwareWallet={this.connectToHardwareWallet}
|
||||||
|
browserSupported={this.state.browserSupported}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountList
|
||||||
|
onPathChange={this.onPathChange}
|
||||||
|
selectedPath={this.props.defaultHdPaths[this.state.device]}
|
||||||
|
device={this.state.device}
|
||||||
|
accounts={this.state.accounts}
|
||||||
|
selectedAccount={this.state.selectedAccount}
|
||||||
|
onAccountChange={this.onAccountChange}
|
||||||
|
network={this.props.network}
|
||||||
|
getPage={this.getPage}
|
||||||
|
history={this.props.history}
|
||||||
|
onUnlockAccount={this.onUnlockAccount}
|
||||||
|
onForgetDevice={this.onForgetDevice}
|
||||||
|
onCancel={this.onCancel}
|
||||||
|
onAccountRestriction={this.onAccountRestriction}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div style={{width: '100%'}}>
|
||||||
|
<div className="section-title flex-row flex-center">
|
||||||
|
<i className="fa fa-arrow-left fa-lg cursor-pointer"
|
||||||
|
onClick={() => this.props.goHome() }
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '30px',
|
||||||
|
}}/>
|
||||||
|
<h2>Connect to hardware wallet</h2>
|
||||||
|
</div>
|
||||||
|
<div style={{overflowY: 'auto', height: '482px'}}>
|
||||||
|
<div style={{padding: '0 30px'}}>
|
||||||
|
{this.renderError()}
|
||||||
|
{this.renderContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectHardwareForm.propTypes = {
|
||||||
|
hideModal: PropTypes.func,
|
||||||
|
showImportPage: PropTypes.func,
|
||||||
|
showConnectPage: PropTypes.func,
|
||||||
|
connectHardware: PropTypes.func,
|
||||||
|
checkHardwareStatus: PropTypes.func,
|
||||||
|
forgetDevice: PropTypes.func,
|
||||||
|
showAlert: PropTypes.func,
|
||||||
|
hideAlert: PropTypes.func,
|
||||||
|
unlockHardwareWalletAccount: PropTypes.func,
|
||||||
|
setHardwareWalletDefaultHdPath: PropTypes.func,
|
||||||
|
goHome: PropTypes.func,
|
||||||
|
numberOfExistingAccounts: PropTypes.number,
|
||||||
|
history: PropTypes.object,
|
||||||
|
t: PropTypes.func,
|
||||||
|
network: PropTypes.string,
|
||||||
|
accounts: PropTypes.object,
|
||||||
|
address: PropTypes.string,
|
||||||
|
defaultHdPaths: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const {
|
||||||
|
metamask: { network, selectedAddress, identities = {}, accounts = [] },
|
||||||
|
} = state
|
||||||
|
const numberOfExistingAccounts = Object.keys(identities).length
|
||||||
|
const {
|
||||||
|
appState: { defaultHdPaths },
|
||||||
|
} = state
|
||||||
|
|
||||||
|
return {
|
||||||
|
network,
|
||||||
|
accounts,
|
||||||
|
address: selectedAddress,
|
||||||
|
numberOfExistingAccounts,
|
||||||
|
defaultHdPaths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
goHome: () => {
|
||||||
|
dispatch(actions.goHome())
|
||||||
|
},
|
||||||
|
setHardwareWalletDefaultHdPath: ({device, path}) => {
|
||||||
|
return dispatch(actions.setHardwareWalletDefaultHdPath({device, path}))
|
||||||
|
},
|
||||||
|
connectHardware: (deviceName, page, hdPath) => {
|
||||||
|
return dispatch(actions.connectHardware(deviceName, page, hdPath))
|
||||||
|
},
|
||||||
|
checkHardwareStatus: (deviceName, hdPath) => {
|
||||||
|
return dispatch(actions.checkHardwareStatus(deviceName, hdPath))
|
||||||
|
},
|
||||||
|
forgetDevice: (deviceName) => {
|
||||||
|
return dispatch(actions.forgetDevice(deviceName))
|
||||||
|
},
|
||||||
|
unlockHardwareWalletAccount: (index, deviceName, hdPath) => {
|
||||||
|
return dispatch(actions.unlockHardwareWalletAccount(index, deviceName, hdPath))
|
||||||
|
},
|
||||||
|
showImportPage: () => dispatch(actions.showImportPage()),
|
||||||
|
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||||
|
showAlert: (msg) => dispatch(actions.showAlert(msg)),
|
||||||
|
hideAlert: () => dispatch(actions.hideAlert()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = connect(mapStateToProps, mapDispatchToProps)(ConnectHardwareForm)
|
|
@ -0,0 +1,262 @@
|
||||||
|
.hw-tutorial {
|
||||||
|
width: 375px;
|
||||||
|
border-top: 1px solid #D2D8DD;
|
||||||
|
border-bottom: 1px solid #D2D8DD;
|
||||||
|
overflow: visible;
|
||||||
|
display: block;
|
||||||
|
padding: 15px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-connect {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.hw-connect__header__title {
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
.hw-connect__header__msg {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9b9b9b;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.hw-connect__btn-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.hw-connect__connect-btn {
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 54px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.hw-connect__connect-btn.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
.hw-connect__btn {
|
||||||
|
background: #fbfbfb;
|
||||||
|
border: 1px solid #e5e5e5;
|
||||||
|
height: 100px;
|
||||||
|
width: 150px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.hw-connect__btn__img {
|
||||||
|
width: 95px;
|
||||||
|
}
|
||||||
|
.hw-connect__btn.selected {
|
||||||
|
border: 2px solid #60DB97;
|
||||||
|
width: 149px;
|
||||||
|
}
|
||||||
|
.hw-connect__btn:first-child {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.hw-connect__btn:last-child {
|
||||||
|
|
||||||
|
}
|
||||||
|
.hw-connect__hdPath {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.hw-connect__hdPath__title {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.hw-connect__hdPath__select {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.hw-connect__learn-more {
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #5B5D67;
|
||||||
|
line-height: 19px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.hw-connect__learn-more__arrow {
|
||||||
|
-webkit-transform: rotate(90deg);
|
||||||
|
transform: rotate(90deg);
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0px auto 10px;
|
||||||
|
}
|
||||||
|
.hw-connect__title {
|
||||||
|
padding-top: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.hw-connect__unlock-title {
|
||||||
|
padding-top: 10px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 22px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.hw-connect__msg {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9b9b9b;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.hw-connect__link {
|
||||||
|
color: #2f9ae0;
|
||||||
|
}
|
||||||
|
.hw-connect__footer__title {
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.hw-connect__footer__msg {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9b9b9b;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 27px;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
.hw-connect__footer__link {
|
||||||
|
color: #2f9ae0;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.hw-connect__get-hw {
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
.hw-connect__get-hw__msg {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9b9b9b;
|
||||||
|
}
|
||||||
|
.hw-connect__get-hw__link {
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
color: #60DB97;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.hw-connect__step-asset {
|
||||||
|
margin: 0px auto 20px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hw-account-list {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-flow: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.hw-account-list__title_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.hw-account-list__title {
|
||||||
|
margin-bottom: 23px;
|
||||||
|
align-self: flex-start;
|
||||||
|
color: #5d5d5d;
|
||||||
|
font-family: Roboto;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 21px;
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.hw-account-list__device {
|
||||||
|
margin-bottom: 23px;
|
||||||
|
align-self: flex-end;
|
||||||
|
color: #5d5d5d;
|
||||||
|
font-family: Roboto;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 21px;
|
||||||
|
font-weight: normal;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.hw-account-list__item {
|
||||||
|
font-size: 15px;
|
||||||
|
flex-direction: row;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.hw-account-list__item:nth-of-type(even) {
|
||||||
|
background-color: #fbfbfb;
|
||||||
|
}
|
||||||
|
.hw-account-list__item:nth-of-type(odd) {
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
.hw-account-list__item:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.hw-account-list__item__index {
|
||||||
|
display: flex;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
.hw-account-list__item__radio {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.hw-account-list__item__radio input {
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
.hw-account-list__item__label {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.hw-account-list__item__balance {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.hw-account-list__item__link {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 13px;
|
||||||
|
}
|
||||||
|
.hw-account-list__item__link img {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
.hw-list-pagination {
|
||||||
|
display: flex;
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.hw-list-pagination__button {
|
||||||
|
height: 19px;
|
||||||
|
display: flex;
|
||||||
|
color: #33a4e7;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 19px;
|
||||||
|
border: none;
|
||||||
|
min-width: 46px;
|
||||||
|
margin-right: 0px;
|
||||||
|
margin-left: 16px;
|
||||||
|
padding: 0px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: Roboto;
|
||||||
|
}
|
Loading…
Reference in New Issue