import * as _ from 'lodash'; import TextField from 'material-ui/TextField'; import * as React from 'react'; import { Blockchain } from 'ts/blockchain'; import { AddressInput } from 'ts/components/inputs/address_input'; import { Alert } from 'ts/components/ui/alert'; import { LifeCycleRaisedButton } from 'ts/components/ui/lifecycle_raised_button'; import { RequiredLabel } from 'ts/components/ui/required_label'; import { AlertTypes, Token, TokenByAddress } from 'ts/types'; import { colors } from 'ts/utils/colors'; interface NewTokenFormProps { blockchain: Blockchain; tokenByAddress: TokenByAddress; onNewTokenSubmitted: (token: Token) => void; } interface NewTokenFormState { globalErrMsg: string; name: string; nameErrText: string; symbol: string; symbolErrText: string; address: string; shouldShowAddressIncompleteErr: boolean; decimals: string; decimalsErrText: string; } export class NewTokenForm extends React.Component { constructor(props: NewTokenFormProps) { super(props); this.state = { address: '', globalErrMsg: '', name: '', nameErrText: '', shouldShowAddressIncompleteErr: false, symbol: '', symbolErrText: '', decimals: '18', decimalsErrText: '', }; } public render() { return (
} value={this.state.name} errorText={this.state.nameErrText} onChange={this._onTokenNameChanged.bind(this)} />
} value={this.state.symbol} errorText={this.state.symbolErrText} onChange={this._onTokenSymbolChanged.bind(this)} />
} value={this.state.decimals} errorText={this.state.decimalsErrText} onChange={this._onTokenDecimalsChanged.bind(this)} />
{this.state.globalErrMsg !== '' && }
); } private async _onAddNewTokenClickAsync() { // Trigger validation of name and symbol this._onTokenNameChanged(undefined, this.state.name); this._onTokenSymbolChanged(undefined, this.state.symbol); this._onTokenDecimalsChanged(undefined, this.state.decimals); const isAddressIncomplete = this.state.address === ''; let doesContractExist = false; if (!isAddressIncomplete) { doesContractExist = await this.props.blockchain.doesContractExistAtAddressAsync(this.state.address); } let hasBalanceAllowanceErr = false; if (doesContractExist) { try { await this.props.blockchain.getCurrentUserTokenBalanceAndAllowanceAsync(this.state.address); } catch (err) { hasBalanceAllowanceErr = true; } } let globalErrMsg = ''; if ( this.state.nameErrText !== '' || this.state.symbolErrText !== '' || this.state.decimalsErrText !== '' || isAddressIncomplete ) { globalErrMsg = 'Please fix the above issues'; } else if (!doesContractExist) { globalErrMsg = 'No contract found at supplied address'; } else if (hasBalanceAllowanceErr) { globalErrMsg = 'Unsuccessful call to `balanceOf` and/or `allowance` on supplied contract address'; } else if (!isAddressIncomplete && !_.isUndefined(this.props.tokenByAddress[this.state.address])) { globalErrMsg = 'A token already exists with this address'; } if (globalErrMsg !== '') { this.setState({ globalErrMsg, shouldShowAddressIncompleteErr: isAddressIncomplete, }); return; } const newToken: Token = { address: this.state.address, decimals: _.parseInt(this.state.decimals), iconUrl: undefined, name: this.state.name, symbol: this.state.symbol.toUpperCase(), isTracked: true, isRegistered: false, }; this.props.onNewTokenSubmitted(newToken); } private _onTokenNameChanged(e: any, name: string) { let nameErrText = ''; const maxLength = 30; const tokens = _.values(this.props.tokenByAddress); const tokenWithNameIfExists = _.find(tokens, { name }); const tokenWithNameExists = !_.isUndefined(tokenWithNameIfExists); if (name === '') { nameErrText = 'Name is required'; } else if (!this._isValidName(name)) { nameErrText = 'Name should only contain letters, digits and spaces'; } else if (name.length > maxLength) { nameErrText = `Max length is ${maxLength}`; } else if (tokenWithNameExists) { nameErrText = 'Token with this name already exists'; } this.setState({ name, nameErrText, }); } private _onTokenSymbolChanged(e: any, symbol: string) { let symbolErrText = ''; const maxLength = 5; const tokens = _.values(this.props.tokenByAddress); const tokenWithSymbolExists = !_.isUndefined(_.find(tokens, { symbol })); if (symbol === '') { symbolErrText = 'Symbol is required'; } else if (!this._isAlphanumeric(symbol)) { symbolErrText = 'Can only include alphanumeric characters'; } else if (symbol.length > maxLength) { symbolErrText = `Max length is ${maxLength}`; } else if (tokenWithSymbolExists) { symbolErrText = 'Token with symbol already exists'; } this.setState({ symbol, symbolErrText, }); } private _onTokenDecimalsChanged(e: any, decimals: string) { let decimalsErrText = ''; const maxLength = 2; if (decimals === '') { decimalsErrText = 'Decimals is required'; } else if (!this._isInteger(decimals)) { decimalsErrText = 'Must be an integer'; } else if (decimals.length > maxLength) { decimalsErrText = `Max length is ${maxLength}`; } this.setState({ decimals, decimalsErrText, }); } private _onTokenAddressChanged(address?: string) { if (!_.isUndefined(address)) { this.setState({ address, }); } } private _isValidName(input: string) { return /^[a-z0-9 ]+$/i.test(input); } private _isInteger(input: string) { return /^[0-9]+$/i.test(input); } private _isAlphanumeric(input: string) { return /^[a-zA-Z0-9]+$/i.test(input); } }