161 lines
4.3 KiB
TypeScript
161 lines
4.3 KiB
TypeScript
import React from 'react';
|
|
import classnames from 'classnames';
|
|
import { Token, HELP_ARTICLE } from 'config';
|
|
import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators';
|
|
import translate from 'translations';
|
|
import { HelpLink } from 'components/ui';
|
|
import './AddCustomTokenForm.scss';
|
|
|
|
interface Props {
|
|
allTokens: Token[];
|
|
onSave(params: Token): void;
|
|
toggleForm(): void;
|
|
}
|
|
|
|
interface IGenerateSymbolLookup {
|
|
[tokenSymbol: string]: boolean;
|
|
}
|
|
|
|
interface State {
|
|
tokenSymbolLookup: IGenerateSymbolLookup;
|
|
address: string;
|
|
symbol: string;
|
|
decimal: string;
|
|
}
|
|
|
|
export default class AddCustomTokenForm extends React.PureComponent<Props, State> {
|
|
public state: State = {
|
|
tokenSymbolLookup: {},
|
|
address: '',
|
|
symbol: '',
|
|
decimal: ''
|
|
};
|
|
|
|
constructor(props: Props) {
|
|
super(props);
|
|
this.state = {
|
|
...this.state,
|
|
tokenSymbolLookup: this.generateSymbolLookup(props.allTokens)
|
|
};
|
|
}
|
|
|
|
public render() {
|
|
const { address, symbol, decimal } = this.state;
|
|
const inputClasses = 'AddCustom-field-input form-control input-sm';
|
|
const errors = this.getErrors();
|
|
|
|
const fields = [
|
|
{
|
|
name: 'symbol',
|
|
value: symbol,
|
|
label: translate('TOKEN_Symbol')
|
|
},
|
|
{
|
|
name: 'address',
|
|
value: address,
|
|
label: translate('TOKEN_Addr')
|
|
},
|
|
{
|
|
name: 'decimal',
|
|
value: decimal,
|
|
label: translate('TOKEN_Dec')
|
|
}
|
|
];
|
|
|
|
return (
|
|
<form className="AddCustom" onSubmit={this.onSave}>
|
|
{fields.map(field => {
|
|
return (
|
|
<label className="AddCustom-field form-group" key={field.name}>
|
|
<span className="AddCustom-field-label">{field.label}</span>
|
|
<input
|
|
className={classnames(
|
|
inputClasses,
|
|
errors[field.name] ? 'is-invalid' : field.value ? 'is-valid' : ''
|
|
)}
|
|
type="text"
|
|
name={field.name}
|
|
value={field.value}
|
|
onChange={this.onFieldChange}
|
|
/>
|
|
{typeof errors[field.name] === 'string' && (
|
|
<div className="AddCustom-field-error">{errors[field.name]}</div>
|
|
)}
|
|
</label>
|
|
);
|
|
})}
|
|
|
|
<div className="AddCustom-buttons">
|
|
<HelpLink article={HELP_ARTICLE.ADDING_NEW_TOKENS} className="AddCustom-buttons-help">
|
|
{translate('Need help? Learn how to add custom tokens.')}
|
|
</HelpLink>
|
|
<button
|
|
className="AddCustom-buttons-btn btn btn-primary btn-sm"
|
|
disabled={!this.isValid()}
|
|
>
|
|
{translate('x_Save')}
|
|
</button>
|
|
<button
|
|
className="AddCustom-buttons-btn btn btn-sm btn-default"
|
|
onClick={this.props.toggleForm}
|
|
>
|
|
{translate('x_Cancel')}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
public getErrors() {
|
|
const { address, symbol, decimal } = this.state;
|
|
const errors: { [key: string]: boolean | string } = {};
|
|
|
|
// Formatting errors
|
|
if (decimal && !isPositiveIntegerOrZero(parseInt(decimal, 10))) {
|
|
errors.decimal = true;
|
|
}
|
|
if (address && !isValidETHAddress(address)) {
|
|
errors.address = true;
|
|
}
|
|
|
|
// Message errors
|
|
if (symbol && this.state.tokenSymbolLookup[symbol]) {
|
|
errors.symbol = 'A token with this symbol already exists';
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
public isValid() {
|
|
const { address, symbol, decimal } = this.state;
|
|
return !Object.keys(this.getErrors()).length && address && symbol && decimal;
|
|
}
|
|
|
|
public onFieldChange = (e: React.FormEvent<HTMLInputElement>) => {
|
|
// TODO: typescript bug: https://github.com/Microsoft/TypeScript/issues/13948
|
|
const name: any = e.currentTarget.name;
|
|
const value = e.currentTarget.value;
|
|
this.setState({ [name]: value });
|
|
};
|
|
|
|
public onSave = (ev: React.FormEvent<HTMLFormElement>) => {
|
|
ev.preventDefault();
|
|
if (!this.isValid()) {
|
|
return;
|
|
}
|
|
|
|
const { address, symbol, decimal } = this.state;
|
|
this.props.onSave({ address, symbol, decimal: parseInt(decimal, 10) });
|
|
};
|
|
|
|
private generateSymbolLookup(tokens: Token[]) {
|
|
return tokens.reduce(
|
|
(prev, tk) => {
|
|
prev[tk.symbol] = true;
|
|
return prev;
|
|
},
|
|
{} as IGenerateSymbolLookup
|
|
);
|
|
}
|
|
}
|