3d iteration: Remove confirm screens
This commit is contained in:
parent
0b6be5f7fd
commit
700416bea3
|
@ -1,122 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DEFAULT_ROUTE } from '../../../routes'
|
||||
import Button from '../../button'
|
||||
import Identicon from '../../../components/identicon'
|
||||
import TokenBalance from '../../token-balance'
|
||||
|
||||
export default class ConfirmAddSuggestedToken extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
clearPendingTokens: PropTypes.func,
|
||||
addToken: PropTypes.func,
|
||||
pendingTokens: PropTypes.object,
|
||||
removeSuggestedTokens: PropTypes.func,
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { pendingTokens = {}, history } = this.props
|
||||
|
||||
if (Object.keys(pendingTokens).length === 0) {
|
||||
history.push(DEFAULT_ROUTE)
|
||||
}
|
||||
}
|
||||
|
||||
getTokenName (name, symbol) {
|
||||
return typeof name === 'undefined'
|
||||
? symbol
|
||||
: `${name} (${symbol})`
|
||||
}
|
||||
|
||||
render () {
|
||||
const { addToken, pendingTokens, removeSuggestedTokens, history } = this.props
|
||||
const pendingTokenKey = Object.keys(pendingTokens)[0]
|
||||
const pendingToken = pendingTokens[pendingTokenKey]
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="page-container__header">
|
||||
<div className="page-container__title">
|
||||
{ this.context.t('addSuggestedTokens') }
|
||||
</div>
|
||||
<div className="page-container__subtitle">
|
||||
{ this.context.t('likeToAddTokens') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-container__content">
|
||||
<div className="confirm-add-token">
|
||||
<div className="confirm-add-token__header">
|
||||
<div className="confirm-add-token__token">
|
||||
{ this.context.t('token') }
|
||||
</div>
|
||||
<div className="confirm-add-token__balance">
|
||||
{ this.context.t('balance') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-add-token__token-list">
|
||||
{
|
||||
Object.entries(pendingTokens)
|
||||
.map(([ address, token ]) => {
|
||||
const { name, symbol, image } = token
|
||||
|
||||
return (
|
||||
<div
|
||||
className="confirm-add-token__token-list-item"
|
||||
key={address}
|
||||
>
|
||||
<div className="confirm-add-token__token confirm-add-token__data">
|
||||
<Identicon
|
||||
className="confirm-add-token__token-icon"
|
||||
diameter={48}
|
||||
address={address}
|
||||
image={image}
|
||||
/>
|
||||
<div className="confirm-add-token__name">
|
||||
{ this.getTokenName(name, symbol) }
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-add-token__balance">
|
||||
<TokenBalance token={token} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-container__footer">
|
||||
<header>
|
||||
<Button
|
||||
type="default"
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => {
|
||||
removeSuggestedTokens()
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
}}
|
||||
>
|
||||
{ this.context.t('cancel') }
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => {
|
||||
addToken(pendingToken)
|
||||
.then(() => removeSuggestedTokens())
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
}}
|
||||
>
|
||||
{ this.context.t('addToken') }
|
||||
</Button>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import ConfirmAddSuggestedToken from './confirm-add-suggested-token.component'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
|
||||
const extend = require('xtend')
|
||||
|
||||
const { addToken, removeSuggestedTokens } = require('../../../actions')
|
||||
|
||||
const mapStateToProps = ({ metamask }) => {
|
||||
const { pendingTokens, suggestedTokens } = metamask
|
||||
const params = extend(pendingTokens, suggestedTokens)
|
||||
|
||||
return {
|
||||
pendingTokens: params,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)),
|
||||
removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(ConfirmAddSuggestedToken)
|
|
@ -1,2 +0,0 @@
|
|||
import ConfirmAddSuggestedToken from './confirm-add-suggested-token.container'
|
||||
module.exports = ConfirmAddSuggestedToken
|
|
@ -1,117 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DEFAULT_ROUTE, ADD_TOKEN_ROUTE } from '../../../routes'
|
||||
import Button from '../../button'
|
||||
import Identicon from '../../identicon'
|
||||
import TokenBalance from '../../token-balance'
|
||||
|
||||
export default class ConfirmAddToken extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
clearPendingTokens: PropTypes.func,
|
||||
addTokens: PropTypes.func,
|
||||
pendingTokens: PropTypes.object,
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { pendingTokens = {}, history } = this.props
|
||||
|
||||
if (Object.keys(pendingTokens).length === 0) {
|
||||
history.push(DEFAULT_ROUTE)
|
||||
}
|
||||
}
|
||||
|
||||
getTokenName (name, symbol) {
|
||||
return typeof name === 'undefined'
|
||||
? symbol
|
||||
: `${name} (${symbol})`
|
||||
}
|
||||
|
||||
render () {
|
||||
const { history, addTokens, clearPendingTokens, pendingTokens } = this.props
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<div className="page-container__header">
|
||||
<div className="page-container__title">
|
||||
{ this.context.t('addTokens') }
|
||||
</div>
|
||||
<div className="page-container__subtitle">
|
||||
{ this.context.t('likeToAddTokens') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-container__content">
|
||||
<div className="confirm-add-token">
|
||||
<div className="confirm-add-token__header">
|
||||
<div className="confirm-add-token__token">
|
||||
{ this.context.t('token') }
|
||||
</div>
|
||||
<div className="confirm-add-token__balance">
|
||||
{ this.context.t('balance') }
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-add-token__token-list">
|
||||
{
|
||||
Object.entries(pendingTokens)
|
||||
.map(([ address, token ]) => {
|
||||
const { name, symbol } = token
|
||||
|
||||
return (
|
||||
<div
|
||||
className="confirm-add-token__token-list-item"
|
||||
key={address}
|
||||
>
|
||||
<div className="confirm-add-token__token confirm-add-token__data">
|
||||
<Identicon
|
||||
className="confirm-add-token__token-icon"
|
||||
diameter={48}
|
||||
address={address}
|
||||
/>
|
||||
<div className="confirm-add-token__name">
|
||||
{ this.getTokenName(name, symbol) }
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-add-token__balance">
|
||||
<TokenBalance token={token} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-container__footer">
|
||||
<header>
|
||||
<Button
|
||||
type="default"
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => history.push(ADD_TOKEN_ROUTE)}
|
||||
>
|
||||
{ this.context.t('back') }
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
large
|
||||
className="page-container__footer-button"
|
||||
onClick={() => {
|
||||
addTokens(pendingTokens)
|
||||
.then(() => {
|
||||
clearPendingTokens()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
})
|
||||
}}
|
||||
>
|
||||
{ this.context.t('addTokens') }
|
||||
</Button>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import ConfirmAddToken from './confirm-add-token.component'
|
||||
|
||||
const { addTokens, clearPendingTokens } = require('../../../actions')
|
||||
|
||||
const mapStateToProps = ({ metamask }) => {
|
||||
const { pendingTokens } = metamask
|
||||
return {
|
||||
pendingTokens,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
addTokens: tokens => dispatch(addTokens(tokens)),
|
||||
clearPendingTokens: () => dispatch(clearPendingTokens()),
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ConfirmAddToken)
|
|
@ -1,2 +0,0 @@
|
|||
import ConfirmAddToken from './confirm-add-token.container'
|
||||
module.exports = ConfirmAddToken
|
|
@ -1,69 +0,0 @@
|
|||
.confirm-add-token {
|
||||
padding: 16px;
|
||||
|
||||
&__header {
|
||||
font-size: .75rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__token {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__balance {
|
||||
flex: 0 0 30%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__token-list {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
||||
.token-balance {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: flex-start;
|
||||
|
||||
&__amount {
|
||||
color: $scorpion;
|
||||
font-size: 43px;
|
||||
line-height: 43px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&__symbol {
|
||||
color: $scorpion;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__token-list-item {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__data {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__token-icon {
|
||||
margin-right: 12px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
|
||||
|
||||
export default class ConfirmApprove extends Component {
|
||||
static propTypes = {
|
||||
tokenAmount: PropTypes.number,
|
||||
tokenSymbol: PropTypes.string,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { tokenAmount, tokenSymbol } = this.props
|
||||
|
||||
return (
|
||||
<ConfirmTokenTransactionBase
|
||||
tokenAmount={tokenAmount}
|
||||
warning={`By approving this action, you grant permission for this contract to spend up to ${tokenAmount} of your ${tokenSymbol}.`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import ConfirmApprove from './confirm-approve.component'
|
||||
import { approveTokenAmountAndToAddressSelector } from '../../../selectors/confirm-transaction'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { confirmTransaction: { tokenProps: { tokenSymbol } = {} } } = state
|
||||
const { tokenAmount } = approveTokenAmountAndToAddressSelector(state)
|
||||
|
||||
return {
|
||||
tokenAmount,
|
||||
tokenSymbol,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ConfirmApprove)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-approve.container'
|
|
@ -1,64 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
|
||||
export default class ConfirmDeployContract extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
txData: PropTypes.object,
|
||||
}
|
||||
|
||||
renderData () {
|
||||
const { t } = this.context
|
||||
const {
|
||||
txData: {
|
||||
origin,
|
||||
txParams: {
|
||||
data,
|
||||
} = {},
|
||||
} = {},
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="confirm-page-container-content__data">
|
||||
<div className="confirm-page-container-content__data-box">
|
||||
<div className="confirm-page-container-content__data-field">
|
||||
<div className="confirm-page-container-content__data-field-label">
|
||||
{ `${t('origin')}:` }
|
||||
</div>
|
||||
<div>
|
||||
{ origin }
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-page-container-content__data-field">
|
||||
<div className="confirm-page-container-content__data-field-label">
|
||||
{ `${t('bytes')}:` }
|
||||
</div>
|
||||
<div>
|
||||
{ ethUtil.toBuffer(data).length }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="confirm-page-container-content__data-box-label">
|
||||
{ `${t('hexData')}:` }
|
||||
</div>
|
||||
<div className="confirm-page-container-content__data-box">
|
||||
{ data }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<ConfirmTransactionBase
|
||||
action={this.context.t('contractDeployment')}
|
||||
dataComponent={this.renderData()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import ConfirmDeployContract from './confirm-deploy-contract.component'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { confirmTransaction: { txData } = {} } = state
|
||||
|
||||
return {
|
||||
txData,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ConfirmDeployContract)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-deploy-contract.container'
|
|
@ -1,39 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
import { SEND_ROUTE } from '../../../routes'
|
||||
|
||||
export default class ConfirmSendEther extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
editTransaction: PropTypes.func,
|
||||
history: PropTypes.object,
|
||||
txParams: PropTypes.object,
|
||||
}
|
||||
|
||||
handleEdit ({ txData }) {
|
||||
const { editTransaction, history } = this.props
|
||||
editTransaction(txData)
|
||||
history.push(SEND_ROUTE)
|
||||
}
|
||||
|
||||
shouldHideData () {
|
||||
const { txParams = {} } = this.props
|
||||
return !txParams.data
|
||||
}
|
||||
|
||||
render () {
|
||||
const hideData = this.shouldHideData()
|
||||
|
||||
return (
|
||||
<ConfirmTransactionBase
|
||||
action={this.context.t('confirm')}
|
||||
hideData={hideData}
|
||||
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { updateSend } from '../../../actions'
|
||||
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
|
||||
import ConfirmSendEther from './confirm-send-ether.component'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { confirmTransaction: { txData: { txParams } = {} } } = state
|
||||
|
||||
return {
|
||||
txParams,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
editTransaction: txData => {
|
||||
const { id, txParams } = txData
|
||||
const {
|
||||
gas: gasLimit,
|
||||
gasPrice,
|
||||
to,
|
||||
value: amount,
|
||||
} = txParams
|
||||
|
||||
dispatch(updateSend({
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
gasTotal: null,
|
||||
to,
|
||||
amount,
|
||||
errors: { to: null, amount: null },
|
||||
editingTransactionId: id && id.toString(),
|
||||
}))
|
||||
|
||||
dispatch(clearConfirmTransaction())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(ConfirmSendEther)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-send-ether.container'
|
|
@ -1,29 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
|
||||
import { SEND_ROUTE } from '../../../routes'
|
||||
|
||||
export default class ConfirmSendToken extends Component {
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
editTransaction: PropTypes.func,
|
||||
tokenAmount: PropTypes.number,
|
||||
}
|
||||
|
||||
handleEdit (confirmTransactionData) {
|
||||
const { editTransaction, history } = this.props
|
||||
editTransaction(confirmTransactionData)
|
||||
history.push(SEND_ROUTE)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { tokenAmount } = this.props
|
||||
|
||||
return (
|
||||
<ConfirmTokenTransactionBase
|
||||
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
|
||||
tokenAmount={tokenAmount}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import ConfirmSendToken from './confirm-send-token.component'
|
||||
import { clearConfirmTransaction } from '../../../ducks/confirm-transaction.duck'
|
||||
import { setSelectedToken, updateSend, showSendTokenPage } from '../../../actions'
|
||||
import { conversionUtil } from '../../../conversion-util'
|
||||
import { sendTokenTokenAmountAndToAddressSelector } from '../../../selectors/confirm-transaction'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { tokenAmount } = sendTokenTokenAmountAndToAddressSelector(state)
|
||||
|
||||
return {
|
||||
tokenAmount,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
editTransaction: ({ txData, tokenData, tokenProps }) => {
|
||||
const { txParams: { to: tokenAddress, gas: gasLimit, gasPrice } = {}, id } = txData
|
||||
const { params = [] } = tokenData
|
||||
const { value: to } = params[0] || {}
|
||||
const { value: tokenAmountInDec } = params[1] || {}
|
||||
const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
|
||||
fromNumericBase: 'dec',
|
||||
toNumericBase: 'hex',
|
||||
})
|
||||
dispatch(setSelectedToken(tokenAddress))
|
||||
dispatch(updateSend({
|
||||
gasLimit,
|
||||
gasPrice,
|
||||
gasTotal: null,
|
||||
to,
|
||||
amount: tokenAmountInHex,
|
||||
errors: { to: null, amount: null },
|
||||
editingTransactionId: id && id.toString(),
|
||||
token: {
|
||||
...tokenProps,
|
||||
address: tokenAddress,
|
||||
},
|
||||
}))
|
||||
dispatch(clearConfirmTransaction())
|
||||
dispatch(showSendTokenPage())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(ConfirmSendToken)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-send-token.container'
|
|
@ -1,119 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import {
|
||||
formatCurrency,
|
||||
convertTokenToFiat,
|
||||
addFiat,
|
||||
roundExponential,
|
||||
} from '../../../helpers/confirm-transaction/util'
|
||||
import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
|
||||
import { ETH, PRIMARY } from '../../../constants/common'
|
||||
|
||||
export default class ConfirmTokenTransactionBase extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
tokenAddress: PropTypes.string,
|
||||
toAddress: PropTypes.string,
|
||||
tokenAmount: PropTypes.number,
|
||||
tokenSymbol: PropTypes.string,
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
ethTransactionTotal: PropTypes.string,
|
||||
contractExchangeRate: PropTypes.number,
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
}
|
||||
|
||||
getFiatTransactionAmount () {
|
||||
const { tokenAmount, currentCurrency, conversionRate, contractExchangeRate } = this.props
|
||||
|
||||
return convertTokenToFiat({
|
||||
value: tokenAmount,
|
||||
toCurrency: currentCurrency,
|
||||
conversionRate,
|
||||
contractExchangeRate,
|
||||
})
|
||||
}
|
||||
|
||||
renderSubtitleComponent () {
|
||||
const { contractExchangeRate, tokenAmount } = this.props
|
||||
|
||||
const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
|
||||
const hexWeiValue = getWeiHexFromDecimalValue({
|
||||
value: decimalEthValue,
|
||||
fromCurrency: ETH,
|
||||
fromDenomination: ETH,
|
||||
})
|
||||
|
||||
return typeof contractExchangeRate === 'undefined'
|
||||
? (
|
||||
<span>
|
||||
{ this.context.t('noConversionRateAvailable') }
|
||||
</span>
|
||||
) : (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={hexWeiValue}
|
||||
type={PRIMARY}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderPrimaryTotalTextOverride () {
|
||||
const { tokenAmount, tokenSymbol, ethTransactionTotal } = this.props
|
||||
const tokensText = `${tokenAmount} ${tokenSymbol}`
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>{ `${tokensText} + ` }</span>
|
||||
<img
|
||||
src="/images/eth.svg"
|
||||
height="18"
|
||||
/>
|
||||
<span>{ ethTransactionTotal }</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
getSecondaryTotalTextOverride () {
|
||||
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
|
||||
|
||||
if (typeof contractExchangeRate === 'undefined') {
|
||||
return formatCurrency(fiatTransactionTotal, currentCurrency)
|
||||
} else {
|
||||
const fiatTransactionAmount = this.getFiatTransactionAmount()
|
||||
const fiatTotal = addFiat(fiatTransactionAmount, fiatTransactionTotal)
|
||||
const roundedFiatTotal = roundExponential(fiatTotal)
|
||||
return formatCurrency(roundedFiatTotal, currentCurrency)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
toAddress,
|
||||
tokenAddress,
|
||||
tokenSymbol,
|
||||
tokenAmount,
|
||||
...restProps
|
||||
} = this.props
|
||||
|
||||
const tokensText = `${tokenAmount} ${tokenSymbol}`
|
||||
|
||||
return (
|
||||
<ConfirmTransactionBase
|
||||
toAddress={toAddress}
|
||||
identiconAddress={tokenAddress}
|
||||
title={tokensText}
|
||||
subtitleComponent={this.renderSubtitleComponent()}
|
||||
primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
|
||||
secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
|
||||
{...restProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import ConfirmTokenTransactionBase from './confirm-token-transaction-base.component'
|
||||
import {
|
||||
tokenAmountAndToAddressSelector,
|
||||
contractExchangeRateSelector,
|
||||
} from '../../../selectors/confirm-transaction'
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { tokenAmount: ownTokenAmount } = ownProps
|
||||
const { confirmTransaction, metamask: { currentCurrency, conversionRate } } = state
|
||||
const {
|
||||
txData: { txParams: { to: tokenAddress } = {} } = {},
|
||||
tokenProps: { tokenSymbol } = {},
|
||||
fiatTransactionTotal,
|
||||
ethTransactionTotal,
|
||||
} = confirmTransaction
|
||||
|
||||
const { tokenAmount, toAddress } = tokenAmountAndToAddressSelector(state)
|
||||
const contractExchangeRate = contractExchangeRateSelector(state)
|
||||
|
||||
return {
|
||||
toAddress,
|
||||
tokenAddress,
|
||||
tokenAmount: typeof ownTokenAmount !== 'undefined' ? ownTokenAmount : tokenAmount,
|
||||
tokenSymbol,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
contractExchangeRate,
|
||||
fiatTransactionTotal,
|
||||
ethTransactionTotal,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ConfirmTokenTransactionBase)
|
|
@ -1,2 +0,0 @@
|
|||
export { default } from './confirm-token-transaction-base.container'
|
||||
export { default as ConfirmTokenTransactionBase } from './confirm-token-transaction-base.component'
|
|
@ -1,412 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
|
||||
import { isBalanceSufficient } from '../../send/send.utils'
|
||||
import { DEFAULT_ROUTE } from '../../../routes'
|
||||
import {
|
||||
INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||
TRANSACTION_ERROR_KEY,
|
||||
} from '../../../constants/error-keys'
|
||||
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
|
||||
import { PRIMARY, SECONDARY } from '../../../constants/common'
|
||||
|
||||
export default class ConfirmTransactionBase extends Component {
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
// react-router props
|
||||
match: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
// Redux props
|
||||
balance: PropTypes.string,
|
||||
cancelTransaction: PropTypes.func,
|
||||
cancelAllTransactions: PropTypes.func,
|
||||
clearConfirmTransaction: PropTypes.func,
|
||||
clearSend: PropTypes.func,
|
||||
conversionRate: PropTypes.number,
|
||||
currentCurrency: PropTypes.string,
|
||||
editTransaction: PropTypes.func,
|
||||
ethTransactionAmount: PropTypes.string,
|
||||
ethTransactionFee: PropTypes.string,
|
||||
ethTransactionTotal: PropTypes.string,
|
||||
fiatTransactionAmount: PropTypes.string,
|
||||
fiatTransactionFee: PropTypes.string,
|
||||
fiatTransactionTotal: PropTypes.string,
|
||||
fromAddress: PropTypes.string,
|
||||
fromName: PropTypes.string,
|
||||
hexTransactionAmount: PropTypes.string,
|
||||
hexTransactionFee: PropTypes.string,
|
||||
hexTransactionTotal: PropTypes.string,
|
||||
isTxReprice: PropTypes.bool,
|
||||
methodData: PropTypes.object,
|
||||
nonce: PropTypes.string,
|
||||
assetImage: PropTypes.string,
|
||||
sendTransaction: PropTypes.func,
|
||||
showCustomizeGasModal: PropTypes.func,
|
||||
showTransactionConfirmedModal: PropTypes.func,
|
||||
showRejectTransactionsConfirmationModal: PropTypes.func,
|
||||
toAddress: PropTypes.string,
|
||||
tokenData: PropTypes.object,
|
||||
tokenProps: PropTypes.object,
|
||||
toName: PropTypes.string,
|
||||
transactionStatus: PropTypes.string,
|
||||
txData: PropTypes.object,
|
||||
unapprovedTxCount: PropTypes.number,
|
||||
// Component props
|
||||
action: PropTypes.string,
|
||||
contentComponent: PropTypes.node,
|
||||
dataComponent: PropTypes.node,
|
||||
detailsComponent: PropTypes.node,
|
||||
errorKey: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
secondaryTotalTextOverride: PropTypes.string,
|
||||
hideData: PropTypes.bool,
|
||||
hideDetails: PropTypes.bool,
|
||||
hideSubtitle: PropTypes.bool,
|
||||
identiconAddress: PropTypes.string,
|
||||
onCancel: PropTypes.func,
|
||||
onEdit: PropTypes.func,
|
||||
onEditGas: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
subtitle: PropTypes.string,
|
||||
subtitleComponent: PropTypes.node,
|
||||
summaryComponent: PropTypes.node,
|
||||
title: PropTypes.string,
|
||||
titleComponent: PropTypes.node,
|
||||
valid: PropTypes.bool,
|
||||
warning: PropTypes.string,
|
||||
}
|
||||
|
||||
state = {
|
||||
submitting: false,
|
||||
submitError: null,
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
const {
|
||||
transactionStatus,
|
||||
showTransactionConfirmedModal,
|
||||
history,
|
||||
clearConfirmTransaction,
|
||||
} = this.props
|
||||
|
||||
if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) {
|
||||
showTransactionConfirmedModal({
|
||||
onSubmit: () => {
|
||||
clearConfirmTransaction()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
},
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
getErrorKey () {
|
||||
const {
|
||||
balance,
|
||||
conversionRate,
|
||||
hexTransactionFee,
|
||||
txData: {
|
||||
simulationFails,
|
||||
txParams: {
|
||||
value: amount,
|
||||
} = {},
|
||||
} = {},
|
||||
} = this.props
|
||||
|
||||
const insufficientBalance = balance && !isBalanceSufficient({
|
||||
amount,
|
||||
gasTotal: hexTransactionFee || '0x0',
|
||||
balance,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
if (insufficientBalance) {
|
||||
return {
|
||||
valid: false,
|
||||
errorKey: INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||
}
|
||||
}
|
||||
|
||||
if (simulationFails) {
|
||||
return {
|
||||
valid: true,
|
||||
errorKey: TRANSACTION_ERROR_KEY,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
handleEditGas () {
|
||||
const { onEditGas, showCustomizeGasModal } = this.props
|
||||
|
||||
if (onEditGas) {
|
||||
onEditGas()
|
||||
} else {
|
||||
showCustomizeGasModal()
|
||||
}
|
||||
}
|
||||
|
||||
renderDetails () {
|
||||
const {
|
||||
detailsComponent,
|
||||
primaryTotalTextOverride,
|
||||
secondaryTotalTextOverride,
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
hideDetails,
|
||||
} = this.props
|
||||
|
||||
if (hideDetails) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
detailsComponent || (
|
||||
<div className="confirm-page-container-content__details">
|
||||
<div className="confirm-page-container-content__gas-fee">
|
||||
<ConfirmDetailRow
|
||||
label="Gas Fee"
|
||||
value={hexTransactionFee}
|
||||
headerText="Edit"
|
||||
headerTextClassName="confirm-detail-row__header-text--edit"
|
||||
onHeaderClick={() => this.handleEditGas()}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ConfirmDetailRow
|
||||
label="Total"
|
||||
value={hexTransactionTotal}
|
||||
primaryText={primaryTotalTextOverride}
|
||||
secondaryText={secondaryTotalTextOverride}
|
||||
headerText="Amount + Gas Fee"
|
||||
headerTextClassName="confirm-detail-row__header-text--total"
|
||||
primaryValueTextColor="#2f9ae0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
renderData () {
|
||||
const { t } = this.context
|
||||
const {
|
||||
txData: {
|
||||
txParams: {
|
||||
data,
|
||||
} = {},
|
||||
} = {},
|
||||
methodData: {
|
||||
name,
|
||||
params,
|
||||
} = {},
|
||||
hideData,
|
||||
dataComponent,
|
||||
} = this.props
|
||||
|
||||
if (hideData) {
|
||||
return null
|
||||
}
|
||||
|
||||
return dataComponent || (
|
||||
<div className="confirm-page-container-content__data">
|
||||
<div className="confirm-page-container-content__data-box-label">
|
||||
{`${t('functionType')}:`}
|
||||
<span className="confirm-page-container-content__function-type">
|
||||
{ name || t('notFound') }
|
||||
</span>
|
||||
</div>
|
||||
{
|
||||
params && (
|
||||
<div className="confirm-page-container-content__data-box">
|
||||
<div className="confirm-page-container-content__data-field-label">
|
||||
{ `${t('parameters')}:` }
|
||||
</div>
|
||||
<div>
|
||||
<pre>{ JSON.stringify(params, null, 2) }</pre>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="confirm-page-container-content__data-box-label">
|
||||
{`${t('hexData')}:`}
|
||||
</div>
|
||||
<div className="confirm-page-container-content__data-box">
|
||||
{ data }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleEdit () {
|
||||
const { txData, tokenData, tokenProps, onEdit } = this.props
|
||||
onEdit({ txData, tokenData, tokenProps })
|
||||
}
|
||||
|
||||
handleCancelAll () {
|
||||
const {
|
||||
cancelAllTransactions,
|
||||
clearConfirmTransaction,
|
||||
history,
|
||||
showRejectTransactionsConfirmationModal,
|
||||
unapprovedTxCount,
|
||||
} = this.props
|
||||
|
||||
showRejectTransactionsConfirmationModal({
|
||||
unapprovedTxCount,
|
||||
async onSubmit () {
|
||||
await cancelAllTransactions()
|
||||
clearConfirmTransaction()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
handleCancel () {
|
||||
const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction } = this.props
|
||||
|
||||
if (onCancel) {
|
||||
onCancel(txData)
|
||||
} else {
|
||||
cancelTransaction(txData)
|
||||
.then(() => {
|
||||
clearConfirmTransaction()
|
||||
history.push(DEFAULT_ROUTE)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit () {
|
||||
const { sendTransaction, clearConfirmTransaction, txData, history, onSubmit } = this.props
|
||||
const { submitting } = this.state
|
||||
|
||||
if (submitting) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({ submitting: true, submitError: null })
|
||||
|
||||
if (onSubmit) {
|
||||
Promise.resolve(onSubmit(txData))
|
||||
.then(this.setState({ submitting: false }))
|
||||
} else {
|
||||
sendTransaction(txData)
|
||||
.then(() => {
|
||||
clearConfirmTransaction()
|
||||
this.setState({ submitting: false })
|
||||
history.push(DEFAULT_ROUTE)
|
||||
})
|
||||
.catch(error => {
|
||||
this.setState({ submitting: false, submitError: error.message })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
renderTitleComponent () {
|
||||
const { title, titleComponent, hexTransactionAmount } = this.props
|
||||
|
||||
// Title string passed in by props takes priority
|
||||
if (title) {
|
||||
return null
|
||||
}
|
||||
|
||||
return titleComponent || (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={hexTransactionAmount}
|
||||
type={PRIMARY}
|
||||
showEthLogo
|
||||
ethLogoHeight="26"
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderSubtitleComponent () {
|
||||
const { subtitle, subtitleComponent, hexTransactionAmount } = this.props
|
||||
|
||||
// Subtitle string passed in by props takes priority
|
||||
if (subtitle) {
|
||||
return null
|
||||
}
|
||||
|
||||
return subtitleComponent || (
|
||||
<UserPreferencedCurrencyDisplay
|
||||
value={hexTransactionAmount}
|
||||
type={SECONDARY}
|
||||
showEthLogo
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
isTxReprice,
|
||||
fromName,
|
||||
fromAddress,
|
||||
toName,
|
||||
toAddress,
|
||||
methodData,
|
||||
valid: propsValid = true,
|
||||
errorMessage,
|
||||
errorKey: propsErrorKey,
|
||||
action,
|
||||
title,
|
||||
subtitle,
|
||||
hideSubtitle,
|
||||
identiconAddress,
|
||||
summaryComponent,
|
||||
contentComponent,
|
||||
onEdit,
|
||||
nonce,
|
||||
assetImage,
|
||||
warning,
|
||||
unapprovedTxCount,
|
||||
} = this.props
|
||||
const { submitting, submitError } = this.state
|
||||
|
||||
const { name } = methodData
|
||||
const { valid, errorKey } = this.getErrorKey()
|
||||
|
||||
return (
|
||||
<ConfirmPageContainer
|
||||
fromName={fromName}
|
||||
fromAddress={fromAddress}
|
||||
toName={toName}
|
||||
toAddress={toAddress}
|
||||
showEdit={onEdit && !isTxReprice}
|
||||
action={action || name || this.context.t('unknownFunction')}
|
||||
title={title}
|
||||
titleComponent={this.renderTitleComponent()}
|
||||
subtitle={subtitle}
|
||||
subtitleComponent={this.renderSubtitleComponent()}
|
||||
hideSubtitle={hideSubtitle}
|
||||
summaryComponent={summaryComponent}
|
||||
detailsComponent={this.renderDetails()}
|
||||
dataComponent={this.renderData()}
|
||||
contentComponent={contentComponent}
|
||||
nonce={nonce}
|
||||
unapprovedTxCount={unapprovedTxCount}
|
||||
assetImage={assetImage}
|
||||
identiconAddress={identiconAddress}
|
||||
errorMessage={errorMessage || submitError}
|
||||
errorKey={propsErrorKey || errorKey}
|
||||
warning={warning}
|
||||
disabled={!propsValid || !valid || submitting}
|
||||
onEdit={() => this.handleEdit()}
|
||||
onCancelAll={() => this.handleCancelAll()}
|
||||
onCancel={() => this.handleCancel()}
|
||||
onSubmit={() => this.handleSubmit()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import R from 'ramda'
|
||||
import contractMap from 'eth-contract-metadata'
|
||||
import ConfirmTransactionBase from './confirm-transaction-base.component'
|
||||
import {
|
||||
clearConfirmTransaction,
|
||||
updateGasAndCalculate,
|
||||
} from '../../../ducks/confirm-transaction.duck'
|
||||
import { clearSend, cancelTx, cancelTxs, updateAndApproveTx, showModal } from '../../../actions'
|
||||
import {
|
||||
INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||
GAS_LIMIT_TOO_LOW_ERROR_KEY,
|
||||
} from '../../../constants/error-keys'
|
||||
import { getHexGasTotal } from '../../../helpers/confirm-transaction/util'
|
||||
import { isBalanceSufficient } from '../../send/send.utils'
|
||||
import { conversionGreaterThan } from '../../../conversion-util'
|
||||
import { MIN_GAS_LIMIT_DEC } from '../../send/send.constants'
|
||||
import { addressSlicer, valuesFor } from '../../../util'
|
||||
import { getMetaMaskAccounts } from '../../../selectors'
|
||||
|
||||
const casedContractMap = Object.keys(contractMap).reduce((acc, base) => {
|
||||
return {
|
||||
...acc,
|
||||
[base.toLowerCase()]: contractMap[base],
|
||||
}
|
||||
}, {})
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const { toAddress: propsToAddress } = props
|
||||
const { confirmTransaction, metamask } = state
|
||||
const {
|
||||
ethTransactionAmount,
|
||||
ethTransactionFee,
|
||||
ethTransactionTotal,
|
||||
fiatTransactionAmount,
|
||||
fiatTransactionFee,
|
||||
fiatTransactionTotal,
|
||||
hexTransactionAmount,
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
tokenData,
|
||||
methodData,
|
||||
txData,
|
||||
tokenProps,
|
||||
nonce,
|
||||
} = confirmTransaction
|
||||
const { txParams = {}, lastGasPrice, id: transactionId } = txData
|
||||
const { from: fromAddress, to: txParamsToAddress } = txParams
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const {
|
||||
conversionRate,
|
||||
identities,
|
||||
currentCurrency,
|
||||
selectedAddress,
|
||||
selectedAddressTxList,
|
||||
assetImages,
|
||||
network,
|
||||
unapprovedTxs,
|
||||
} = metamask
|
||||
const assetImage = assetImages[txParamsToAddress]
|
||||
const { balance } = accounts[selectedAddress]
|
||||
const { name: fromName } = identities[selectedAddress]
|
||||
const toAddress = propsToAddress || txParamsToAddress
|
||||
const toName = identities[toAddress]
|
||||
? identities[toAddress].name
|
||||
: casedContractMap[toAddress] ? casedContractMap[toAddress].name : addressSlicer(toAddress)
|
||||
|
||||
const isTxReprice = Boolean(lastGasPrice)
|
||||
|
||||
const transaction = R.find(({ id }) => id === transactionId)(selectedAddressTxList)
|
||||
const transactionStatus = transaction ? transaction.status : ''
|
||||
|
||||
const currentNetworkUnapprovedTxs = R.filter(
|
||||
({ metamaskNetworkId }) => metamaskNetworkId === network,
|
||||
valuesFor(unapprovedTxs),
|
||||
)
|
||||
const unapprovedTxCount = currentNetworkUnapprovedTxs.length
|
||||
|
||||
return {
|
||||
balance,
|
||||
fromAddress,
|
||||
fromName,
|
||||
toAddress,
|
||||
toName,
|
||||
ethTransactionAmount,
|
||||
ethTransactionFee,
|
||||
ethTransactionTotal,
|
||||
fiatTransactionAmount,
|
||||
fiatTransactionFee,
|
||||
fiatTransactionTotal,
|
||||
hexTransactionAmount,
|
||||
hexTransactionFee,
|
||||
hexTransactionTotal,
|
||||
txData,
|
||||
tokenData,
|
||||
methodData,
|
||||
tokenProps,
|
||||
isTxReprice,
|
||||
currentCurrency,
|
||||
conversionRate,
|
||||
transactionStatus,
|
||||
nonce,
|
||||
assetImage,
|
||||
unapprovedTxs,
|
||||
unapprovedTxCount,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
|
||||
clearSend: () => dispatch(clearSend()),
|
||||
showTransactionConfirmedModal: ({ onSubmit }) => {
|
||||
return dispatch(showModal({ name: 'TRANSACTION_CONFIRMED', onSubmit }))
|
||||
},
|
||||
showCustomizeGasModal: ({ txData, onSubmit, validate }) => {
|
||||
return dispatch(showModal({ name: 'CONFIRM_CUSTOMIZE_GAS', txData, onSubmit, validate }))
|
||||
},
|
||||
updateGasAndCalculate: ({ gasLimit, gasPrice }) => {
|
||||
return dispatch(updateGasAndCalculate({ gasLimit, gasPrice }))
|
||||
},
|
||||
showRejectTransactionsConfirmationModal: ({ onSubmit, unapprovedTxCount }) => {
|
||||
return dispatch(showModal({ name: 'REJECT_TRANSACTIONS', onSubmit, unapprovedTxCount }))
|
||||
},
|
||||
cancelTransaction: ({ id }) => dispatch(cancelTx({ id })),
|
||||
cancelAllTransactions: (txList) => dispatch(cancelTxs(txList)),
|
||||
sendTransaction: txData => dispatch(updateAndApproveTx(txData)),
|
||||
}
|
||||
}
|
||||
|
||||
const getValidateEditGas = ({ balance, conversionRate, txData }) => {
|
||||
const { txParams: { value: amount } = {} } = txData
|
||||
|
||||
return ({ gasLimit, gasPrice }) => {
|
||||
const gasTotal = getHexGasTotal({ gasLimit, gasPrice })
|
||||
const hasSufficientBalance = isBalanceSufficient({
|
||||
amount,
|
||||
gasTotal,
|
||||
balance,
|
||||
conversionRate,
|
||||
})
|
||||
|
||||
if (!hasSufficientBalance) {
|
||||
return {
|
||||
valid: false,
|
||||
errorKey: INSUFFICIENT_FUNDS_ERROR_KEY,
|
||||
}
|
||||
}
|
||||
|
||||
const gasLimitTooLow = gasLimit && conversionGreaterThan(
|
||||
{
|
||||
value: MIN_GAS_LIMIT_DEC,
|
||||
fromNumericBase: 'dec',
|
||||
conversionRate,
|
||||
},
|
||||
{
|
||||
value: gasLimit,
|
||||
fromNumericBase: 'hex',
|
||||
},
|
||||
)
|
||||
|
||||
if (gasLimitTooLow) {
|
||||
return {
|
||||
valid: false,
|
||||
errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { balance, conversionRate, txData, unapprovedTxs } = stateProps
|
||||
const {
|
||||
cancelAllTransactions: dispatchCancelAllTransactions,
|
||||
showCustomizeGasModal: dispatchShowCustomizeGasModal,
|
||||
updateGasAndCalculate: dispatchUpdateGasAndCalculate,
|
||||
...otherDispatchProps
|
||||
} = dispatchProps
|
||||
|
||||
const validateEditGas = getValidateEditGas({ balance, conversionRate, txData })
|
||||
|
||||
return {
|
||||
...stateProps,
|
||||
...otherDispatchProps,
|
||||
...ownProps,
|
||||
showCustomizeGasModal: () => dispatchShowCustomizeGasModal({
|
||||
txData,
|
||||
onSubmit: txData => dispatchUpdateGasAndCalculate(txData),
|
||||
validate: validateEditGas,
|
||||
}),
|
||||
cancelAllTransactions: () => dispatchCancelAllTransactions(valuesFor(unapprovedTxs)),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps, mergeProps)
|
||||
)(ConfirmTransactionBase)
|
|
@ -1 +0,0 @@
|
|||
export { default } from './confirm-transaction-base.container'
|
|
@ -1,92 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import Loading from '../../loading-screen'
|
||||
import {
|
||||
CONFIRM_TRANSACTION_ROUTE,
|
||||
CONFIRM_DEPLOY_CONTRACT_PATH,
|
||||
CONFIRM_SEND_ETHER_PATH,
|
||||
CONFIRM_SEND_TOKEN_PATH,
|
||||
CONFIRM_APPROVE_PATH,
|
||||
CONFIRM_TRANSFER_FROM_PATH,
|
||||
CONFIRM_TOKEN_METHOD_PATH,
|
||||
SIGNATURE_REQUEST_PATH,
|
||||
} from '../../../routes'
|
||||
import { isConfirmDeployContract } from '../../../helpers/transactions.util'
|
||||
import {
|
||||
TOKEN_METHOD_TRANSFER,
|
||||
TOKEN_METHOD_APPROVE,
|
||||
TOKEN_METHOD_TRANSFER_FROM,
|
||||
} from '../../../constants/transactions'
|
||||
|
||||
export default class ConfirmTransactionSwitch extends Component {
|
||||
static propTypes = {
|
||||
txData: PropTypes.object,
|
||||
methodData: PropTypes.object,
|
||||
fetchingData: PropTypes.bool,
|
||||
isEtherTransaction: PropTypes.bool,
|
||||
}
|
||||
|
||||
redirectToTransaction () {
|
||||
const {
|
||||
txData,
|
||||
methodData: { name },
|
||||
fetchingData,
|
||||
isEtherTransaction,
|
||||
} = this.props
|
||||
const { id, txParams: { data } = {} } = txData
|
||||
|
||||
if (isConfirmDeployContract(txData)) {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_DEPLOY_CONTRACT_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
|
||||
if (fetchingData) {
|
||||
return <Loading />
|
||||
}
|
||||
|
||||
if (isEtherTransaction) {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
|
||||
if (data) {
|
||||
const methodName = name && name.toLowerCase()
|
||||
|
||||
switch (methodName) {
|
||||
case TOKEN_METHOD_TRANSFER: {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_TOKEN_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
case TOKEN_METHOD_APPROVE: {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_APPROVE_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
case TOKEN_METHOD_TRANSFER_FROM: {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
default: {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TOKEN_METHOD_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SEND_ETHER_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
|
||||
render () {
|
||||
const { txData } = this.props
|
||||
|
||||
if (txData.txParams) {
|
||||
return this.redirectToTransaction()
|
||||
} else if (txData.msgParams) {
|
||||
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
|
||||
return <Redirect to={{ pathname }} />
|
||||
}
|
||||
|
||||
return <Loading />
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import ConfirmTransactionSwitch from './confirm-transaction-switch.component'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const {
|
||||
confirmTransaction: {
|
||||
txData,
|
||||
methodData,
|
||||
fetchingData,
|
||||
toSmartContract,
|
||||
},
|
||||
} = state
|
||||
|
||||
return {
|
||||
txData,
|
||||
methodData,
|
||||
fetchingData,
|
||||
isEtherTransaction: !toSmartContract,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ConfirmTransactionSwitch)
|
|
@ -1,4 +0,0 @@
|
|||
export function isConfirmDeployContract (txData = {}) {
|
||||
const { txParams = {} } = txData
|
||||
return !txParams.to
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
import ConfirmTransactionSwitch from './confirm-transaction-switch.container'
|
||||
module.exports = ConfirmTransactionSwitch
|
|
@ -1,157 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Switch, Route } from 'react-router-dom'
|
||||
import Loading from '../../loading-screen'
|
||||
import ConfirmTransactionSwitch from '../confirm-transaction-switch'
|
||||
import ConfirmTransactionBase from '../confirm-transaction-base'
|
||||
import ConfirmSendEther from '../confirm-send-ether'
|
||||
import ConfirmSendToken from '../confirm-send-token'
|
||||
import ConfirmDeployContract from '../confirm-deploy-contract'
|
||||
import ConfirmApprove from '../confirm-approve'
|
||||
import ConfirmTokenTransactionBase from '../confirm-token-transaction-base'
|
||||
import ConfTx from '../../../conf-tx'
|
||||
import {
|
||||
DEFAULT_ROUTE,
|
||||
CONFIRM_TRANSACTION_ROUTE,
|
||||
CONFIRM_DEPLOY_CONTRACT_PATH,
|
||||
CONFIRM_SEND_ETHER_PATH,
|
||||
CONFIRM_SEND_TOKEN_PATH,
|
||||
CONFIRM_APPROVE_PATH,
|
||||
CONFIRM_TRANSFER_FROM_PATH,
|
||||
CONFIRM_TOKEN_METHOD_PATH,
|
||||
SIGNATURE_REQUEST_PATH,
|
||||
} from '../../../routes'
|
||||
|
||||
export default class ConfirmTransaction extends Component {
|
||||
static propTypes = {
|
||||
history: PropTypes.object.isRequired,
|
||||
totalUnapprovedCount: PropTypes.number.isRequired,
|
||||
match: PropTypes.object,
|
||||
send: PropTypes.object,
|
||||
unconfirmedTransactions: PropTypes.array,
|
||||
setTransactionToConfirm: PropTypes.func,
|
||||
confirmTransaction: PropTypes.object,
|
||||
clearConfirmTransaction: PropTypes.func,
|
||||
}
|
||||
|
||||
getParamsTransactionId () {
|
||||
const { match: { params: { id } = {} } } = this.props
|
||||
return id || null
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const {
|
||||
totalUnapprovedCount = 0,
|
||||
send = {},
|
||||
history,
|
||||
confirmTransaction: { txData: { id: transactionId } = {} },
|
||||
} = this.props
|
||||
|
||||
if (!totalUnapprovedCount && !send.to) {
|
||||
history.replace(DEFAULT_ROUTE)
|
||||
return
|
||||
}
|
||||
|
||||
if (!transactionId) {
|
||||
this.setTransactionToConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
const {
|
||||
setTransactionToConfirm,
|
||||
confirmTransaction: { txData: { id: transactionId } = {} },
|
||||
clearConfirmTransaction,
|
||||
} = this.props
|
||||
const paramsTransactionId = this.getParamsTransactionId()
|
||||
|
||||
if (paramsTransactionId && transactionId && paramsTransactionId !== transactionId + '') {
|
||||
clearConfirmTransaction()
|
||||
setTransactionToConfirm(paramsTransactionId)
|
||||
return
|
||||
}
|
||||
|
||||
if (!transactionId) {
|
||||
this.setTransactionToConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
setTransactionToConfirm () {
|
||||
const {
|
||||
history,
|
||||
unconfirmedTransactions,
|
||||
setTransactionToConfirm,
|
||||
} = this.props
|
||||
const paramsTransactionId = this.getParamsTransactionId()
|
||||
|
||||
if (paramsTransactionId) {
|
||||
// Check to make sure params ID is valid
|
||||
const tx = unconfirmedTransactions.find(({ id }) => id + '' === paramsTransactionId)
|
||||
|
||||
if (!tx) {
|
||||
history.replace(DEFAULT_ROUTE)
|
||||
} else {
|
||||
setTransactionToConfirm(paramsTransactionId)
|
||||
}
|
||||
} else if (unconfirmedTransactions.length) {
|
||||
const totalUnconfirmed = unconfirmedTransactions.length
|
||||
const transaction = unconfirmedTransactions[totalUnconfirmed - 1]
|
||||
const { id: transactionId, loadingDefaults } = transaction
|
||||
|
||||
if (!loadingDefaults) {
|
||||
setTransactionToConfirm(transactionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { confirmTransaction: { txData: { id } } = {} } = this.props
|
||||
const paramsTransactionId = this.getParamsTransactionId()
|
||||
|
||||
// Show routes when state.confirmTransaction has been set and when either the ID in the params
|
||||
// isn't specified or is specified and matches the ID in state.confirmTransaction in order to
|
||||
// support URLs of /confirm-transaction or /confirm-transaction/<transactionId>
|
||||
return id && (!paramsTransactionId || paramsTransactionId === id + '')
|
||||
? (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_DEPLOY_CONTRACT_PATH}`}
|
||||
component={ConfirmDeployContract}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TOKEN_METHOD_PATH}`}
|
||||
component={ConfirmTransactionBase}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_ETHER_PATH}`}
|
||||
component={ConfirmSendEther}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_SEND_TOKEN_PATH}`}
|
||||
component={ConfirmSendToken}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_APPROVE_PATH}`}
|
||||
component={ConfirmApprove}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${CONFIRM_TRANSFER_FROM_PATH}`}
|
||||
component={ConfirmTokenTransactionBase}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`}
|
||||
component={ConfTx}
|
||||
/>
|
||||
<Route path="*" component={ConfirmTransactionSwitch} />
|
||||
</Switch>
|
||||
)
|
||||
: <Loading />
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { compose } from 'recompose'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import {
|
||||
setTransactionToConfirm,
|
||||
clearConfirmTransaction,
|
||||
} from '../../../ducks/confirm-transaction.duck'
|
||||
import ConfirmTransaction from './confirm-transaction.component'
|
||||
import { getTotalUnapprovedCount } from '../../../selectors'
|
||||
import { unconfirmedTransactionsListSelector } from '../../../selectors/confirm-transaction'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { metamask: { send }, confirmTransaction } = state
|
||||
|
||||
return {
|
||||
totalUnapprovedCount: getTotalUnapprovedCount(state),
|
||||
send,
|
||||
confirmTransaction,
|
||||
unconfirmedTransactions: unconfirmedTransactionsListSelector(state),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
setTransactionToConfirm: transactionId => dispatch(setTransactionToConfirm(transactionId)),
|
||||
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
)(ConfirmTransaction)
|
|
@ -1,2 +0,0 @@
|
|||
import ConfirmTransaction from './confirm-transaction.container'
|
||||
module.exports = ConfirmTransaction
|
|
@ -1,205 +0,0 @@
|
|||
const { Component } = require('react')
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const ethNetProps = require('eth-net-props')
|
||||
const Select = require('react-select').default
|
||||
import Button from '../../../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 h('div', [
|
||||
h('h3.hw-connect__hdPath__title', {}, this.context.t('selectHdPath')),
|
||||
h('p.hw-connect__msg', {}, this.context.t('selectPathHelp')),
|
||||
h('div.hw-connect__hdPath', [
|
||||
h(Select, {
|
||||
className: 'hw-connect__hdPath__select',
|
||||
name: 'hd-path-select',
|
||||
clearable: false,
|
||||
value: selectedPath,
|
||||
options,
|
||||
onChange: (opt) => {
|
||||
onPathChange(opt.value)
|
||||
},
|
||||
}),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
capitalizeDevice (device) {
|
||||
return device.slice(0, 1).toUpperCase() + device.slice(1)
|
||||
}
|
||||
|
||||
renderHeader () {
|
||||
const { device } = this.props
|
||||
return (
|
||||
h('div.hw-connect', [
|
||||
|
||||
h('h3.hw-connect__unlock-title', {}, `${this.context.t('unlock')} ${this.capitalizeDevice(device)}`),
|
||||
|
||||
device.toLowerCase() === 'ledger' ? this.renderHdPathSelector() : null,
|
||||
|
||||
h('h3.hw-connect__hdPath__title', {}, this.context.t('selectAnAccount')),
|
||||
h('p.hw-connect__msg', {}, this.context.t('selectAnAccountHelp')),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
return h('div.hw-account-list', [
|
||||
this.props.accounts.map((a, i) => {
|
||||
|
||||
return h('div.hw-account-list__item', { key: a.address }, [
|
||||
h('div.hw-account-list__item__radio', [
|
||||
h('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(),
|
||||
}),
|
||||
h(
|
||||
'label.hw-account-list__item__label',
|
||||
{
|
||||
htmlFor: `address-${i}`,
|
||||
},
|
||||
[
|
||||
h('span.hw-account-list__item__index', a.index + 1),
|
||||
`${a.address.slice(0, 4)}...${a.address.slice(-4)}`,
|
||||
h('span.hw-account-list__item__balance', `${a.balance}`),
|
||||
]),
|
||||
]),
|
||||
h(
|
||||
'a.hw-account-list__item__link',
|
||||
{
|
||||
href: ethNetProps.explorerLinks.getExplorerAccountLinkFor(a.address, this.props.network),
|
||||
target: '_blank',
|
||||
title: this.context.t('etherscanView'),
|
||||
},
|
||||
h('img', { src: 'images/popout.svg' })
|
||||
),
|
||||
])
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
renderPagination () {
|
||||
return h('div.hw-list-pagination', [
|
||||
h(
|
||||
'button.hw-list-pagination__button',
|
||||
{
|
||||
onClick: this.goToPreviousPage,
|
||||
},
|
||||
`< ${this.context.t('prev')}`
|
||||
),
|
||||
|
||||
h(
|
||||
'button.hw-list-pagination__button',
|
||||
{
|
||||
onClick: this.goToNextPage,
|
||||
},
|
||||
`${this.context.t('next')} >`
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
const disabled = this.props.selectedAccount === null
|
||||
const buttonProps = {}
|
||||
if (disabled) {
|
||||
buttonProps.disabled = true
|
||||
}
|
||||
|
||||
return h('div.new-account-connect-form__buttons', {}, [
|
||||
h(Button, {
|
||||
type: 'default',
|
||||
large: true,
|
||||
className: 'new-account-connect-form__button',
|
||||
onClick: this.props.onCancel.bind(this),
|
||||
}, [this.context.t('cancel')]),
|
||||
|
||||
h(Button, {
|
||||
type: 'primary',
|
||||
large: true,
|
||||
className: 'new-account-connect-form__button unlock',
|
||||
disabled,
|
||||
onClick: this.props.onUnlockAccount.bind(this, this.props.device),
|
||||
}, [this.context.t('unlock')]),
|
||||
])
|
||||
}
|
||||
|
||||
renderForgetDevice () {
|
||||
return h('div.hw-forget-device-container', {}, [
|
||||
h('a', {
|
||||
onClick: this.props.onForgetDevice.bind(this, this.props.device),
|
||||
}, this.context.t('forgetDevice')),
|
||||
])
|
||||
}
|
||||
|
||||
render () {
|
||||
return h('div.new-account-connect-form.account-list', {}, [
|
||||
this.renderHeader(),
|
||||
this.renderAccounts(),
|
||||
this.renderPagination(),
|
||||
this.renderButtons(),
|
||||
this.renderForgetDevice(),
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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
|
|
@ -1,197 +0,0 @@
|
|||
const { Component } = require('react')
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
import Button from '../../../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 h(
|
||||
`button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`,
|
||||
{ onClick: _ => this.setState({selectedDevice: 'trezor'}) },
|
||||
h('img.hw-connect__btn__img', {
|
||||
src: 'images/trezor-logo.svg',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
renderConnectToLedgerButton () {
|
||||
return h(
|
||||
`button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`,
|
||||
{ onClick: _ => this.setState({selectedDevice: 'ledger'}) },
|
||||
h('img.hw-connect__btn__img', {
|
||||
src: 'images/ledger-logo.svg',
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
renderButtons () {
|
||||
return (
|
||||
h('div', {}, [
|
||||
h('div.hw-connect__btn-wrapper', {}, [
|
||||
this.renderConnectToLedgerButton(),
|
||||
this.renderConnectToTrezorButton(),
|
||||
]),
|
||||
h(
|
||||
`button.hw-connect__connect-btn${!this.state.selectedDevice ? '.disabled' : ''}`,
|
||||
{ onClick: this.connect },
|
||||
this.context.t('connect')
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderUnsupportedBrowser () {
|
||||
return (
|
||||
h('div.new-account-connect-form.unsupported-browser', {}, [
|
||||
h('div.hw-connect', [
|
||||
h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')),
|
||||
h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')),
|
||||
]),
|
||||
h(Button, {
|
||||
type: 'primary',
|
||||
large: true,
|
||||
onClick: () => global.platform.openWindow({
|
||||
url: 'https://google.com/chrome',
|
||||
}),
|
||||
},
|
||||
this.context.t('downloadGoogleChrome')
|
||||
),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderHeader () {
|
||||
return (
|
||||
h('div.hw-connect__header', {}, [
|
||||
h('h3.hw-connect__header__title', {}, this.context.t(`hardwareWallets`)),
|
||||
h('p.hw-connect__header__msg', {}, this.context.t(`hardwareWalletsMsg`)),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
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 = this.context.t('orderOneHere')
|
||||
const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger)
|
||||
|
||||
return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }})
|
||||
}
|
||||
|
||||
renderTrezorAffiliateLink () {
|
||||
return h('div.hw-connect__get-hw', {}, [
|
||||
h('p.hw-connect__get-hw__msg', {}, this.context.t(`dontHaveAHardwareWallet`)),
|
||||
this.getAffiliateLinks(),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
scrollToTutorial = (e) => {
|
||||
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
|
||||
}
|
||||
|
||||
renderLearnMore () {
|
||||
return (
|
||||
h('p.hw-connect__learn-more', {
|
||||
onClick: this.scrollToTutorial,
|
||||
}, [
|
||||
this.context.t('learnMore'),
|
||||
h('img.hw-connect__learn-more__arrow', { src: 'images/caret-right.svg'}),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderTutorialSteps () {
|
||||
const steps = [
|
||||
{
|
||||
asset: 'hardware-wallet-step-1',
|
||||
dimensions: {width: '225px', height: '75px'},
|
||||
},
|
||||
{
|
||||
asset: 'hardware-wallet-step-2',
|
||||
dimensions: {width: '300px', height: '100px'},
|
||||
},
|
||||
{
|
||||
asset: 'hardware-wallet-step-3',
|
||||
dimensions: {width: '120px', height: '90px'},
|
||||
},
|
||||
]
|
||||
|
||||
return h('.hw-tutorial', {
|
||||
ref: node => { this.referenceNode = node },
|
||||
},
|
||||
steps.map((step, i) => (
|
||||
h('div.hw-connect', {}, [
|
||||
h('h3.hw-connect__title', {}, this.context.t(`step${i + 1}HardwareWallet`)),
|
||||
h('p.hw-connect__msg', {}, this.context.t(`step${i + 1}HardwareWalletMsg`)),
|
||||
h('img.hw-connect__step-asset', { src: `images/${step.asset}.svg`, ...step.dimensions }),
|
||||
])
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
renderFooter () {
|
||||
return (
|
||||
h('div.hw-connect__footer', {}, [
|
||||
h('h3.hw-connect__footer__title', {}, this.context.t(`readyToConnect`)),
|
||||
this.renderButtons(),
|
||||
h('p.hw-connect__footer__msg', {}, [
|
||||
this.context.t(`havingTroubleConnecting`),
|
||||
h('a.hw-connect__footer__link', {
|
||||
href: 'https://support.metamask.io/',
|
||||
target: '_blank',
|
||||
}, this.context.t('getHelp')),
|
||||
]),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
renderConnectScreen () {
|
||||
return (
|
||||
h('div.new-account-connect-form', {}, [
|
||||
this.renderHeader(),
|
||||
this.renderButtons(),
|
||||
this.renderTrezorAffiliateLink(),
|
||||
this.renderLearnMore(),
|
||||
this.renderTutorialSteps(),
|
||||
this.renderFooter(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.props.browserSupported) {
|
||||
return this.renderConnectScreen()
|
||||
}
|
||||
return this.renderUnsupportedBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
ConnectScreen.propTypes = {
|
||||
connectToHardwareWallet: PropTypes.func.isRequired,
|
||||
browserSupported: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
ConnectScreen.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = ConnectScreen
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
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 { getMetaMaskAccounts } = require('../../../../selectors')
|
||||
const ConnectScreen = require('./connect-screen')
|
||||
const AccountList = require('./account-list')
|
||||
const { DEFAULT_ROUTE } = require('../../../../routes')
|
||||
const { formatBalance } = require('../../../../util')
|
||||
const { getPlatform } = require('../../../../../../app/scripts/lib/util')
|
||||
const { PLATFORM_FIREFOX } = require('../../../../../../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: 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, 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: this.context.t('accountSelectionRequired') })
|
||||
}
|
||||
|
||||
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
|
||||
? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error)
|
||||
: null
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
if (!this.state.accounts.length) {
|
||||
return h(ConnectScreen, {
|
||||
connectToHardwareWallet: this.connectToHardwareWallet,
|
||||
browserSupported: this.state.browserSupported,
|
||||
})
|
||||
}
|
||||
|
||||
return h(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 h('div', [
|
||||
this.renderError(),
|
||||
this.renderContent(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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 = {} },
|
||||
} = state
|
||||
const accounts = getMetaMaskAccounts(state)
|
||||
const numberOfExistingAccounts = Object.keys(identities).length
|
||||
const {
|
||||
appState: { defaultHdPaths },
|
||||
} = state
|
||||
|
||||
return {
|
||||
network,
|
||||
accounts,
|
||||
address: selectedAddress,
|
||||
numberOfExistingAccounts,
|
||||
defaultHdPaths,
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
showAlert: (msg) => dispatch(actions.showAlert(msg)),
|
||||
hideAlert: () => dispatch(actions.hideAlert()),
|
||||
}
|
||||
}
|
||||
|
||||
ConnectHardwareForm.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(
|
||||
ConnectHardwareForm
|
||||
)
|
|
@ -1,96 +0,0 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const PropTypes = require('prop-types')
|
||||
const connect = require('react-redux').connect
|
||||
import Select from 'react-select'
|
||||
|
||||
// Subviews
|
||||
const JsonImportView = require('./json.js')
|
||||
const PrivateKeyImportView = require('./private-key.js')
|
||||
|
||||
|
||||
AccountImportSubview.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect()(AccountImportSubview)
|
||||
|
||||
|
||||
inherits(AccountImportSubview, Component)
|
||||
function AccountImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.getMenuItemTexts = function () {
|
||||
return [
|
||||
this.context.t('privateKey'),
|
||||
this.context.t('jsonFile'),
|
||||
]
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.render = function () {
|
||||
const state = this.state || {}
|
||||
const menuItems = this.getMenuItemTexts()
|
||||
const { type } = state
|
||||
|
||||
return (
|
||||
h('div.new-account-import-form', [
|
||||
|
||||
h('.new-account-import-disclaimer', [
|
||||
h('span', this.context.t('importAccountMsg')),
|
||||
h('span', {
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
onClick: () => {
|
||||
global.platform.openWindow({
|
||||
url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932',
|
||||
})
|
||||
},
|
||||
}, this.context.t('here')),
|
||||
]),
|
||||
|
||||
h('div.new-account-import-form__select-section', [
|
||||
|
||||
h('div.new-account-import-form__select-label', this.context.t('selectType')),
|
||||
|
||||
h(Select, {
|
||||
className: 'new-account-import-form__select',
|
||||
name: 'import-type-select',
|
||||
clearable: false,
|
||||
value: type || menuItems[0],
|
||||
options: menuItems.map((type) => {
|
||||
return {
|
||||
value: type,
|
||||
label: type,
|
||||
}
|
||||
}),
|
||||
onChange: (opt) => {
|
||||
this.setState({ type: opt.value })
|
||||
},
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
this.renderImportView(),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
AccountImportSubview.prototype.renderImportView = function () {
|
||||
const state = this.state || {}
|
||||
const { type } = state
|
||||
const menuItems = this.getMenuItemTexts()
|
||||
const current = type || menuItems[0]
|
||||
|
||||
switch (current) {
|
||||
case this.context.t('privateKey'):
|
||||
return h(PrivateKeyImportView)
|
||||
case this.context.t('jsonFile'):
|
||||
return h(JsonImportView)
|
||||
default:
|
||||
return h(JsonImportView)
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const { withRouter } = require('react-router-dom')
|
||||
const { compose } = require('recompose')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../actions')
|
||||
const FileInput = require('react-simple-file-input').default
|
||||
const { DEFAULT_ROUTE } = require('../../../../routes')
|
||||
const { getMetaMaskAccounts } = require('../../../../selectors')
|
||||
const HELP_LINK = 'https://support.metamask.io/kb/article/7-importing-accounts'
|
||||
import Button from '../../../button'
|
||||
|
||||
class JsonImportSubview extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
file: null,
|
||||
fileContents: '',
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { error } = this.props
|
||||
|
||||
return (
|
||||
h('div.new-account-import-form__json', [
|
||||
|
||||
h('p', this.context.t('usedByClients')),
|
||||
h('a.warning', {
|
||||
href: HELP_LINK,
|
||||
target: '_blank',
|
||||
}, this.context.t('fileImportFail')),
|
||||
|
||||
h(FileInput, {
|
||||
readAs: 'text',
|
||||
onLoad: this.onLoad.bind(this),
|
||||
style: {
|
||||
margin: '20px 0px 12px 34%',
|
||||
fontSize: '15px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
}),
|
||||
|
||||
h('input.new-account-import-form__input-password', {
|
||||
type: 'password',
|
||||
placeholder: this.context.t('enterPassword'),
|
||||
id: 'json-password-box',
|
||||
onKeyPress: this.createKeyringOnEnter.bind(this),
|
||||
}),
|
||||
|
||||
h('div.new-account-create-form__buttons', {}, [
|
||||
|
||||
h(Button, {
|
||||
type: 'default',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => this.props.history.push(DEFAULT_ROUTE),
|
||||
}, [this.context.t('cancel')]),
|
||||
|
||||
h(Button, {
|
||||
type: 'primary',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => this.createNewKeychain(),
|
||||
}, [this.context.t('import')]),
|
||||
|
||||
]),
|
||||
|
||||
error ? h('span.error', error) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
onLoad (event, file) {
|
||||
this.setState({file: file, fileContents: event.target.result})
|
||||
}
|
||||
|
||||
createKeyringOnEnter (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
createNewKeychain () {
|
||||
const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress, history } = this.props
|
||||
const state = this.state
|
||||
|
||||
if (!state) {
|
||||
const message = this.context.t('validFileImport')
|
||||
return displayWarning(message)
|
||||
}
|
||||
|
||||
const { fileContents } = state
|
||||
|
||||
if (!fileContents) {
|
||||
const message = this.context.t('needImportFile')
|
||||
return displayWarning(message)
|
||||
}
|
||||
|
||||
const passwordInput = document.getElementById('json-password-box')
|
||||
const password = passwordInput.value
|
||||
|
||||
if (!password) {
|
||||
const message = this.context.t('needImportPassword')
|
||||
return displayWarning(message)
|
||||
}
|
||||
|
||||
importNewJsonAccount([ fileContents, password ])
|
||||
.then(({ selectedAddress }) => {
|
||||
if (selectedAddress) {
|
||||
history.push(DEFAULT_ROUTE)
|
||||
displayWarning(null)
|
||||
} else {
|
||||
displayWarning('Error importing account.')
|
||||
setSelectedAddress(firstAddress)
|
||||
}
|
||||
})
|
||||
.catch(err => err && displayWarning(err.message || err))
|
||||
}
|
||||
}
|
||||
|
||||
JsonImportSubview.propTypes = {
|
||||
error: PropTypes.string,
|
||||
goHome: PropTypes.func,
|
||||
displayWarning: PropTypes.func,
|
||||
firstAddress: PropTypes.string,
|
||||
importNewJsonAccount: PropTypes.func,
|
||||
history: PropTypes.object,
|
||||
setSelectedAddress: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
error: state.appState.warning,
|
||||
firstAddress: Object.keys(getMetaMaskAccounts(state))[0],
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
goHome: () => dispatch(actions.goHome()),
|
||||
displayWarning: warning => dispatch(actions.displayWarning(warning)),
|
||||
importNewJsonAccount: options => dispatch(actions.importNewAccount('JSON File', options)),
|
||||
setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
|
||||
}
|
||||
}
|
||||
|
||||
JsonImportSubview.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(JsonImportSubview)
|
|
@ -1,113 +0,0 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const { withRouter } = require('react-router-dom')
|
||||
const { compose } = require('recompose')
|
||||
const PropTypes = require('prop-types')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../../actions')
|
||||
const { DEFAULT_ROUTE } = require('../../../../routes')
|
||||
const { getMetaMaskAccounts } = require('../../../../selectors')
|
||||
import Button from '../../../button'
|
||||
|
||||
PrivateKeyImportView.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
)(PrivateKeyImportView)
|
||||
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
error: state.appState.warning,
|
||||
firstAddress: Object.keys(getMetaMaskAccounts(state))[0],
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
importNewAccount: (strategy, [ privateKey ]) => {
|
||||
return dispatch(actions.importNewAccount(strategy, [ privateKey ]))
|
||||
},
|
||||
displayWarning: (message) => dispatch(actions.displayWarning(message || null)),
|
||||
setSelectedAddress: (address) => dispatch(actions.setSelectedAddress(address)),
|
||||
}
|
||||
}
|
||||
|
||||
inherits(PrivateKeyImportView, Component)
|
||||
function PrivateKeyImportView () {
|
||||
this.createKeyringOnEnter = this.createKeyringOnEnter.bind(this)
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.render = function () {
|
||||
const { error, displayWarning } = this.props
|
||||
|
||||
return (
|
||||
h('div.new-account-import-form__private-key', [
|
||||
|
||||
h('span.new-account-create-form__instruction', this.context.t('pastePrivateKey')),
|
||||
|
||||
h('div.new-account-import-form__private-key-password-container', [
|
||||
|
||||
h('input.new-account-import-form__input-password', {
|
||||
type: 'password',
|
||||
id: 'private-key-box',
|
||||
onKeyPress: e => this.createKeyringOnEnter(e),
|
||||
}),
|
||||
|
||||
]),
|
||||
|
||||
h('div.new-account-import-form__buttons', {}, [
|
||||
|
||||
h(Button, {
|
||||
type: 'default',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => {
|
||||
displayWarning(null)
|
||||
this.props.history.push(DEFAULT_ROUTE)
|
||||
},
|
||||
}, [this.context.t('cancel')]),
|
||||
|
||||
h(Button, {
|
||||
type: 'primary',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => this.createNewKeychain(),
|
||||
}, [this.context.t('import')]),
|
||||
|
||||
]),
|
||||
|
||||
error ? h('span.error', error) : null,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
this.createNewKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
PrivateKeyImportView.prototype.createNewKeychain = function () {
|
||||
const input = document.getElementById('private-key-box')
|
||||
const privateKey = input.value
|
||||
const { importNewAccount, history, displayWarning, setSelectedAddress, firstAddress } = this.props
|
||||
|
||||
importNewAccount('Private Key', [ privateKey ])
|
||||
.then(({ selectedAddress }) => {
|
||||
if (selectedAddress) {
|
||||
history.push(DEFAULT_ROUTE)
|
||||
displayWarning(null)
|
||||
} else {
|
||||
displayWarning('Error importing account.')
|
||||
setSelectedAddress(firstAddress)
|
||||
}
|
||||
})
|
||||
.catch(err => err && displayWarning(err.message || err))
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const PropTypes = require('prop-types')
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
SeedImportSubview.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps)(SeedImportSubview)
|
||||
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {}
|
||||
}
|
||||
|
||||
inherits(SeedImportSubview, Component)
|
||||
function SeedImportSubview () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
SeedImportSubview.prototype.render = function () {
|
||||
return (
|
||||
h('div', {
|
||||
style: {
|
||||
},
|
||||
}, [
|
||||
this.context.t('pasteSeed'),
|
||||
h('textarea'),
|
||||
h('br'),
|
||||
h('button', this.context.t('submit')),
|
||||
])
|
||||
)
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
const Component = require('react').Component
|
||||
const { Switch, Route, matchPath } = require('react-router-dom')
|
||||
const PropTypes = require('prop-types')
|
||||
const h = require('react-hyperscript')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('../../../actions')
|
||||
const { getCurrentViewContext } = require('../../../selectors')
|
||||
const classnames = require('classnames')
|
||||
const NewAccountCreateForm = require('./new-account')
|
||||
const NewAccountImportForm = require('./import-account')
|
||||
const ConnectHardwareForm = require('./connect-hardware')
|
||||
const {
|
||||
NEW_ACCOUNT_ROUTE,
|
||||
IMPORT_ACCOUNT_ROUTE,
|
||||
CONNECT_HARDWARE_ROUTE,
|
||||
} = require('../../../routes')
|
||||
|
||||
class CreateAccountPage extends Component {
|
||||
renderTabs () {
|
||||
const { history, location } = this.props
|
||||
|
||||
return h('div.new-account__tabs', [
|
||||
h('div.new-account__tabs__tab', {
|
||||
className: classnames('new-account__tabs__tab', {
|
||||
'new-account__tabs__selected': matchPath(location.pathname, {
|
||||
path: NEW_ACCOUNT_ROUTE, exact: true,
|
||||
}),
|
||||
}),
|
||||
onClick: () => history.push(NEW_ACCOUNT_ROUTE),
|
||||
}, [
|
||||
this.context.t('create'),
|
||||
]),
|
||||
|
||||
h('div.new-account__tabs__tab', {
|
||||
className: classnames('new-account__tabs__tab', {
|
||||
'new-account__tabs__selected': matchPath(location.pathname, {
|
||||
path: IMPORT_ACCOUNT_ROUTE, exact: true,
|
||||
}),
|
||||
}),
|
||||
onClick: () => history.push(IMPORT_ACCOUNT_ROUTE),
|
||||
}, [
|
||||
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')
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
render () {
|
||||
return h('div.new-account', {}, [
|
||||
h('div.new-account__header', [
|
||||
h('div.new-account__title', this.context.t('newAccount')),
|
||||
this.renderTabs(),
|
||||
]),
|
||||
h('div.new-account__form', [
|
||||
h(Switch, [
|
||||
h(Route, {
|
||||
exact: true,
|
||||
path: NEW_ACCOUNT_ROUTE,
|
||||
component: NewAccountCreateForm,
|
||||
}),
|
||||
h(Route, {
|
||||
exact: true,
|
||||
path: IMPORT_ACCOUNT_ROUTE,
|
||||
component: NewAccountImportForm,
|
||||
}),
|
||||
h(Route, {
|
||||
exact: true,
|
||||
path: CONNECT_HARDWARE_ROUTE,
|
||||
component: ConnectHardwareForm,
|
||||
}),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
CreateAccountPage.propTypes = {
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
CreateAccountPage.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
displayedForm: getCurrentViewContext(state),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
displayForm: form => dispatch(actions.setNewAccountForm(form)),
|
||||
showQrView: (selected, identity) => dispatch(actions.showQrView(selected, identity)),
|
||||
showExportPrivateKeyModal: () => {
|
||||
dispatch(actions.showModal({ name: 'EXPORT_PRIVATE_KEY' }))
|
||||
},
|
||||
hideModal: () => dispatch(actions.hideModal()),
|
||||
setAccountLabel: (address, label) => dispatch(actions.setAccountLabel(address, label)),
|
||||
})
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(CreateAccountPage)
|
|
@ -1,108 +0,0 @@
|
|||
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 { DEFAULT_ROUTE } = require('../../../routes')
|
||||
import Button from '../../button'
|
||||
|
||||
class NewAccountCreateForm extends Component {
|
||||
constructor (props, context) {
|
||||
super(props)
|
||||
|
||||
const { numberOfExistingAccounts = 0 } = props
|
||||
const newAccountNumber = numberOfExistingAccounts + 1
|
||||
|
||||
this.state = {
|
||||
newAccountName: '',
|
||||
defaultAccountName: context.t('newAccountNumberName', [newAccountNumber]),
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { newAccountName, defaultAccountName } = this.state
|
||||
const { history, createAccount } = this.props
|
||||
|
||||
return h('div.new-account-create-form', [
|
||||
|
||||
h('div.new-account-create-form__input-label', {}, [
|
||||
this.context.t('accountName'),
|
||||
]),
|
||||
|
||||
h('div.new-account-create-form__input-wrapper', {}, [
|
||||
h('input.new-account-create-form__input', {
|
||||
value: newAccountName,
|
||||
placeholder: defaultAccountName,
|
||||
onChange: event => this.setState({ newAccountName: event.target.value }),
|
||||
}, []),
|
||||
]),
|
||||
|
||||
h('div.new-account-create-form__buttons', {}, [
|
||||
|
||||
h(Button, {
|
||||
type: 'default',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => history.push(DEFAULT_ROUTE),
|
||||
}, [this.context.t('cancel')]),
|
||||
|
||||
h(Button, {
|
||||
type: 'primary',
|
||||
large: true,
|
||||
className: 'new-account-create-form__button',
|
||||
onClick: () => {
|
||||
createAccount(newAccountName || defaultAccountName)
|
||||
.then(() => history.push(DEFAULT_ROUTE))
|
||||
},
|
||||
}, [this.context.t('create')]),
|
||||
|
||||
]),
|
||||
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
NewAccountCreateForm.propTypes = {
|
||||
hideModal: PropTypes.func,
|
||||
showImportPage: PropTypes.func,
|
||||
showConnectPage: PropTypes.func,
|
||||
createAccount: PropTypes.func,
|
||||
numberOfExistingAccounts: PropTypes.number,
|
||||
history: PropTypes.object,
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
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()),
|
||||
createAccount: newAccountName => {
|
||||
return dispatch(actions.addNewAccount())
|
||||
.then(newAccountAddress => {
|
||||
if (newAccountName) {
|
||||
dispatch(actions.setAccountLabel(newAccountAddress, newAccountName))
|
||||
}
|
||||
})
|
||||
},
|
||||
showImportPage: () => dispatch(actions.showImportPage()),
|
||||
showConnectPage: () => dispatch(actions.showConnectPage()),
|
||||
}
|
||||
}
|
||||
|
||||
NewAccountCreateForm.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
}
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(NewAccountCreateForm)
|
||||
|
Loading…
Reference in New Issue