Commit 6c168635 authored by Kenny Tran's avatar Kenny Tran Committed by Chi Kei Chan

Create error state for currency input (#43)

* Create error state for currency input

* Create error functionality for redux

* Add proptypes and convert to float before compare

* Use big number for balance comparison
parent c691cfab
......@@ -7,9 +7,13 @@
position: relative;
z-index: 200;
border-radius: 1.25rem;
border: 1px solid $mercury-gray;
border: 0.5px solid $mercury-gray;
background-color: $white;
box-shadow: 0px 4px 4px 2px rgba($royal-blue, 0.05);
&--error {
border: 0.5px solid $salmon-red;
}
}
&__label-row {
......@@ -42,6 +46,15 @@
&__input {
@extend %borderless-input;
&--error {
color: $salmon-red;
}
}
&__extra-text {
&--error {
color: $salmon-red;
}
}
&__currency-select {
......
......@@ -6,6 +6,7 @@ import classnames from 'classnames';
import {BigNumber as BN} from 'bignumber.js';
import Fuse from '../../helpers/fuse';
import { updateField } from '../../ducks/swap';
import { INSUFFICIENT_BALANCE } from '../../constants/currencyInputErrorTypes';
import Modal from '../Modal';
import TokenLogo from '../TokenLogo';
import SearchIcon from '../../assets/images/magnifying-glass.svg';
......@@ -41,11 +42,17 @@ class CurrencyInputPanel extends Component {
exchangeAddresses: PropTypes.shape({
fromToken: PropTypes.object.isRequired,
}).isRequired,
errors: PropTypes.arrayOf(PropTypes.string),
addError: PropTypes.func,
removeError: PropTypes.func,
};
static defaultProps = {
onCurrencySelected() {},
onValueChange() {},
addError() {},
removeError() {},
errors: [],
};
static contextTypes = {
......@@ -99,24 +106,24 @@ class CurrencyInputPanel extends Component {
const { web3 } = drizzle;
if (!selectedTokenAddress || !initialized || !web3 || !balance) {
return '';
return null;
}
if (selectedTokenAddress === 'ETH') {
return `Balance: ${BN(web3.utils.fromWei(balance, 'ether')).toFixed(2)}`;
return BN(web3.utils.fromWei(balance, 'ether')).toFixed(2);
}
const tokenData = this.getTokenData(selectedTokenAddress);
if (!tokenData) {
return '';
return null;
}
const tokenBalance = BN(tokenData.balance);
const denomination = Math.pow(10, tokenData.decimals);
const adjustedBalance = tokenBalance.dividedBy(denomination);
return `Balance: ${adjustedBalance.toFixed(2)}`;
return adjustedBalance.toFixed(2);
}
createTokenList = () => {
......@@ -135,6 +142,18 @@ class CurrencyInputPanel extends Component {
return tokenList;
};
validate = (balance) => {
const { value, addError, removeError, errors } = this.props;
const hasInsufficientBalance = errors.indexOf(INSUFFICIENT_BALANCE) > -1;
const balanceIsLess = BN(value).isGreaterThan(BN(balance));
if (balanceIsLess && !hasInsufficientBalance) {
addError(INSUFFICIENT_BALANCE);
} else if (!balanceIsLess && hasInsufficientBalance) {
removeError(INSUFFICIENT_BALANCE);
}
};
onTokenSelect = (address) => {
this.setState({
selectedTokenAddress: address || 'ETH',
......@@ -178,6 +197,14 @@ class CurrencyInputPanel extends Component {
}
};
renderBalance(balance) {
if (balance === null) {
return null;
}
return `Balance: ${balance}`;
}
renderTokenList() {
const tokens = this.createTokenList();
const { searchQuery } = this.state;
......@@ -250,26 +277,43 @@ class CurrencyInputPanel extends Component {
const {
title,
description,
value,
errors
} = this.props;
const { selectedTokenAddress } = this.state;
const balance = this.getBalance();
this.validate(balance);
const hasInsufficientBalance = errors.indexOf(INSUFFICIENT_BALANCE) > -1;
return (
<div className="currency-input-panel">
<div className="currency-input-panel__container">
<div className={
classnames('currency-input-panel__container',
{ 'currency-input-panel__container--error': hasInsufficientBalance }
)
}>
<div className="currency-input-panel__label-row">
<div className="currency-input-panel__label-container">
<span className="currency-input-panel__label">{title}</span>
<span className="currency-input-panel__label-description">{description}</span>
</div>
<span className="currency-input-panel__extra-text">
{this.getBalance()}
<span className={
classnames('currency-input-panel__extra-text',
{ 'currency-input-panel__extra-text--error': hasInsufficientBalance }
)
}>
{this.renderBalance(balance)}
</span>
</div>
<div className="currency-input-panel__input-row">
<input
type="number"
className="currency-input-panel__input"
className={
classnames('currency-input-panel__input',
{ 'currency-input-panel__input--error': hasInsufficientBalance }
)
}
placeholder="0.0"
onChange={e => this.props.onValueChange(e.target.value)}
value={this.props.value}
......
export const INSUFFICIENT_BALANCE = 'Insufficient balance';
import {
EXCHANGE_CONTRACT_READY
} from '../constants'
} from '../constants/actionTypes';
// definitely needs to be redux thunk
export const exchangeContractReady = (symbol, exchangeContract) => ({
......
......@@ -24,7 +24,7 @@ import {
SET_INVEST_ETH_REQUIRED,
SET_INVEST_TOKENS_REQUIRED,
SET_INVEST_CHECKED
} from '../constants';
} from '../constants/actionTypes';
export const setInputBalance = (inputBalance) => ({
......
const UPDATE_FIELD = 'app/swap/updateField';
const ADD_ERROR = 'app/swap/addError';
const REMOVE_ERROR = 'app/swap/removeError';
const initialState = {
input: '',
......@@ -6,12 +8,46 @@ const initialState = {
inputCurrency: '',
outputCurrency: '',
lastEditedField: '',
inputErrors: [],
outputErrors: [],
};
export const updateField = ({ name, value }) => ({
type: UPDATE_FIELD,
payload: { name, value },
})
});
export const addError = ({ name, value }) => ({
type: ADD_ERROR,
payload: { name, value },
});
export const removeError = ({ name, value }) => ({
type: REMOVE_ERROR,
payload: { name, value },
});
function reduceAddError(state, payload) {
const { name, value } = payload;
let nextErrors = state[name];
if (nextErrors.indexOf(value) === -1) {
nextErrors = [...nextErrors, value];
}
return {
...state,
[name]: nextErrors,
};
}
function reduceRemoveError(state, payload) {
const { name, value } = payload;
return {
...state,
[name]: state[name].filter(error => error !== value),
};
}
export default function swapReducer(state = initialState, { type, payload }) {
switch (type) {
......@@ -20,7 +56,11 @@ export default function swapReducer(state = initialState, { type, payload }) {
...state,
[payload.name]: payload.value,
};
case ADD_ERROR:
return reduceAddError(state, payload);
case REMOVE_ERROR:
return reduceRemoveError(state, payload);
default:
return state;
}
}
\ No newline at end of file
}
import {
TOKEN_CONTRACT_READY
} from '../constants';
} from '../constants/actionTypes';
// again, needs to be redux thunk
export const tokenContractReady = (symbol, tokenContract) => ({
......
......@@ -4,7 +4,7 @@ import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {BigNumber as BN} from "bignumber.js";
import { updateField } from '../../ducks/swap';
import { updateField, addError, removeError } from '../../ducks/swap';
import Header from '../../components/Header';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
......@@ -32,6 +32,8 @@ class Swap extends Component {
inputCurrency: PropTypes.string,
outputCurrency: PropTypes.string,
lastEditedField: PropTypes.string,
inputErrors: PropTypes.arrayOf(PropTypes.string),
outputErrors: PropTypes.arrayOf(PropTypes.string),
};
static contextTypes = {
......@@ -192,7 +194,7 @@ class Swap extends Component {
};
render() {
const { lastEditedField, inputCurrency, outputCurrency, input, output } = this.props;
const { lastEditedField, inputCurrency, outputCurrency, input, output, outputErrors, inputErrors } = this.props;
const { exchangeRate } = this.state;
const inputLabel = this.getTokenLabel(inputCurrency);
const outputLabel = this.getTokenLabel(outputCurrency);
......@@ -212,6 +214,9 @@ class Swap extends Component {
onCurrencySelected={d => this.props.updateField('inputCurrency', d)}
onValueChange={d => this.updateInput(d)}
selectedTokens={[inputCurrency, outputCurrency]}
addError={error => this.props.addError('inputErrors', error)}
removeError={error => this.props.removeError('inputErrors', error)}
errors={inputErrors}
value={input}
/>
<OversizedPanel>
......@@ -225,6 +230,9 @@ class Swap extends Component {
onCurrencySelected={d => this.props.updateField('outputCurrency', d)}
onValueChange={d => this.updateOutput(d)}
selectedTokens={[inputCurrency, outputCurrency]}
addError={error => this.props.addError('outputErrors', error)}
removeError={error => this.props.removeError('outputErrors', error)}
errors={outputErrors}
value={output}
/>
<OversizedPanel hideBottom>
......@@ -283,9 +291,13 @@ export default withRouter(
outputCurrency: state.swap.outputCurrency,
lastEditedField: state.swap.lastEditedField,
exchangeAddresses: state.addresses.exchangeAddresses,
inputErrors: state.swap.inputErrors,
outputErrors: state.swap.outputErrors,
}),
dispatch => ({
updateField: (name, value) => dispatch(updateField({ name, value })),
addError: (name, value) => dispatch(addError({ name, value })),
removeError: (name, value) => dispatch(removeError({ name, value }))
})
),
);
......@@ -16,6 +16,9 @@ $royal-blue: #2F80ED;
// Purple
$wisteria-purple: #AE60B9;
// Red
$salmon-red: #FF8368;
%col-nowrap {
display: flex;
flex-flow: column nowrap;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment