1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-23 11:46:13 +02:00
metamask-extension/ui/app/pages/create-account/connect-hardware/index.js
Mark Stacey df85ab6e10
Implement asset page (#8696)
A new page has been created for viewing assets. This replaces the old
`selectedToken` state, which previously would augment the home page
to show token-specific information.

The new asset page shows the standard token overview as seen previously
on the home page, plus a history filtered to show just transactions
relevant to that token.

The actions that were available in the old token list menu have been
moved to a "Token Options" menu that mirrors the "Account Options"
menu.

The `selectedTokenAddress` state has been removed, as it is no longer
being used for anything.

`getMetaMetricState` has been renamed to `getBackgroundMetaMetricState`
because its sole purpose is extracting data from the background state
to send metrics from the background. It's not really a selector, but
it was convenient for it to use the same selectors the UI uses to
extract background data, so I left it there for now.

A new Redux store has been added to track state related to browser history.
The most recent "overview" page (i.e. the home page or the asset page) is
currently being tracked, so that actions taken from the asset page can return
the user back to the asset page when the action has finished.
2020-06-01 14:54:32 -03:00

290 lines
8.4 KiB
JavaScript

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import * as actions from '../../../store/actions'
import { getMetaMaskAccounts } from '../../../selectors'
import SelectHardware from './select-hardware'
import AccountList from './account-list'
import { formatBalance } from '../../../helpers/utils/util'
import { getMostRecentOverviewPage } from '../../../ducks/history/history'
class ConnectHardwareForm extends Component {
state = {
error: null,
selectedAccount: null,
accounts: [],
browserSupported: true,
unlocked: false,
device: null,
}
UNSAFE_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) => {
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: this.context.t('ledgerAccountRestriction') })
}
showTemporaryAlert () {
this.props.showAlert(this.context.t('hardwareWalletConnected'))
// 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) => {
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) => {
const errorMessage = e.message
if (errorMessage === 'Window blocked') {
this.setState({ browserSupported: false, error: null })
} else if (errorMessage !== 'Window closed' && errorMessage !== 'Popup closed') {
this.setState({ error: errorMessage })
}
})
}
onForgetDevice = (device) => {
this.props.forgetDevice(device)
.then((_) => {
this.setState({
error: null,
selectedAccount: null,
accounts: [],
unlocked: false,
})
}).catch((e) => {
this.setState({ error: e.message })
})
}
onUnlockAccount = (device) => {
const { history, mostRecentOverviewPage, unlockHardwareWalletAccount } = this.props
if (this.state.selectedAccount === null) {
this.setState({ error: this.context.t('accountSelectionRequired') })
}
unlockHardwareWalletAccount(this.state.selectedAccount, device)
.then((_) => {
this.context.metricsEvent({
eventOpts: {
category: 'Accounts',
action: 'Connected Hardware Wallet',
name: 'Connected Account with: ' + device,
},
})
history.push(mostRecentOverviewPage)
}).catch((e) => {
this.context.metricsEvent({
eventOpts: {
category: 'Accounts',
action: 'Connected Hardware Wallet',
name: 'Error connecting hardware wallet',
},
customVariables: {
error: e.message,
},
})
this.setState({ error: e.message })
})
}
onCancel = () => {
const { history, mostRecentOverviewPage } = this.props
history.push(mostRecentOverviewPage)
}
renderError () {
return this.state.error
? (
<span
className="error"
style={{ margin: '20px 20px 10px', display: 'block', textAlign: 'center' }}
>
{this.state.error}
</span>
)
: null
}
renderContent () {
if (!this.state.accounts.length) {
return (
<SelectHardware
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}
onUnlockAccount={this.onUnlockAccount}
onForgetDevice={this.onForgetDevice}
onCancel={this.onCancel}
onAccountRestriction={this.onAccountRestriction}
/>
)
}
render () {
return (
<>
{this.renderError()}
{this.renderContent()}
</>
)
}
}
ConnectHardwareForm.propTypes = {
connectHardware: PropTypes.func,
checkHardwareStatus: PropTypes.func,
forgetDevice: PropTypes.func,
showAlert: PropTypes.func,
hideAlert: PropTypes.func,
unlockHardwareWalletAccount: PropTypes.func,
setHardwareWalletDefaultHdPath: PropTypes.func,
history: PropTypes.object,
network: PropTypes.string,
accounts: PropTypes.object,
address: PropTypes.string,
defaultHdPaths: PropTypes.object,
mostRecentOverviewPage: PropTypes.string.isRequired,
}
const mapStateToProps = (state) => {
const {
metamask: { network, selectedAddress },
} = state
const accounts = getMetaMaskAccounts(state)
const {
appState: { defaultHdPaths },
} = state
return {
network,
accounts,
address: selectedAddress,
defaultHdPaths,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
}
}
const mapDispatchToProps = (dispatch) => {
return {
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))
},
showAlert: (msg) => dispatch(actions.showAlert(msg)),
hideAlert: () => dispatch(actions.hideAlert()),
}
}
ConnectHardwareForm.contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
export default connect(mapStateToProps, mapDispatchToProps)(
ConnectHardwareForm
)