mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge pull request #4157 from danjm/i3725-refactor-send-component-2
I3725 refactor send component
This commit is contained in:
commit
6def76e294
1
ui/app/components/page-container/index.js
Normal file
1
ui/app/components/page-container/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './page-container.component'
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './page-container-footer.component'
|
@ -5,12 +5,24 @@ export default class PageContainerFooter extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
|
cancelText: PropTypes.string,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
|
submitText: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
};
|
}
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { onCancel, onSubmit, disabled } = this.props
|
const {
|
||||||
|
onCancel,
|
||||||
|
cancelText,
|
||||||
|
onSubmit,
|
||||||
|
submitText,
|
||||||
|
disabled,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container__footer">
|
<div className="page-container__footer">
|
||||||
@ -19,15 +31,15 @@ export default class PageContainerFooter extends Component {
|
|||||||
className="btn-secondary--lg page-container__footer-button"
|
className="btn-secondary--lg page-container__footer-button"
|
||||||
onClick={() => onCancel()}
|
onClick={() => onCancel()}
|
||||||
>
|
>
|
||||||
{this.context.t('cancel')}
|
{ this.context.t('cancel') || cancelText }
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn-primary--lg page-container__footer-button"
|
className="btn-primary--lg page-container__footer-button"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={(e) => onSubmit(e)}
|
onClick={e => onSubmit(e)}
|
||||||
>
|
>
|
||||||
{this.context.t('next')}
|
{ this.context.t('next') || submitText }
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -35,7 +47,3 @@ export default class PageContainerFooter extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PageContainerFooter.contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './page-container-header.component'
|
@ -0,0 +1,57 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
export default class PageContainerHeader extends Component {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
showBackButton: PropTypes.bool,
|
||||||
|
onBackButtonClick: PropTypes.func,
|
||||||
|
backButtonStyles: PropTypes.object,
|
||||||
|
backButtonString: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderHeaderRow () {
|
||||||
|
const { showBackButton, onBackButtonClick, backButtonStyles, backButtonString } = this.props
|
||||||
|
|
||||||
|
return showBackButton && (
|
||||||
|
<div className="page-container__header-row">
|
||||||
|
<span
|
||||||
|
className="page-container__back-button"
|
||||||
|
onClick={onBackButtonClick}
|
||||||
|
style={backButtonStyles}
|
||||||
|
>
|
||||||
|
{ backButtonString || 'Back' }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { title, subtitle, onClose } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container__header">
|
||||||
|
|
||||||
|
{ this.renderHeaderRow() }
|
||||||
|
|
||||||
|
<div className="page-container__title">
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="page-container__subtitle">
|
||||||
|
{subtitle}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="page-container__header-close"
|
||||||
|
onClick={() => onClose()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,70 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
|
import PageContainerHeader from './page-container-header'
|
||||||
|
import PageContainerFooter from './page-container-footer'
|
||||||
|
|
||||||
export default class PageContainer extends Component {
|
export default class PageContainer extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
// PageContainerHeader props
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
subtitle: PropTypes.string,
|
||||||
|
onClose: PropTypes.func,
|
||||||
|
showBackButton: PropTypes.bool,
|
||||||
|
onBackButtonClick: PropTypes.func,
|
||||||
|
backButtonStyles: PropTypes.object,
|
||||||
|
backButtonString: PropTypes.string,
|
||||||
|
// Content props
|
||||||
|
ContentComponent: PropTypes.func,
|
||||||
|
contentComponentProps: PropTypes.object,
|
||||||
|
// PageContainerFooter props
|
||||||
|
onCancel: PropTypes.func,
|
||||||
|
cancelText: PropTypes.string,
|
||||||
|
onSubmit: PropTypes.func,
|
||||||
|
submitText: PropTypes.string,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
onClose,
|
||||||
|
showBackButton,
|
||||||
|
onBackButtonClick,
|
||||||
|
backButtonStyles,
|
||||||
|
backButtonString,
|
||||||
|
ContentComponent,
|
||||||
|
contentComponentProps,
|
||||||
|
onCancel,
|
||||||
|
cancelText,
|
||||||
|
onSubmit,
|
||||||
|
submitText,
|
||||||
|
disabled,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container">
|
<div className="page-container">
|
||||||
{this.props.children}
|
<PageContainerHeader
|
||||||
|
title={title}
|
||||||
|
subtitle={subtitle}
|
||||||
|
onClose={onClose}
|
||||||
|
showBackButton={showBackButton}
|
||||||
|
onBackButtonClick={onBackButtonClick}
|
||||||
|
backButtonStyles={backButtonStyles}
|
||||||
|
backButtonString={backButtonString}
|
||||||
|
/>
|
||||||
|
<div className="page-container__content">
|
||||||
|
<ContentComponent { ...contentComponentProps } />
|
||||||
|
</div>
|
||||||
|
<PageContainerFooter
|
||||||
|
onCancel={onCancel}
|
||||||
|
cancelText={cancelText}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
submitText={submitText}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@ function mapStateToProps (state) {
|
|||||||
tokenContract: getSelectedTokenContract(state),
|
tokenContract: getSelectedTokenContract(state),
|
||||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||||
network: state.metamask.network,
|
network: state.metamask.network,
|
||||||
|
isToken: Boolean(getSelectedToken(state)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import PageContainerFooter from '../../page-container/page-container-footer.component'
|
import PageContainerFooter from '../../page-container/page-container-footer'
|
||||||
import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes'
|
import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../routes'
|
||||||
|
|
||||||
export default class SendFooter extends Component {
|
export default class SendFooter extends Component {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import PageContainerHeader from '../../page-container/page-container-header.component'
|
import PageContainerHeader from '../../page-container/page-container-header'
|
||||||
import { DEFAULT_ROUTE } from '../../../routes'
|
import { DEFAULT_ROUTE } from '../../../routes'
|
||||||
|
|
||||||
export default class SendHeader extends Component {
|
export default class SendHeader extends Component {
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
doesAmountErrorRequireUpdate,
|
doesAmountErrorRequireUpdate,
|
||||||
} from './send.utils'
|
} from './send.utils'
|
||||||
|
|
||||||
import PageContainer from '..//page-container/page-container.component'
|
|
||||||
import SendHeader from './send-header/send-header.container'
|
import SendHeader from './send-header/send-header.container'
|
||||||
import SendContent from './send-content/send-content.component'
|
import SendContent from './send-content/send-content.component'
|
||||||
import SendFooter from './send-footer/send-footer.container'
|
import SendFooter from './send-footer/send-footer.container'
|
||||||
@ -128,11 +127,11 @@ export default class SendTransactionScreen extends PersistentForm {
|
|||||||
const { history } = this.props
|
const { history } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<div className="page-container">
|
||||||
<SendHeader history={history}/>
|
<SendHeader history={history}/>
|
||||||
<SendContent/>
|
<SendContent/>
|
||||||
<SendFooter history={history}/>
|
<SendFooter history={history}/>
|
||||||
</PageContainer>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,23 @@ const PropTypes = require('prop-types')
|
|||||||
const PersistentForm = require('../lib/persistent-form')
|
const PersistentForm = require('../lib/persistent-form')
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
|
|
||||||
|
const ethAbi = require('ethereumjs-abi')
|
||||||
|
const ethUtil = require('ethereumjs-util')
|
||||||
|
|
||||||
|
const FromDropdown = require('./components/send/from-dropdown')
|
||||||
|
const EnsInput = require('./components/ens-input')
|
||||||
|
const CurrencyDisplay = require('./components/send/currency-display')
|
||||||
|
const MemoTextArea = require('./components/send/memo-textarea')
|
||||||
|
const GasFeeDisplay = require('./components/send/gas-fee-display-v2')
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
|
||||||
|
} = require('./components/send/send-constants')
|
||||||
|
|
||||||
|
const {
|
||||||
|
multiplyCurrencies,
|
||||||
conversionGreaterThan,
|
conversionGreaterThan,
|
||||||
|
subtractCurrencies,
|
||||||
} = require('./conversion-util')
|
} = require('./conversion-util')
|
||||||
const {
|
const {
|
||||||
calcTokenAmount,
|
calcTokenAmount,
|
||||||
@ -14,11 +29,12 @@ const {
|
|||||||
isTokenBalanceSufficient,
|
isTokenBalanceSufficient,
|
||||||
getGasTotal,
|
getGasTotal,
|
||||||
} = require('./components/send/send-utils')
|
} = require('./components/send/send-utils')
|
||||||
|
const { isValidAddress } = require('./util')
|
||||||
|
|
||||||
import PageContainer from './components/page-container/page-container.component'
|
import PageContainer from './components/page-container'
|
||||||
import SendHeader from './components/send_/send-header/send-header.container'
|
// import SendHeader from './components/send_/send-header/send-header.container'
|
||||||
import SendContent from './components/send_/send-content/send-content.component'
|
// import PageContainerContent from './components/page-container/page-container-content.component'
|
||||||
import SendFooter from './components/send_/send-footer/send-footer.container'
|
// import PageContainerFooter from './components/page-container/page-container-footer.component'
|
||||||
|
|
||||||
SendTransactionScreen.contextTypes = {
|
SendTransactionScreen.contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
@ -40,6 +56,8 @@ function SendTransactionScreen () {
|
|||||||
gasLoadingError: false,
|
gasLoadingError: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleToChange = this.handleToChange.bind(this)
|
||||||
|
this.handleAmountChange = this.handleAmountChange.bind(this)
|
||||||
this.validateAmount = this.validateAmount.bind(this)
|
this.validateAmount = this.validateAmount.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +92,17 @@ SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SendTransactionScreen.prototype.componentWillMount = function () {
|
SendTransactionScreen.prototype.componentWillMount = function () {
|
||||||
|
const {
|
||||||
|
updateTokenExchangeRate,
|
||||||
|
selectedToken = {},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const { symbol } = selectedToken || {}
|
||||||
|
|
||||||
|
if (symbol) {
|
||||||
|
updateTokenExchangeRate(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
this.updateGas()
|
this.updateGas()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +113,7 @@ SendTransactionScreen.prototype.updateGas = function () {
|
|||||||
estimateGas,
|
estimateGas,
|
||||||
selectedAddress,
|
selectedAddress,
|
||||||
data,
|
data,
|
||||||
setGasTotal,
|
updateGasTotal,
|
||||||
from,
|
from,
|
||||||
tokenContract,
|
tokenContract,
|
||||||
editingTransactionId,
|
editingTransactionId,
|
||||||
@ -110,7 +139,7 @@ SendTransactionScreen.prototype.updateGas = function () {
|
|||||||
])
|
])
|
||||||
.then(([gasPrice, gas]) => {
|
.then(([gasPrice, gas]) => {
|
||||||
const newGasTotal = getGasTotal(gas, gasPrice)
|
const newGasTotal = getGasTotal(gas, gasPrice)
|
||||||
setGasTotal(newGasTotal)
|
updateGasTotal(newGasTotal)
|
||||||
this.setState({ gasLoadingError: false })
|
this.setState({ gasLoadingError: false })
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
@ -118,7 +147,7 @@ SendTransactionScreen.prototype.updateGas = function () {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const newGasTotal = getGasTotal(gasLimit, gasPrice)
|
const newGasTotal = getGasTotal(gasLimit, gasPrice)
|
||||||
setGasTotal(newGasTotal)
|
updateGasTotal(newGasTotal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +186,139 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderErrorMessage = function (errorType) {
|
||||||
|
const { errors } = this.props
|
||||||
|
const errorMessage = errors[errorType]
|
||||||
|
|
||||||
|
return errorMessage
|
||||||
|
? h('div.send-v2__error', [ errorMessage ])
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.handleFromChange = async function (newFrom) {
|
||||||
|
const {
|
||||||
|
updateSendFrom,
|
||||||
|
tokenContract,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (tokenContract) {
|
||||||
|
const usersToken = await tokenContract.balanceOf(newFrom.address)
|
||||||
|
this.updateSendTokenBalance(usersToken)
|
||||||
|
}
|
||||||
|
updateSendFrom(newFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderFromRow = function () {
|
||||||
|
const {
|
||||||
|
from,
|
||||||
|
fromAccounts,
|
||||||
|
conversionRate,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const { fromDropdownOpen } = this.state
|
||||||
|
|
||||||
|
return h('div.send-v2__form-row', [
|
||||||
|
|
||||||
|
h('div.send-v2__form-label', 'From:'),
|
||||||
|
|
||||||
|
h('div.send-v2__form-field', [
|
||||||
|
h(FromDropdown, {
|
||||||
|
dropdownOpen: fromDropdownOpen,
|
||||||
|
accounts: fromAccounts,
|
||||||
|
selectedAccount: from,
|
||||||
|
onSelect: newFrom => this.handleFromChange(newFrom),
|
||||||
|
openDropdown: () => this.setState({ fromDropdownOpen: true }),
|
||||||
|
closeDropdown: () => this.setState({ fromDropdownOpen: false }),
|
||||||
|
conversionRate,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') {
|
||||||
|
const {
|
||||||
|
updateSendTo,
|
||||||
|
updateSendErrors,
|
||||||
|
} = this.props
|
||||||
|
let toError = null
|
||||||
|
|
||||||
|
if (!to) {
|
||||||
|
toError = this.context.t('required')
|
||||||
|
} else if (!isValidAddress(to)) {
|
||||||
|
toError = this.context.t('invalidAddressRecipient')
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSendTo(to, nickname)
|
||||||
|
updateSendErrors({ to: toError })
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderToRow = function () {
|
||||||
|
const { toAccounts, errors, to, network } = this.props
|
||||||
|
|
||||||
|
const { toDropdownOpen } = this.state
|
||||||
|
|
||||||
|
return h('div.send-v2__form-row', [
|
||||||
|
|
||||||
|
h('div.send-v2__form-label', [
|
||||||
|
|
||||||
|
this.context.t('to'),
|
||||||
|
|
||||||
|
this.renderErrorMessage(this.context.t('to')),
|
||||||
|
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div.send-v2__form-field', [
|
||||||
|
h(EnsInput, {
|
||||||
|
name: 'address',
|
||||||
|
placeholder: 'Recipient Address',
|
||||||
|
network,
|
||||||
|
to,
|
||||||
|
accounts: Object.entries(toAccounts).map(([key, account]) => account),
|
||||||
|
dropdownOpen: toDropdownOpen,
|
||||||
|
openDropdown: () => this.setState({ toDropdownOpen: true }),
|
||||||
|
closeDropdown: () => this.setState({ toDropdownOpen: false }),
|
||||||
|
onChange: this.handleToChange,
|
||||||
|
inError: Boolean(errors.to),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.handleAmountChange = function (value) {
|
||||||
|
const amount = value
|
||||||
|
const { updateSendAmount, setMaxModeTo } = this.props
|
||||||
|
|
||||||
|
setMaxModeTo(false)
|
||||||
|
this.validateAmount(amount)
|
||||||
|
updateSendAmount(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.setAmountToMax = function () {
|
||||||
|
const {
|
||||||
|
from: { balance },
|
||||||
|
updateSendAmount,
|
||||||
|
updateSendErrors,
|
||||||
|
tokenBalance,
|
||||||
|
selectedToken,
|
||||||
|
gasTotal,
|
||||||
|
} = this.props
|
||||||
|
const { decimals } = selectedToken || {}
|
||||||
|
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||||
|
|
||||||
|
const maxAmount = selectedToken
|
||||||
|
? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'})
|
||||||
|
: subtractCurrencies(
|
||||||
|
ethUtil.addHexPrefix(balance),
|
||||||
|
ethUtil.addHexPrefix(gasTotal),
|
||||||
|
{ toNumericBase: 'hex' }
|
||||||
|
)
|
||||||
|
|
||||||
|
updateSendErrors({ amount: null })
|
||||||
|
|
||||||
|
updateSendAmount(maxAmount)
|
||||||
|
}
|
||||||
|
|
||||||
SendTransactionScreen.prototype.validateAmount = function (value) {
|
SendTransactionScreen.prototype.validateAmount = function (value) {
|
||||||
const {
|
const {
|
||||||
@ -203,29 +365,291 @@ SendTransactionScreen.prototype.validateAmount = function (value) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (conversionRate && !sufficientBalance) {
|
if (conversionRate && !sufficientBalance) {
|
||||||
amountError = 'insufficientFunds'
|
amountError = this.context.t('insufficientFunds')
|
||||||
} else if (verifyTokenBalance && !sufficientTokens) {
|
} else if (verifyTokenBalance && !sufficientTokens) {
|
||||||
amountError = 'insufficientTokens'
|
amountError = this.context.t('insufficientTokens')
|
||||||
} else if (amountLessThanZero) {
|
} else if (amountLessThanZero) {
|
||||||
amountError = 'negativeETH'
|
amountError = this.context.t('negativeETH')
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSendErrors({ amount: amountError })
|
updateSendErrors({ amount: amountError })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderAmountRow = function () {
|
||||||
|
const {
|
||||||
|
selectedToken,
|
||||||
|
primaryCurrency = 'ETH',
|
||||||
|
convertedCurrency,
|
||||||
|
amountConversionRate,
|
||||||
|
errors,
|
||||||
|
amount,
|
||||||
|
setMaxModeTo,
|
||||||
|
maxModeOn,
|
||||||
|
gasTotal,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return h('div.send-v2__form-row', [
|
||||||
|
|
||||||
|
h('div.send-v2__form-label', [
|
||||||
|
'Amount:',
|
||||||
|
this.renderErrorMessage('amount'),
|
||||||
|
!errors.amount && gasTotal && h('div.send-v2__amount-max', {
|
||||||
|
onClick: (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setMaxModeTo(true)
|
||||||
|
this.setAmountToMax()
|
||||||
|
},
|
||||||
|
}, [ !maxModeOn ? this.context.t('max') : '' ]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
h('div.send-v2__form-field', [
|
||||||
|
h(CurrencyDisplay, {
|
||||||
|
inError: Boolean(errors.amount),
|
||||||
|
primaryCurrency,
|
||||||
|
convertedCurrency,
|
||||||
|
selectedToken,
|
||||||
|
value: amount || '0x0',
|
||||||
|
conversionRate: amountConversionRate,
|
||||||
|
handleChange: this.handleAmountChange,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderGasRow = function () {
|
||||||
|
const {
|
||||||
|
conversionRate,
|
||||||
|
convertedCurrency,
|
||||||
|
showCustomizeGasModal,
|
||||||
|
gasTotal,
|
||||||
|
} = this.props
|
||||||
|
const { gasLoadingError } = this.state
|
||||||
|
|
||||||
|
return h('div.send-v2__form-row', [
|
||||||
|
|
||||||
|
h('div.send-v2__form-label', this.context.t('gasFee')),
|
||||||
|
|
||||||
|
h('div.send-v2__form-field', [
|
||||||
|
|
||||||
|
h(GasFeeDisplay, {
|
||||||
|
gasTotal,
|
||||||
|
conversionRate,
|
||||||
|
convertedCurrency,
|
||||||
|
onClick: showCustomizeGasModal,
|
||||||
|
gasLoadingError,
|
||||||
|
}),
|
||||||
|
|
||||||
|
]),
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderMemoRow = function () {
|
||||||
|
const { updateSendMemo, memo } = this.props
|
||||||
|
|
||||||
|
return h('div.send-v2__form-row', [
|
||||||
|
|
||||||
|
h('div.send-v2__form-label', 'Transaction Memo:'),
|
||||||
|
|
||||||
|
h('div.send-v2__form-field', [
|
||||||
|
h(MemoTextArea, {
|
||||||
|
memo,
|
||||||
|
onChange: (event) => updateSendMemo(event.target.value),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderForm = function () {
|
||||||
|
return h(PageContainerContent, [
|
||||||
|
h('.send-v2__form', [
|
||||||
|
this.renderFromRow(),
|
||||||
|
|
||||||
|
this.renderToRow(),
|
||||||
|
|
||||||
|
this.renderAmountRow(),
|
||||||
|
|
||||||
|
this.renderGasRow(),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.renderFooter = function () {
|
||||||
|
const {
|
||||||
|
goHome,
|
||||||
|
clearSend,
|
||||||
|
gasTotal,
|
||||||
|
tokenBalance,
|
||||||
|
selectedToken,
|
||||||
|
errors: { amount: amountError, to: toError },
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const missingTokenBalance = selectedToken && !tokenBalance
|
||||||
|
const noErrors = !amountError && toError === null
|
||||||
|
|
||||||
|
return h(PageContainerFooter, {
|
||||||
|
onCancel: () => {
|
||||||
|
clearSend()
|
||||||
|
goHome()
|
||||||
|
},
|
||||||
|
onSubmit: e => this.onSubmit(e),
|
||||||
|
disabled: !noErrors || !gasTotal || missingTokenBalance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
SendTransactionScreen.prototype.render = function () {
|
SendTransactionScreen.prototype.render = function () {
|
||||||
const { history } = this.props
|
const {
|
||||||
|
isToken,
|
||||||
|
clearSend,
|
||||||
|
goHome,
|
||||||
|
gasTotal,
|
||||||
|
tokenBalance,
|
||||||
|
selectedToken,
|
||||||
|
errors: { amount: amountError, to: toError },
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const missingTokenBalance = selectedToken && !tokenBalance
|
||||||
|
const noErrors = !amountError && toError === null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
h(PageContainer, [
|
h(PageContainer, {
|
||||||
|
title: isToken ? this.context.t('sendTokens') : this.context.t('sendETH'),
|
||||||
|
subtitle: this.context.t('onlySendToEtherAddress'),
|
||||||
|
onClose: () => {
|
||||||
|
clearSend()
|
||||||
|
goHome()
|
||||||
|
},
|
||||||
|
ContentComponent: this.renderForm,
|
||||||
|
onCancel: () => {
|
||||||
|
clearSend()
|
||||||
|
goHome()
|
||||||
|
},
|
||||||
|
onSubmit: e => this.onSubmit(e),
|
||||||
|
disabled: !noErrors || !gasTotal || missingTokenBalance,
|
||||||
|
})
|
||||||
|
// , [
|
||||||
|
|
||||||
h(SendHeader),
|
// h(SendHeader),
|
||||||
|
|
||||||
h(SendContent),
|
// this.renderForm(),
|
||||||
|
|
||||||
h(SendFooter, { history }),
|
// this.renderFooter(),
|
||||||
])
|
// ])
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress, nickname = '') {
|
||||||
|
const { toAccounts, addToAddressBook } = this.props
|
||||||
|
if (!toAccounts.find(({ address }) => newAddress === address)) {
|
||||||
|
// TODO: nickname, i.e. addToAddressBook(recipient, nickname)
|
||||||
|
addToAddressBook(newAddress, nickname)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.getEditedTx = function () {
|
||||||
|
const {
|
||||||
|
from: {address: from},
|
||||||
|
to,
|
||||||
|
amount,
|
||||||
|
gasLimit: gas,
|
||||||
|
gasPrice,
|
||||||
|
selectedToken,
|
||||||
|
editingTransactionId,
|
||||||
|
unapprovedTxs,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const editingTx = {
|
||||||
|
...unapprovedTxs[editingTransactionId],
|
||||||
|
txParams: {
|
||||||
|
from: ethUtil.addHexPrefix(from),
|
||||||
|
gas: ethUtil.addHexPrefix(gas),
|
||||||
|
gasPrice: ethUtil.addHexPrefix(gasPrice),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedToken) {
|
||||||
|
const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call(
|
||||||
|
ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]),
|
||||||
|
x => ('00' + x.toString(16)).slice(-2)
|
||||||
|
).join('')
|
||||||
|
|
||||||
|
Object.assign(editingTx.txParams, {
|
||||||
|
value: ethUtil.addHexPrefix('0'),
|
||||||
|
to: ethUtil.addHexPrefix(selectedToken.address),
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const { data } = unapprovedTxs[editingTransactionId].txParams
|
||||||
|
|
||||||
|
Object.assign(editingTx.txParams, {
|
||||||
|
value: ethUtil.addHexPrefix(amount),
|
||||||
|
to: ethUtil.addHexPrefix(to),
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof editingTx.txParams.data === 'undefined') {
|
||||||
|
delete editingTx.txParams.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return editingTx
|
||||||
|
}
|
||||||
|
|
||||||
|
SendTransactionScreen.prototype.onSubmit = function (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const {
|
||||||
|
from: {address: from},
|
||||||
|
to: _to,
|
||||||
|
amount,
|
||||||
|
gasLimit: gas,
|
||||||
|
gasPrice,
|
||||||
|
signTokenTx,
|
||||||
|
signTx,
|
||||||
|
updateTx,
|
||||||
|
selectedToken,
|
||||||
|
editingTransactionId,
|
||||||
|
toNickname,
|
||||||
|
errors: { amount: amountError, to: toError },
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const noErrors = !amountError && toError === null
|
||||||
|
|
||||||
|
if (!noErrors) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const to = ethUtil.addHexPrefix(_to)
|
||||||
|
|
||||||
|
this.addToAddressBookIfNew(to, toNickname)
|
||||||
|
|
||||||
|
if (editingTransactionId) {
|
||||||
|
const editedTx = this.getEditedTx()
|
||||||
|
|
||||||
|
updateTx(editedTx)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const txParams = {
|
||||||
|
from,
|
||||||
|
value: '0',
|
||||||
|
gas,
|
||||||
|
gasPrice,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedToken) {
|
||||||
|
txParams.value = amount
|
||||||
|
txParams.to = to
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(txParams).forEach(key => {
|
||||||
|
txParams[key] = ethUtil.addHexPrefix(txParams[key])
|
||||||
|
})
|
||||||
|
|
||||||
|
selectedToken
|
||||||
|
? signTokenTx(selectedToken.address, to, amount, txParams)
|
||||||
|
: signTx(txParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user