import React, { Component } from 'react' import PropTypes from 'prop-types' import ethUtil from 'ethereumjs-util' import { checkExistingAddresses } from '../../helpers/utils/util' import { tokenInfoGetter } from '../../helpers/utils/token-util' import { CONFIRM_ADD_TOKEN_ROUTE } from '../../helpers/constants/routes' import TextField from '../../components/ui/text-field' import TokenList from './token-list' import TokenSearch from './token-search' import PageContainer from '../../components/ui/page-container' import { Tabs, Tab } from '../../components/ui/tabs' const emptyAddr = '0x0000000000000000000000000000000000000000' class AddToken extends Component { static contextTypes = { t: PropTypes.func, } static propTypes = { history: PropTypes.object, setPendingTokens: PropTypes.func, pendingTokens: PropTypes.object, clearPendingTokens: PropTypes.func, tokens: PropTypes.array, identities: PropTypes.object, mostRecentOverviewPage: PropTypes.string.isRequired, } state = { customAddress: '', customSymbol: '', customDecimals: 0, searchResults: [], selectedTokens: {}, tokenSelectorError: null, customAddressError: null, customSymbolError: null, customDecimalsError: null, autoFilled: false, forceEditSymbol: false, } componentDidMount () { this.tokenInfoGetter = tokenInfoGetter() const { pendingTokens = {} } = this.props const pendingTokenKeys = Object.keys(pendingTokens) if (pendingTokenKeys.length > 0) { let selectedTokens = {} let customToken = {} pendingTokenKeys.forEach((tokenAddress) => { const token = pendingTokens[tokenAddress] const { isCustom } = token if (isCustom) { customToken = { ...token } } else { selectedTokens = { ...selectedTokens, [tokenAddress]: { ...token } } } }) const { address: customAddress = '', symbol: customSymbol = '', decimals: customDecimals = 0, } = customToken this.setState({ selectedTokens, customAddress, customSymbol, customDecimals }) } } handleToggleToken (token) { const { address } = token const { selectedTokens = {} } = this.state const selectedTokensCopy = { ...selectedTokens } if (address in selectedTokensCopy) { delete selectedTokensCopy[address] } else { selectedTokensCopy[address] = token } this.setState({ selectedTokens: selectedTokensCopy, tokenSelectorError: null, }) } hasError () { const { tokenSelectorError, customAddressError, customSymbolError, customDecimalsError, } = this.state return tokenSelectorError || customAddressError || customSymbolError || customDecimalsError } hasSelected () { const { customAddress = '', selectedTokens = {} } = this.state return customAddress || Object.keys(selectedTokens).length > 0 } handleNext () { if (this.hasError()) { return } if (!this.hasSelected()) { this.setState({ tokenSelectorError: this.context.t('mustSelectOne') }) return } const { setPendingTokens, history } = this.props const { customAddress: address, customSymbol: symbol, customDecimals: decimals, selectedTokens, } = this.state const customToken = { address, symbol, decimals, } setPendingTokens({ customToken, selectedTokens }) history.push(CONFIRM_ADD_TOKEN_ROUTE) } async attemptToAutoFillTokenParams (address) { const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address) const autoFilled = Boolean(symbol && decimals) this.setState({ autoFilled }) this.handleCustomSymbolChange(symbol || '') this.handleCustomDecimalsChange(decimals) } handleCustomAddressChange (value) { const customAddress = value.trim() this.setState({ customAddress, customAddressError: null, tokenSelectorError: null, autoFilled: false, }) const isValidAddress = ethUtil.isValidAddress(customAddress) const standardAddress = ethUtil.addHexPrefix(customAddress).toLowerCase() switch (true) { case !isValidAddress: this.setState({ customAddressError: this.context.t('invalidAddress'), customSymbol: '', customDecimals: 0, customSymbolError: null, customDecimalsError: null, }) break case Boolean(this.props.identities[standardAddress]): this.setState({ customAddressError: this.context.t('personalAddressDetected'), }) break case checkExistingAddresses(customAddress, this.props.tokens): this.setState({ customAddressError: this.context.t('tokenAlreadyAdded'), }) break default: if (customAddress !== emptyAddr) { this.attemptToAutoFillTokenParams(customAddress) } } } handleCustomSymbolChange (value) { const customSymbol = value.trim() const symbolLength = customSymbol.length let customSymbolError = null if (symbolLength <= 0 || symbolLength >= 12) { customSymbolError = this.context.t('symbolBetweenZeroTwelve') } this.setState({ customSymbol, customSymbolError }) } handleCustomDecimalsChange (value) { const customDecimals = value.trim() const validDecimals = customDecimals !== null && customDecimals !== '' && customDecimals >= 0 && customDecimals <= 36 let customDecimalsError = null if (!validDecimals) { customDecimalsError = this.context.t('decimalsMustZerotoTen') } this.setState({ customDecimals, customDecimalsError }) } renderCustomTokenForm () { const { customAddress, customSymbol, customDecimals, customAddressError, customSymbolError, customDecimalsError, autoFilled, forceEditSymbol, } = this.state return (
this.handleCustomAddressChange(e.target.value)} error={customAddressError} fullWidth margin="normal" /> {this.context.t('tokenSymbol')} {(autoFilled && !forceEditSymbol) && (
this.setState({ forceEditSymbol: true })} > {this.context.t('edit')}
)}
)} type="text" value={customSymbol} onChange={(e) => this.handleCustomSymbolChange(e.target.value)} error={customSymbolError} fullWidth margin="normal" disabled={autoFilled && !forceEditSymbol} /> this.handleCustomDecimalsChange(e.target.value)} error={customDecimalsError} fullWidth margin="normal" disabled={autoFilled} /> ) } renderSearchToken () { const { tokenSelectorError, selectedTokens, searchResults } = this.state return (
this.setState({ searchResults: results })} error={tokenSelectorError} />
this.handleToggleToken(token)} />
) } renderTabs () { return ( { this.renderSearchToken() } { this.renderCustomTokenForm() } ) } render () { const { history, clearPendingTokens, mostRecentOverviewPage } = this.props return ( this.handleNext()} disabled={Boolean(this.hasError()) || !this.hasSelected()} onCancel={() => { clearPendingTokens() history.push(mostRecentOverviewPage) }} /> ) } } export default AddToken