From 4f6b53c1aa383c05fa3c356adb332fcc6dbf45e9 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sat, 19 May 2018 23:04:19 -0700 Subject: Update designs for Add Token screen --- .../pages/add-token/add-token.component.js | 356 +++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 ui/app/components/pages/add-token/add-token.component.js (limited to 'ui/app/components/pages/add-token/add-token.component.js') diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js new file mode 100644 index 000000000..885c7b2ac --- /dev/null +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -0,0 +1,356 @@ +import React, { Component } from 'react' +import classnames from 'classnames' +import PropTypes from 'prop-types' +import ethUtil from 'ethereumjs-util' +import { checkExistingAddresses } from './util' +import { tokenInfoGetter } from '../../../token-util' +import { DEFAULT_ROUTE, CONFIRM_ADD_TOKEN_ROUTE } from '../../../routes' +import Button from '../../button' +import TextField from '../../text-field' +import TokenList from './token-list' +import TokenSearch from './token-search' + +const emptyAddr = '0x0000000000000000000000000000000000000000' +const SEARCH_TAB = 'SEARCH' +const CUSTOM_TOKEN_TAB = 'CUSTOM_TOKEN' + +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, + } + + constructor (props) { + super(props) + + this.state = { + customAddress: '', + customSymbol: '', + customDecimals: 0, + searchResults: [], + selectedTokens: {}, + tokenSelectorError: null, + customAddressError: null, + customSymbolError: null, + customDecimalsError: null, + autoFilled: false, + displayedTab: SEARCH_TAB, + } + } + + 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 + + const displayedTab = Object.keys(selectedTokens).length > 0 ? SEARCH_TAB : CUSTOM_TOKEN_TAB + this.setState({ selectedTokens, customAddress, customSymbol, customDecimals, displayedTab }) + } + } + + 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 } = await this.tokenInfoGetter(address) + + if (symbol && decimals) { + this.setState({ + customSymbol: symbol, + customDecimals: decimals, + customSymbolError: null, + customDecimalsError: null, + autoFilled: true, + }) + } + } + + 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 >= 10) { + customSymbolError = this.context.t('symbolBetweenZeroTen') + } + + 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, + } = this.state + + return ( +
+ this.handleCustomAddressChange(e.target.value)} + error={customAddressError} + fullWidth + margin="normal" + /> + this.handleCustomSymbolChange(e.target.value)} + error={customSymbolError} + fullWidth + margin="normal" + disabled={autoFilled} + /> + 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)} + /> +
+
+ ) + } + + render () { + const { displayedTab } = this.state + const { history, clearPendingTokens } = this.props + + return ( +
+
+
+ { this.context.t('addTokens') } +
+
+
this.setState({ displayedTab: SEARCH_TAB })} + > + { this.context.t('search') } +
+
this.setState({ displayedTab: CUSTOM_TOKEN_TAB })} + > + { this.context.t('customToken') } +
+
+
+
+ { + displayedTab === CUSTOM_TOKEN_TAB + ? this.renderCustomTokenForm() + : this.renderSearchToken() + } +
+
+ + +
+
+ ) + } +} + +export default AddToken -- cgit From c4e75a7075a2a7d4726475a37d11acb4b1eec5fa Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sun, 20 May 2018 14:08:45 -0700 Subject: Fix tests --- .../components/pages/add-token/add-token.component.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'ui/app/components/pages/add-token/add-token.component.js') diff --git a/ui/app/components/pages/add-token/add-token.component.js b/ui/app/components/pages/add-token/add-token.component.js index 885c7b2ac..0677b4317 100644 --- a/ui/app/components/pages/add-token/add-token.component.js +++ b/ui/app/components/pages/add-token/add-token.component.js @@ -139,17 +139,12 @@ class AddToken extends Component { } async attemptToAutoFillTokenParams (address) { - const { symbol, decimals } = await this.tokenInfoGetter(address) - - if (symbol && decimals) { - this.setState({ - customSymbol: symbol, - customDecimals: decimals, - customSymbolError: null, - customDecimalsError: null, - autoFilled: true, - }) - } + const { symbol = '', decimals = 0 } = await this.tokenInfoGetter(address) + + const autoFilled = Boolean(symbol && decimals) + this.setState({ autoFilled }) + this.handleCustomSymbolChange(symbol || '') + this.handleCustomDecimalsChange(decimals) } handleCustomAddressChange (value) { -- cgit