mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
This component used to persist form contents to LocalStorage. This was especially useful for the popup UI, as each time the mouse left the popup, the UI was completely torn down and state was lost. This component was only being referenced by one form, and it wasn't even being used there (e.g. no fields were labelled appropriately to be persisted). This was a useful component, and it seems this feature was lost somewhere in the past couple of years. It was tempting to re-instate it rather than delete it, but I decided not to because I'd likely approach the problem differently if we wanted to reinstate it again today (maybe by using a React Hook, or storing the state in Redux and persisting a subset of the Redux store instead).
363 lines
8.9 KiB
JavaScript
363 lines
8.9 KiB
JavaScript
import React, { Component } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import {
|
|
getAmountErrorObject,
|
|
getGasFeeErrorObject,
|
|
getToAddressForGasUpdate,
|
|
doesAmountErrorRequireUpdate,
|
|
} from './send.utils'
|
|
import debounce from 'lodash.debounce'
|
|
import { getToWarningObject, getToErrorObject } from './send-content/add-recipient/add-recipient'
|
|
import SendHeader from './send-header'
|
|
import AddRecipient from './send-content/add-recipient'
|
|
import SendContent from './send-content'
|
|
import SendFooter from './send-footer'
|
|
import EnsInput from './send-content/add-recipient/ens-input'
|
|
|
|
|
|
export default class SendTransactionScreen extends Component {
|
|
|
|
static propTypes = {
|
|
addressBook: PropTypes.arrayOf(PropTypes.object),
|
|
amount: PropTypes.string,
|
|
amountConversionRate: PropTypes.oneOfType([
|
|
PropTypes.string,
|
|
PropTypes.number,
|
|
]),
|
|
blockGasLimit: PropTypes.string,
|
|
conversionRate: PropTypes.number,
|
|
editingTransactionId: PropTypes.string,
|
|
fetchBasicGasEstimates: PropTypes.func.isRequired,
|
|
from: PropTypes.object,
|
|
gasLimit: PropTypes.string,
|
|
gasPrice: PropTypes.string,
|
|
gasTotal: PropTypes.string,
|
|
hasHexData: PropTypes.bool,
|
|
history: PropTypes.object,
|
|
network: PropTypes.string,
|
|
primaryCurrency: PropTypes.string,
|
|
recentBlocks: PropTypes.array,
|
|
resetSendState: PropTypes.func.isRequired,
|
|
selectedAddress: PropTypes.string,
|
|
selectedToken: PropTypes.object,
|
|
showHexData: PropTypes.bool,
|
|
to: PropTypes.string,
|
|
toNickname: PropTypes.string,
|
|
tokens: PropTypes.array,
|
|
tokenBalance: PropTypes.string,
|
|
tokenContract: PropTypes.object,
|
|
updateAndSetGasLimit: PropTypes.func.isRequired,
|
|
updateSendEnsResolution: PropTypes.func.isRequired,
|
|
updateSendEnsResolutionError: PropTypes.func.isRequired,
|
|
updateSendErrors: PropTypes.func.isRequired,
|
|
updateSendTo: PropTypes.func.isRequired,
|
|
updateSendTokenBalance: PropTypes.func.isRequired,
|
|
updateToNicknameIfNecessary: PropTypes.func.isRequired,
|
|
scanQrCode: PropTypes.func.isRequired,
|
|
qrCodeDetected: PropTypes.func.isRequired,
|
|
qrCodeData: PropTypes.object,
|
|
}
|
|
|
|
static contextTypes = {
|
|
t: PropTypes.func,
|
|
metricsEvent: PropTypes.func,
|
|
}
|
|
|
|
state = {
|
|
query: '',
|
|
toError: null,
|
|
toWarning: null,
|
|
}
|
|
|
|
constructor (props) {
|
|
super(props)
|
|
this.dValidate = debounce(this.validate, 1000)
|
|
}
|
|
|
|
componentDidUpdate (prevProps) {
|
|
const {
|
|
amount,
|
|
amountConversionRate,
|
|
conversionRate,
|
|
from: { address, balance },
|
|
gasTotal,
|
|
network,
|
|
primaryCurrency,
|
|
selectedToken,
|
|
tokenBalance,
|
|
updateSendErrors,
|
|
updateSendTo,
|
|
updateSendTokenBalance,
|
|
tokenContract,
|
|
to,
|
|
toNickname,
|
|
addressBook,
|
|
updateToNicknameIfNecessary,
|
|
qrCodeData,
|
|
qrCodeDetected,
|
|
} = this.props
|
|
|
|
let updateGas = false
|
|
const {
|
|
from: { balance: prevBalance },
|
|
gasTotal: prevGasTotal,
|
|
tokenBalance: prevTokenBalance,
|
|
network: prevNetwork,
|
|
selectedToken: prevSelectedToken,
|
|
to: prevTo,
|
|
} = prevProps
|
|
|
|
const uninitialized = [prevBalance, prevGasTotal].every(n => n === null)
|
|
|
|
const amountErrorRequiresUpdate = doesAmountErrorRequireUpdate({
|
|
balance,
|
|
gasTotal,
|
|
prevBalance,
|
|
prevGasTotal,
|
|
prevTokenBalance,
|
|
selectedToken,
|
|
tokenBalance,
|
|
})
|
|
|
|
if (amountErrorRequiresUpdate) {
|
|
const amountErrorObject = getAmountErrorObject({
|
|
amount,
|
|
amountConversionRate,
|
|
balance,
|
|
conversionRate,
|
|
gasTotal,
|
|
primaryCurrency,
|
|
selectedToken,
|
|
tokenBalance,
|
|
})
|
|
const gasFeeErrorObject = selectedToken
|
|
? getGasFeeErrorObject({
|
|
amountConversionRate,
|
|
balance,
|
|
conversionRate,
|
|
gasTotal,
|
|
primaryCurrency,
|
|
selectedToken,
|
|
})
|
|
: { gasFee: null }
|
|
updateSendErrors(Object.assign(amountErrorObject, gasFeeErrorObject))
|
|
}
|
|
|
|
if (!uninitialized) {
|
|
|
|
if (network !== prevNetwork && network !== 'loading') {
|
|
updateSendTokenBalance({
|
|
selectedToken,
|
|
tokenContract,
|
|
address,
|
|
})
|
|
updateToNicknameIfNecessary(to, toNickname, addressBook)
|
|
updateGas = true
|
|
}
|
|
}
|
|
|
|
const prevTokenAddress = prevSelectedToken && prevSelectedToken.address
|
|
const selectedTokenAddress = selectedToken && selectedToken.address
|
|
|
|
if (selectedTokenAddress && prevTokenAddress !== selectedTokenAddress) {
|
|
this.updateSendToken()
|
|
updateGas = true
|
|
}
|
|
|
|
let scannedAddress
|
|
if (qrCodeData) {
|
|
if (qrCodeData.type === 'address') {
|
|
scannedAddress = qrCodeData.values.address.toLowerCase()
|
|
const currentAddress = prevTo && prevTo.toLowerCase()
|
|
if (currentAddress !== scannedAddress) {
|
|
updateSendTo(scannedAddress)
|
|
updateGas = true
|
|
// Clean up QR code data after handling
|
|
qrCodeDetected(null)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateGas) {
|
|
if (scannedAddress) {
|
|
this.updateGas({ to: scannedAddress })
|
|
} else {
|
|
this.updateGas()
|
|
}
|
|
}
|
|
}
|
|
|
|
componentDidMount () {
|
|
this.props.fetchBasicGasEstimates()
|
|
.then(() => {
|
|
this.updateGas()
|
|
})
|
|
}
|
|
|
|
UNSAFE_componentWillMount () {
|
|
this.updateSendToken()
|
|
|
|
// Show QR Scanner modal if ?scan=true
|
|
if (window.location.search === '?scan=true') {
|
|
this.props.scanQrCode()
|
|
|
|
// Clear the queryString param after showing the modal
|
|
const cleanUrl = location.href.split('?')[0]
|
|
history.pushState({}, null, `${cleanUrl}`)
|
|
window.location.hash = '#send'
|
|
}
|
|
}
|
|
|
|
componentWillUnmount () {
|
|
this.props.resetSendState()
|
|
}
|
|
|
|
onRecipientInputChange = query => {
|
|
if (query) {
|
|
this.dValidate(query)
|
|
} else {
|
|
this.validate(query)
|
|
}
|
|
|
|
this.setState({
|
|
query,
|
|
})
|
|
}
|
|
|
|
validate (query) {
|
|
const {
|
|
hasHexData,
|
|
tokens,
|
|
selectedToken,
|
|
network,
|
|
} = this.props
|
|
|
|
if (!query) {
|
|
return this.setState({ toError: '', toWarning: '' })
|
|
}
|
|
|
|
const toErrorObject = getToErrorObject(query, null, hasHexData, tokens, selectedToken, network)
|
|
const toWarningObject = getToWarningObject(query, null, tokens, selectedToken)
|
|
|
|
this.setState({
|
|
toError: toErrorObject.to,
|
|
toWarning: toWarningObject.to,
|
|
})
|
|
}
|
|
|
|
updateSendToken () {
|
|
const {
|
|
from: { address },
|
|
selectedToken,
|
|
tokenContract,
|
|
updateSendTokenBalance,
|
|
} = this.props
|
|
|
|
updateSendTokenBalance({
|
|
selectedToken,
|
|
tokenContract,
|
|
address,
|
|
})
|
|
}
|
|
|
|
updateGas ({ to: updatedToAddress, amount: value, data } = {}) {
|
|
const {
|
|
amount,
|
|
blockGasLimit,
|
|
editingTransactionId,
|
|
gasLimit,
|
|
gasPrice,
|
|
recentBlocks,
|
|
selectedAddress,
|
|
selectedToken = {},
|
|
to: currentToAddress,
|
|
updateAndSetGasLimit,
|
|
} = this.props
|
|
|
|
updateAndSetGasLimit({
|
|
blockGasLimit,
|
|
editingTransactionId,
|
|
gasLimit,
|
|
gasPrice,
|
|
recentBlocks,
|
|
selectedAddress,
|
|
selectedToken,
|
|
to: getToAddressForGasUpdate(updatedToAddress, currentToAddress),
|
|
value: value || amount,
|
|
data,
|
|
})
|
|
}
|
|
|
|
render () {
|
|
const { history, to } = this.props
|
|
let content
|
|
|
|
if (to) {
|
|
content = this.renderSendContent()
|
|
} else {
|
|
content = this.renderAddRecipient()
|
|
}
|
|
|
|
return (
|
|
<div className="page-container">
|
|
<SendHeader history={history} />
|
|
{ this.renderInput() }
|
|
{ content }
|
|
</div>
|
|
)
|
|
}
|
|
|
|
renderInput () {
|
|
return (
|
|
<EnsInput
|
|
className="send__to-row"
|
|
scanQrCode={_ => {
|
|
this.context.metricsEvent({
|
|
eventOpts: {
|
|
category: 'Transactions',
|
|
action: 'Edit Screen',
|
|
name: 'Used QR scanner',
|
|
},
|
|
})
|
|
this.props.scanQrCode()
|
|
}}
|
|
onChange={this.onRecipientInputChange}
|
|
onValidAddressTyped={(address) => this.props.updateSendTo(address, '')}
|
|
onPaste={text => {
|
|
this.props.updateSendTo(text) && this.updateGas()
|
|
}}
|
|
onReset={() => this.props.updateSendTo('', '')}
|
|
updateEnsResolution={this.props.updateSendEnsResolution}
|
|
updateEnsResolutionError={this.props.updateSendEnsResolutionError}
|
|
/>
|
|
)
|
|
}
|
|
|
|
renderAddRecipient () {
|
|
const { toError, toWarning } = this.state
|
|
|
|
return (
|
|
<AddRecipient
|
|
updateGas={({ to, amount, data } = {}) => this.updateGas({ to, amount, data })}
|
|
query={this.state.query}
|
|
toError={toError}
|
|
toWarning={toWarning}
|
|
/>
|
|
)
|
|
}
|
|
|
|
renderSendContent () {
|
|
const { history, showHexData } = this.props
|
|
|
|
return [
|
|
<SendContent
|
|
key="send-content"
|
|
updateGas={({ to, amount, data } = {}) => this.updateGas({ to, amount, data })}
|
|
showHexData={showHexData}
|
|
/>,
|
|
<SendFooter key="send-footer" history={history} />,
|
|
]
|
|
}
|
|
|
|
}
|