Implement feedback

This commit is contained in:
Kenny Tran 2018-10-26 13:51:36 -07:00
parent 5987095eea
commit 9d8c5330b8
11 changed files with 241 additions and 60 deletions

@ -4,12 +4,14 @@
@extend %col-nowrap;
&__input {
font-size: .75rem;
font-size: 1rem;
outline: none;
border: none;
flex: 1 1 auto;
width: 0;
color: $royal-blue;
overflow: hidden;
text-overflow: ellipsis;
&::placeholder {
color: $chalice-gray;

@ -59,6 +59,15 @@
&--error {
color: $salmon-red;
}
&[type='number'] {
-moz-appearance:textfield;
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
}
&__extra-text {

@ -272,11 +272,21 @@ class CurrencyInputPanel extends Component {
<div className="currency-input-panel__input-row">
<input
type="number"
min="0"
className={classnames('currency-input-panel__input',{
'currency-input-panel__input--error': errorMessage,
})}
placeholder="0.0"
onChange={e => onValueChange(e.target.value)}
onKeyPress={e => {
const charCode = e.which ? e.which : e.keyCode;
// Prevent 'minus' character
if (charCode === 45) {
e.preventDefault();
e.stopPropagation();
}
}}
value={value}
/>
{ this.renderUnlockButton() }

@ -5,7 +5,7 @@
&__top {
@extend %row-nowrap;
padding: 1.5rem;
padding: 1.5rem 1.5rem .5rem 1.5rem;
align-items: center;
}
@ -22,7 +22,7 @@
}
&__navigation {
margin: 0 .75rem;
margin-bottom: 2rem;
}
&--inactive {

@ -121,11 +121,6 @@ function Header (props) {
</div>
<Web3Status isConnected />
</div>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !props.isConnected,
})}
/>
</div>
)
}

@ -3,6 +3,14 @@ import PropTypes from 'prop-types';
import EthereumLogo from '../../assets/images/ethereum-logo.png';
import GenericTokenLogo from '../../assets/images/generic-token-logo.png';
const RINKEBY_TOKEN_MAP = {
'0xDA5B056Cfb861282B4b59d29c9B395bcC238D29B': '0x0d8775f648430679a709e98d2b0cb6250d2887ef',
'0x2448eE2641d78CC42D7AD76498917359D961A783': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',
'0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85': '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
'0x879884c3C46A24f56089f3bBbe4d5e38dB5788C0': '0xd26114cd6ee289accf82350c8d8487fedb8a0c07',
'0xF22e3F33768354c9805d046af3C0926f27741B43': '0xe41d2489571d322189246dafa5ebde1f4699f498',
};
const TOKEN_ICON_API = 'https://raw.githubusercontent.com/TrustWallet/tokens/master/images';
const BAD_IMAGES = {};
export default class TokenLogo extends Component {
@ -14,7 +22,7 @@ export default class TokenLogo extends Component {
static defaultProps = {
address: '',
size: '1.5rem',
size: '1rem',
className: '',
};
@ -25,13 +33,14 @@ export default class TokenLogo extends Component {
render() {
const { address, size, className } = this.props;
let path = GenericTokenLogo;
const mainAddress = RINKEBY_TOKEN_MAP[address] ? RINKEBY_TOKEN_MAP[address] : address;
if (address === 'ETH') {
if (mainAddress === 'ETH') {
path = EthereumLogo;
}
if (!this.state.error && !BAD_IMAGES[address]) {
path = `${TOKEN_ICON_API}/${address}.png`;
if (!this.state.error && !BAD_IMAGES[mainAddress] && mainAddress !== 'ETH') {
path = `${TOKEN_ICON_API}/${mainAddress}.png`;
}
return (
@ -44,7 +53,7 @@ export default class TokenLogo extends Component {
}}
onError={() => {
this.setState({ error: true });
BAD_IMAGES[address] = true;
BAD_IMAGES[mainAddress] = true;
}}
/>
);

@ -7,7 +7,7 @@
line-height: 1rem;
align-items: center;
border: 1px solid $mercury-gray;
padding: .5rem 1rem;
padding: .5rem;
border-radius: 2rem;
color: $dove-gray;
font-weight: 400;

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import classnames from "classnames";
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
import NavigationTabs from '../../components/NavigationTabs';
import { selectors, sync } from '../../ducks/web3connect';
import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import ModeSelector from './ModeSelector';
@ -61,6 +62,27 @@ class AddLiquidity extends Component {
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`;
}
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { outputCurrency } = this.state;
if (!outputCurrency) {
return false;
}
const { value, label } = selectors().getApprovals(
outputCurrency,
account,
exchangeAddresses.fromToken[outputCurrency]
);
if (!label || value.isLessThan(BN(10 ** 22))) {
return true;
}
return false;
}
onAddLiquidity = async () => {
const { account, web3, exchangeAddresses: { fromToken }, selectors } = this.props;
const { inputValue, outputValue, outputCurrency } = this.state;
@ -155,8 +177,10 @@ class AddLiquidity extends Component {
let inputError;
let outputError;
let isValid = true;
const inputIsZero = BN(inputValue).isEqualTo(BN(0));
const outputIsZero = BN(outputValue).isEqualTo(BN(0));
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency) {
if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || this.isUnapproved()) {
isValid = false;
}
@ -229,10 +253,12 @@ class AddLiquidity extends Component {
inputCurrency,
outputCurrency,
} = this.state;
const inputIsZero = BN(inputValue).isEqualTo(BN(0));
const outputIsZero = BN(outputValue).isEqualTo(BN(0));
if (!inputCurrency || !outputCurrency) {
return (
<div className="swap__summary-wrapper">
<div key="summary" className="swap__summary-wrapper">
<div>Select a token to continue.</div>
</div>
)
@ -240,7 +266,7 @@ class AddLiquidity extends Component {
if (inputCurrency === outputCurrency) {
return (
<div className="swap__summary-wrapper">
<div key="summary" className="swap__summary-wrapper">
<div>Must be different token.</div>
</div>
)
@ -248,17 +274,33 @@ class AddLiquidity extends Component {
if (![inputCurrency, outputCurrency].includes('ETH')) {
return (
<div className="swap__summary-wrapper">
<div key="summary" className="swap__summary-wrapper">
<div>One of the input must be ETH.</div>
</div>
)
}
if (inputIsZero || outputIsZero) {
return (
<div key="summary" className="swap__summary-wrapper">
<div>Amount cannot be zero.</div>
</div>
)
}
if (this.isUnapproved()) {
return (
<div key="summary" className="swap__summary-wrapper">
<div>Please unlock token to continue.</div>
</div>
)
}
const { value, decimals, label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
if (!inputValue || !outputValue) {
return (
<div className="swap__summary-wrapper">
<div key="summary" className="swap__summary-wrapper">
<div>{`Enter a ${inputCurrency} or ${label} value to continue.`}</div>
</div>
)
@ -272,10 +314,10 @@ class AddLiquidity extends Component {
const maxPercentage = maxOutput.dividedBy(maxOutput.plus(tokenReserve)).multipliedBy(100);
return (
<div className="swap__summary-wrapper">
<div>You are adding between {b(`${minOutput.toFixed(2)} - ${maxOutput.toFixed(2)} ${label}`)} + {b(`${BN(inputValue).toFixed(2)} ETH`)} into the liquidity pool.</div>
<div key="summary" className="swap__summary-wrapper">
<div>You are adding between {b(`${minOutput.toFixed(5)} - ${maxOutput.toFixed(5)} ${label}`)} + {b(`${BN(inputValue).toFixed(5)} ETH`)} into the liquidity pool.</div>
<div className="pool__last-summary-text">
You will receive between {b(`${minPercentage.toFixed(2)}%`)} and {b(`${maxPercentage.toFixed(2)}%`)} of the {`${label}/ETH`} pool tokens.
You will receive between {b(`${minPercentage.toFixed(5)}%`)} and {b(`${maxPercentage.toFixed(5)}%`)} of the {`${label}/ETH`} pool tokens.
</div>
</div>
)
@ -296,12 +338,18 @@ class AddLiquidity extends Component {
const { inputError, outputError, isValid } = this.validate();
return (
return [
<div
key="content"
className={classnames('swap__content', {
'swap--inactive': !isConnected,
})}
>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !isConnected,
})}
/>
<ModeSelector />
<CurrencyInputPanel
title="Deposit"
@ -333,7 +381,6 @@ class AddLiquidity extends Component {
<OversizedPanel hideBottom>
{ this.renderInfo() }
</OversizedPanel>
{ this.renderSummary() }
<div className="pool__cta-container">
<button
className={classnames('pool__cta-btn', {
@ -346,8 +393,9 @@ class AddLiquidity extends Component {
Add Liquidity
</button>
</div>
</div>
);
</div>,
this.renderSummary()
];
}
}

@ -5,6 +5,7 @@ import classnames from 'classnames';
import {BigNumber as BN} from "bignumber.js";
import { selectors } from '../../ducks/web3connect';
import Header from '../../components/Header';
import NavigationTabs from '../../components/NavigationTabs';
import AddressInputPanel from '../../components/AddressInputPanel';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
@ -28,7 +29,7 @@ class Send extends Component {
state = {
inputValue: '',
outputValue: '',
inputCurrency: '',
inputCurrency: 'ETH',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
@ -56,7 +57,7 @@ class Send extends Component {
}
validate() {
const { selectors, account } = this.props;
const { selectors, account, web3 } = this.props;
const {
inputValue, outputValue,
inputCurrency, outputCurrency,
@ -66,8 +67,11 @@ class Send extends Component {
let inputError = '';
let outputError = '';
let isValid = true;
const validRecipientAddress = web3 && web3.utils.isAddress(recipient);
const inputIsZero = BN(inputValue).isEqualTo(BN(0));
const outputIsZero = BN(outputValue).isEqualTo(BN(0));
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency || !recipient) {
if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || !recipient || this.isUnapproved() || !validRecipientAddress) {
isValid = false;
}
@ -88,13 +92,40 @@ class Send extends Component {
};
}
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { inputCurrency } = this.state;
if (!inputCurrency || inputCurrency === 'ETH') {
return false;
}
const { value, label } = selectors().getApprovals(
inputCurrency,
account,
exchangeAddresses.fromToken[inputCurrency]
);
if (!label || value.isLessThan(BN(10 ** 22))) {
return true;
}
return false;
}
recalcForm() {
const { inputCurrency, outputCurrency } = this.state;
const { inputCurrency, outputCurrency, lastEditedField } = this.state;
if (!inputCurrency || !outputCurrency) {
return;
}
const editedValue = lastEditedField === INPUT ? this.state.inputValue : this.state.outputValue;
if (BN(editedValue).isEqualTo(BN(0))) {
return;
}
if (inputCurrency === outputCurrency) {
this.setState({
inputValue: '',
@ -457,10 +488,14 @@ class Send extends Component {
outputError,
recipient,
} = this.state;
const { web3 } = this.props;
const { selectors, account } = this.props;
const { label: inputLabel } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel } = selectors().getBalance(account, outputCurrency);
const validRecipientAddress = web3 && web3.utils.isAddress(recipient);
const inputIsZero = BN(inputValue).isEqualTo(BN(0));
const outputIsZero = BN(outputValue).isEqualTo(BN(0));
let nextStepMessage;
if (inputError || outputError) {
@ -472,8 +507,14 @@ class Send extends Component {
} else if (!inputValue || !outputValue) {
const missingCurrencyValue = !inputValue ? inputLabel : outputLabel;
nextStepMessage = `Enter a ${missingCurrencyValue} value to continue.`;
} else if (inputIsZero || outputIsZero) {
nextStepMessage = 'Amount cannot be zero.';
} else if (this.isUnapproved()) {
nextStepMessage = 'Please unlock token to continue.';
} else if (!recipient) {
nextStepMessage = 'Enter a wallet address to send to.';
} else if (!validRecipientAddress) {
nextStepMessage = 'Please enter a valid wallet address recipient.';
}
if (nextStepMessage) {
@ -485,8 +526,8 @@ class Send extends Component {
}
const SLIPPAGE = 0.025;
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(2);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(2);
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(5);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(5);
return (
<div className="swap__summary-wrapper">
@ -547,13 +588,18 @@ class Send extends Component {
const { inputError, outputError, isValid } = this.validate();
return (
<div className="swap">
<div className="send">
<Header />
<div
className={classnames('swap__content', {
'swap--inactive': !this.props.isConnected,
})}
>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !this.props.isConnected,
})}
/>
<CurrencyInputPanel
title="Input"
description={lastEditedField === OUTPUT ? estimatedText : ''}
@ -598,17 +644,19 @@ class Send extends Component {
onChange={address => this.setState({recipient: address})}
/>
{ this.renderExchangeRate() }
{ this.renderSummary() }
<div className="swap__cta-container">
<button
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
disabled={!isValid}
onClick={this.onSend}
>
Send
</button>
</div>
</div>
<button
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
disabled={!isValid}
onClick={this.onSend}
>
Send
</button>
{ this.renderSummary() }
</div>
);
}

@ -5,6 +5,7 @@ import classnames from 'classnames';
import {BigNumber as BN} from "bignumber.js";
import { selectors } from '../../ducks/web3connect';
import Header from '../../components/Header';
import NavigationTabs from '../../components/NavigationTabs';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
import ArrowDown from '../../assets/images/arrow-down-blue.svg';
@ -28,7 +29,7 @@ class Swap extends Component {
state = {
inputValue: '',
outputValue: '',
inputCurrency: '',
inputCurrency: 'ETH',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
@ -63,8 +64,11 @@ class Swap extends Component {
let inputError = '';
let outputError = '';
let isValid = true;
let isUnapproved = this.isUnapproved();
const inputIsZero = BN(inputValue).isEqualTo(BN(0));
const outputIsZero = BN(outputValue).isEqualTo(BN(0));
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency) {
if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || isUnapproved) {
isValid = false;
}
@ -85,13 +89,40 @@ class Swap extends Component {
};
}
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { inputCurrency } = this.state;
if (!inputCurrency || inputCurrency === 'ETH') {
return false;
}
const { value, label } = selectors().getApprovals(
inputCurrency,
account,
exchangeAddresses.fromToken[inputCurrency]
);
if (!label || value.isLessThan(BN(10 ** 22))) {
return true;
}
return false;
}
recalcForm() {
const { inputCurrency, outputCurrency } = this.state;
const { inputCurrency, outputCurrency, lastEditedField } = this.state;
if (!inputCurrency || !outputCurrency) {
return;
}
const editedValue = lastEditedField === INPUT ? this.state.inputValue : this.state.outputValue;
if (BN(editedValue).isEqualTo(BN(0))) {
return;
}
if (inputCurrency === outputCurrency) {
this.setState({
inputValue: '',
@ -450,8 +481,10 @@ class Swap extends Component {
const { label: inputLabel } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel } = selectors().getBalance(account, outputCurrency);
const SLIPPAGE = 0.025;
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(2);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(2);
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(5);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(5);
const inputIsZero = BN(inputValue).isEqualTo(BN(0));
const outputIsZero = BN(outputValue).isEqualTo(BN(0));
if (!inputCurrency || !outputCurrency) {
return (
@ -469,6 +502,22 @@ class Swap extends Component {
)
}
if (inputIsZero || outputIsZero) {
return (
<div className="swap__summary-wrapper">
<div>Amount cannot be zero.</div>
</div>
)
}
if (this.isUnapproved()) {
return (
<div className="swap__summary-wrapper">
<div>Please unlock token to continue.</div>
</div>
);
}
return (
<div className="swap__summary-wrapper">
<div>You are selling {b(`${inputValue} ${inputLabel}`)}</div>
@ -530,6 +579,11 @@ class Swap extends Component {
'swap--inactive': !this.props.isConnected,
})}
>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !this.props.isConnected,
})}
/>
<CurrencyInputPanel
title="Input"
description={lastEditedField === OUTPUT ? estimatedText : ''}
@ -565,17 +619,19 @@ class Swap extends Component {
disableUnlock
/>
{ this.renderExchangeRate() }
{ this.renderSummary() }
<div className="swap__cta-container">
<button
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
disabled={!isValid}
onClick={this.onSwap}
>
Swap
</button>
</div>
</div>
<button
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
disabled={!isValid}
onClick={this.onSwap}
>
Swap
</button>
{ this.renderSummary() }
</div>
);
}

@ -11,7 +11,6 @@
&__content {
padding: 1rem .75rem;
margin-top: 1rem;
flex: 1 1 auto;
height: 0;
overflow-y: auto;
@ -46,7 +45,7 @@
}
&__summary-wrapper {
margin: 2rem 1rem 0;
margin: 2rem 1rem;
color: #737373;
font-size: .75rem;
text-align: center;
@ -56,6 +55,11 @@
color: $royal-blue;
}
&__cta-container {
display: flex;
margin-top: 1.5rem;
}
&__cta-btn {
@extend %primary-button;
margin: 1rem auto;