Commit 3e299ff2 authored by Kenny Tran's avatar Kenny Tran Committed by GitHub

Merge pull request #85 from kennyt/design-feedback

Design feedback
parents 5987095e ae9fa925
<svg width="12" height="7" viewBox="0 0 12 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.4673 6L6.23364 1L0.999995 6" stroke="#388DFF"/>
</svg>
...@@ -4,12 +4,14 @@ ...@@ -4,12 +4,14 @@
@extend %col-nowrap; @extend %col-nowrap;
&__input { &__input {
font-size: .75rem; font-size: 1rem;
outline: none; outline: none;
border: none; border: none;
flex: 1 1 auto; flex: 1 1 auto;
width: 0; width: 0;
color: $royal-blue; color: $royal-blue;
overflow: hidden;
text-overflow: ellipsis;
&::placeholder { &::placeholder {
color: $chalice-gray; color: $chalice-gray;
......
...@@ -59,6 +59,15 @@ ...@@ -59,6 +59,15 @@
&--error { &--error {
color: $salmon-red; color: $salmon-red;
} }
&[type='number'] {
-moz-appearance:textfield;
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
} }
&__extra-text { &__extra-text {
......
...@@ -210,15 +210,16 @@ class CurrencyInputPanel extends Component { ...@@ -210,15 +210,16 @@ class CurrencyInputPanel extends Component {
exchangeAddresses: { fromToken }, exchangeAddresses: { fromToken },
web3, web3,
disableUnlock, disableUnlock,
value,
} = this.props; } = this.props;
if (disableUnlock || !selectedTokenAddress || selectedTokenAddress === 'ETH') { if (disableUnlock || !selectedTokenAddress || selectedTokenAddress === 'ETH') {
return; return;
} }
const { value, decimals, label } = selectors().getApprovals(selectedTokenAddress, account, fromToken[selectedTokenAddress]); const { value: allowance, decimals, label } = selectors().getApprovals(selectedTokenAddress, account, fromToken[selectedTokenAddress]);
if (!label || value.isGreaterThan(BN(10 ** 22))) { if (!label || allowance.isGreaterThanOrEqualTo(BN(value * 10 ** decimals || 0))) {
return; return;
} }
...@@ -272,11 +273,21 @@ class CurrencyInputPanel extends Component { ...@@ -272,11 +273,21 @@ class CurrencyInputPanel extends Component {
<div className="currency-input-panel__input-row"> <div className="currency-input-panel__input-row">
<input <input
type="number" type="number"
min="0"
className={classnames('currency-input-panel__input',{ className={classnames('currency-input-panel__input',{
'currency-input-panel__input--error': errorMessage, 'currency-input-panel__input--error': errorMessage,
})} })}
placeholder="0.0" placeholder="0.0"
onChange={e => onValueChange(e.target.value)} 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} value={value}
/> />
{ this.renderUnlockButton() } { this.renderUnlockButton() }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
&__top { &__top {
@extend %row-nowrap; @extend %row-nowrap;
padding: 1.5rem; padding: 1.5rem 1.5rem .5rem 1.5rem;
align-items: center; align-items: center;
} }
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
} }
&__navigation { &__navigation {
margin: 0 .75rem; margin-bottom: 2rem;
} }
&--inactive { &--inactive {
......
...@@ -121,11 +121,6 @@ function Header (props) { ...@@ -121,11 +121,6 @@ function Header (props) {
</div> </div>
<Web3Status isConnected /> <Web3Status isConnected />
</div> </div>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !props.isConnected,
})}
/>
</div> </div>
) )
} }
......
...@@ -3,6 +3,14 @@ import PropTypes from 'prop-types'; ...@@ -3,6 +3,14 @@ import PropTypes from 'prop-types';
import EthereumLogo from '../../assets/images/ethereum-logo.png'; import EthereumLogo from '../../assets/images/ethereum-logo.png';
import GenericTokenLogo from '../../assets/images/generic-token-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 TOKEN_ICON_API = 'https://raw.githubusercontent.com/TrustWallet/tokens/master/images';
const BAD_IMAGES = {}; const BAD_IMAGES = {};
export default class TokenLogo extends Component { export default class TokenLogo extends Component {
...@@ -14,7 +22,7 @@ export default class TokenLogo extends Component { ...@@ -14,7 +22,7 @@ export default class TokenLogo extends Component {
static defaultProps = { static defaultProps = {
address: '', address: '',
size: '1.5rem', size: '1rem',
className: '', className: '',
}; };
...@@ -25,13 +33,14 @@ export default class TokenLogo extends Component { ...@@ -25,13 +33,14 @@ export default class TokenLogo extends Component {
render() { render() {
const { address, size, className } = this.props; const { address, size, className } = this.props;
let path = GenericTokenLogo; let path = GenericTokenLogo;
const mainAddress = RINKEBY_TOKEN_MAP[address] ? RINKEBY_TOKEN_MAP[address] : address;
if (address === 'ETH') { if (mainAddress === 'ETH') {
path = EthereumLogo; path = EthereumLogo;
} }
if (!this.state.error && !BAD_IMAGES[address]) { if (!this.state.error && !BAD_IMAGES[mainAddress] && mainAddress !== 'ETH') {
path = `${TOKEN_ICON_API}/${address}.png`; path = `${TOKEN_ICON_API}/${mainAddress}.png`;
} }
return ( return (
...@@ -44,7 +53,7 @@ export default class TokenLogo extends Component { ...@@ -44,7 +53,7 @@ export default class TokenLogo extends Component {
}} }}
onError={() => { onError={() => {
this.setState({ error: true }); this.setState({ error: true });
BAD_IMAGES[address] = true; BAD_IMAGES[mainAddress] = true;
}} }}
/> />
); );
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
line-height: 1rem; line-height: 1rem;
align-items: center; align-items: center;
border: 1px solid $mercury-gray; border: 1px solid $mercury-gray;
padding: .5rem 1rem; padding: .5rem;
border-radius: 2rem; border-radius: 2rem;
color: $dove-gray; color: $dove-gray;
font-weight: 400; font-weight: 400;
......
...@@ -2,10 +2,15 @@ import React, { Component } from 'react'; ...@@ -2,10 +2,15 @@ import React, { Component } from 'react';
import { drizzleConnect } from 'drizzle-react'; import { drizzleConnect } from 'drizzle-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from "classnames"; import classnames from "classnames";
import { CSSTransitionGroup } from "react-transition-group";
import CurrencyInputPanel from '../../components/CurrencyInputPanel'; import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel'; import OversizedPanel from '../../components/OversizedPanel';
import NavigationTabs from '../../components/NavigationTabs';
import Modal from '../../components/Modal';
import { selectors, sync } from '../../ducks/web3connect'; import { selectors, sync } from '../../ducks/web3connect';
import ArrowDown from '../../assets/images/arrow-down-blue.svg'; import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import DropdownBlue from "../../assets/images/dropdown-blue.svg";
import DropupBlue from "../../assets/images/dropup-blue.svg";
import ModeSelector from './ModeSelector'; import ModeSelector from './ModeSelector';
import {BigNumber as BN} from 'bignumber.js'; import {BigNumber as BN} from 'bignumber.js';
import EXCHANGE_ABI from '../../abi/exchange'; import EXCHANGE_ABI from '../../abi/exchange';
...@@ -32,11 +37,12 @@ class AddLiquidity extends Component { ...@@ -32,11 +37,12 @@ class AddLiquidity extends Component {
inputCurrency: 'ETH', inputCurrency: 'ETH',
outputCurrency: '', outputCurrency: '',
lastEditedField: '', lastEditedField: '',
showSummaryModal: false,
}; };
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { isConnected, account, exchangeAddresses, balances, web3 } = this.props; const { isConnected, account, exchangeAddresses, balances, web3 } = this.props;
const { inputValue, outputValue, inputCurrency, outputCurrency, lastEditedField } = this.state; const { inputValue, outputValue, inputCurrency, outputCurrency, lastEditedField, showSummaryModal } = this.state;
return isConnected !== nextProps.isConnected || return isConnected !== nextProps.isConnected ||
account !== nextProps.account || account !== nextProps.account ||
...@@ -47,7 +53,8 @@ class AddLiquidity extends Component { ...@@ -47,7 +53,8 @@ class AddLiquidity extends Component {
outputValue !== nextState.outputValue || outputValue !== nextState.outputValue ||
inputCurrency !== nextState.inputCurrency || inputCurrency !== nextState.inputCurrency ||
outputCurrency !== nextState.outputCurrency || outputCurrency !== nextState.outputCurrency ||
lastEditedField !== nextState.lastEditedField; lastEditedField !== nextState.lastEditedField ||
showSummaryModal !== nextState.showSummaryModal;
} }
getBalance(currency) { getBalance(currency) {
...@@ -61,6 +68,27 @@ class AddLiquidity extends Component { ...@@ -61,6 +68,27 @@ class AddLiquidity extends Component {
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`; return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`;
} }
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { outputCurrency, outputValue } = this.state;
if (!outputCurrency) {
return false;
}
const { value: allowance, label, decimals } = selectors().getApprovals(
outputCurrency,
account,
exchangeAddresses.fromToken[outputCurrency]
);
if (label && allowance.isLessThan(BN(outputValue * 10 ** decimals || 0))) {
return true;
}
return false;
}
onAddLiquidity = async () => { onAddLiquidity = async () => {
const { account, web3, exchangeAddresses: { fromToken }, selectors } = this.props; const { account, web3, exchangeAddresses: { fromToken }, selectors } = this.props;
const { inputValue, outputValue, outputCurrency } = this.state; const { inputValue, outputValue, outputCurrency } = this.state;
...@@ -155,8 +183,10 @@ class AddLiquidity extends Component { ...@@ -155,8 +183,10 @@ class AddLiquidity extends Component {
let inputError; let inputError;
let outputError; let outputError;
let isValid = true; let isValid = true;
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency) { if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || this.isUnapproved()) {
isValid = false; isValid = false;
} }
...@@ -229,10 +259,12 @@ class AddLiquidity extends Component { ...@@ -229,10 +259,12 @@ class AddLiquidity extends Component {
inputCurrency, inputCurrency,
outputCurrency, outputCurrency,
} = this.state; } = this.state;
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
if (!inputCurrency || !outputCurrency) { if (!inputCurrency || !outputCurrency) {
return ( return (
<div className="swap__summary-wrapper"> <div key="summary" className="swap__summary-wrapper">
<div>Select a token to continue.</div> <div>Select a token to continue.</div>
</div> </div>
) )
...@@ -240,7 +272,7 @@ class AddLiquidity extends Component { ...@@ -240,7 +272,7 @@ class AddLiquidity extends Component {
if (inputCurrency === outputCurrency) { if (inputCurrency === outputCurrency) {
return ( return (
<div className="swap__summary-wrapper"> <div key="summary" className="swap__summary-wrapper">
<div>Must be different token.</div> <div>Must be different token.</div>
</div> </div>
) )
...@@ -248,22 +280,66 @@ class AddLiquidity extends Component { ...@@ -248,22 +280,66 @@ class AddLiquidity extends Component {
if (![inputCurrency, outputCurrency].includes('ETH')) { if (![inputCurrency, outputCurrency].includes('ETH')) {
return ( return (
<div className="swap__summary-wrapper"> <div key="summary" className="swap__summary-wrapper">
<div>One of the input must be ETH.</div> <div>One of the input must be ETH.</div>
</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]); const { value, decimals, label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
if (!inputValue || !outputValue) { if (!inputValue || !outputValue) {
return ( return (
<div className="swap__summary-wrapper"> <div key="summary" className="swap__summary-wrapper">
<div>{`Enter a ${inputCurrency} or ${label} value to continue.`}</div> <div>{`Enter a ${inputCurrency} or ${label} value to continue.`}</div>
</div> </div>
) )
} }
return [
<div
key="open-details"
className="swap__summary-wrapper swap__open-details-container"
onClick={() => this.setState({showSummaryModal: true})}
>
<span>Transaction Details</span>
<img src={DropdownBlue} />
</div>,
this.renderSummaryModal()
];
}
renderSummaryModal() {
const { selectors, exchangeAddresses: { fromToken } } = this.props;
const {
inputValue,
outputValue,
inputCurrency,
outputCurrency,
showSummaryModal,
} = this.state;
if (!showSummaryModal) {
return null;
}
const { value, decimals, label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
const SLIPPAGE = 0.025; const SLIPPAGE = 0.025;
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE); const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE); const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE);
...@@ -272,13 +348,34 @@ class AddLiquidity extends Component { ...@@ -272,13 +348,34 @@ class AddLiquidity extends Component {
const maxPercentage = maxOutput.dividedBy(maxOutput.plus(tokenReserve)).multipliedBy(100); const maxPercentage = maxOutput.dividedBy(maxOutput.plus(tokenReserve)).multipliedBy(100);
return ( return (
<div className="swap__summary-wrapper"> <Modal key="modal" onClose={() => this.setState({ showSummaryModal: false })}>
<div>You are adding between {b(`${minOutput.toFixed(2)} - ${maxOutput.toFixed(2)} ${label}`)} + {b(`${BN(inputValue).toFixed(2)} ETH`)} into the liquidity pool.</div> <CSSTransitionGroup
<div className="pool__last-summary-text"> transitionName="summary-modal"
You will receive between {b(`${minPercentage.toFixed(2)}%`)} and {b(`${maxPercentage.toFixed(2)}%`)} of the {`${label}/ETH`} pool tokens. transitionAppear={true}
</div> transitionLeave={true}
</div> transitionAppearTimeout={200}
) transitionLeaveTimeout={200}
transitionEnterTimeout={200}
>
<div className="swap__summary-modal">
<div
key="open-details"
className="swap__open-details-container"
onClick={() => this.setState({showSummaryModal: false})}
>
<span>Transaction Details</span>
<img src={DropupBlue} />
</div>
<div>
<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(5)}%`)} and {b(`${maxPercentage.toFixed(5)}%`)} of the {`${label}/ETH`} pool tokens.
</div>
</div>
</div>
</CSSTransitionGroup>
</Modal>
);
} }
render() { render() {
...@@ -296,12 +393,18 @@ class AddLiquidity extends Component { ...@@ -296,12 +393,18 @@ class AddLiquidity extends Component {
const { inputError, outputError, isValid } = this.validate(); const { inputError, outputError, isValid } = this.validate();
return ( return [
<div <div
key="content"
className={classnames('swap__content', { className={classnames('swap__content', {
'swap--inactive': !isConnected, 'swap--inactive': !isConnected,
})} })}
> >
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !isConnected,
})}
/>
<ModeSelector /> <ModeSelector />
<CurrencyInputPanel <CurrencyInputPanel
title="Deposit" title="Deposit"
...@@ -333,7 +436,6 @@ class AddLiquidity extends Component { ...@@ -333,7 +436,6 @@ class AddLiquidity extends Component {
<OversizedPanel hideBottom> <OversizedPanel hideBottom>
{ this.renderInfo() } { this.renderInfo() }
</OversizedPanel> </OversizedPanel>
{ this.renderSummary() }
<div className="pool__cta-container"> <div className="pool__cta-container">
<button <button
className={classnames('pool__cta-btn', { className={classnames('pool__cta-btn', {
...@@ -346,8 +448,9 @@ class AddLiquidity extends Component { ...@@ -346,8 +448,9 @@ class AddLiquidity extends Component {
Add Liquidity Add Liquidity
</button> </button>
</div> </div>
</div> </div>,
); this.renderSummary()
];
} }
} }
......
...@@ -3,11 +3,16 @@ import { drizzleConnect } from 'drizzle-react'; ...@@ -3,11 +3,16 @@ import { drizzleConnect } from 'drizzle-react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import {BigNumber as BN} from "bignumber.js"; import {BigNumber as BN} from "bignumber.js";
import { CSSTransitionGroup } from "react-transition-group";
import { selectors } from '../../ducks/web3connect'; import { selectors } from '../../ducks/web3connect';
import Header from '../../components/Header'; import Header from '../../components/Header';
import NavigationTabs from '../../components/NavigationTabs';
import AddressInputPanel from '../../components/AddressInputPanel'; import AddressInputPanel from '../../components/AddressInputPanel';
import CurrencyInputPanel from '../../components/CurrencyInputPanel'; import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import Modal from '../../components/Modal';
import OversizedPanel from '../../components/OversizedPanel'; import OversizedPanel from '../../components/OversizedPanel';
import DropdownBlue from "../../assets/images/dropdown-blue.svg";
import DropupBlue from "../../assets/images/dropup-blue.svg";
import ArrowDown from '../../assets/images/arrow-down-blue.svg'; import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import EXCHANGE_ABI from '../../abi/exchange'; import EXCHANGE_ABI from '../../abi/exchange';
...@@ -28,11 +33,12 @@ class Send extends Component { ...@@ -28,11 +33,12 @@ class Send extends Component {
state = { state = {
inputValue: '', inputValue: '',
outputValue: '', outputValue: '',
inputCurrency: '', inputCurrency: 'ETH',
outputCurrency: '', outputCurrency: '',
inputAmountB: '', inputAmountB: '',
lastEditedField: '', lastEditedField: '',
recipient: '', recipient: '',
showSummaryModal: false,
}; };
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
...@@ -48,6 +54,7 @@ class Send extends Component { ...@@ -48,6 +54,7 @@ class Send extends Component {
inputAmountB: '', inputAmountB: '',
lastEditedField: '', lastEditedField: '',
recipient: '', recipient: '',
showSummaryModal: false,
}); });
} }
...@@ -56,7 +63,7 @@ class Send extends Component { ...@@ -56,7 +63,7 @@ class Send extends Component {
} }
validate() { validate() {
const { selectors, account } = this.props; const { selectors, account, web3 } = this.props;
const { const {
inputValue, outputValue, inputValue, outputValue,
inputCurrency, outputCurrency, inputCurrency, outputCurrency,
...@@ -66,8 +73,11 @@ class Send extends Component { ...@@ -66,8 +73,11 @@ class Send extends Component {
let inputError = ''; let inputError = '';
let outputError = ''; let outputError = '';
let isValid = true; let isValid = true;
const validRecipientAddress = web3 && web3.utils.isAddress(recipient);
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency || !recipient) { if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || !recipient || this.isUnapproved() || !validRecipientAddress) {
isValid = false; isValid = false;
} }
...@@ -88,13 +98,40 @@ class Send extends Component { ...@@ -88,13 +98,40 @@ class Send extends Component {
}; };
} }
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { inputCurrency, inputValue } = this.state;
if (!inputCurrency || inputCurrency === 'ETH') {
return false;
}
const { value: allowance, label, decimals } = selectors().getApprovals(
inputCurrency,
account,
exchangeAddresses.fromToken[inputCurrency]
);
if (label && allowance.isLessThan(BN(inputValue * 10 ** decimals || 0))) {
return true;
}
return false;
}
recalcForm() { recalcForm() {
const { inputCurrency, outputCurrency } = this.state; const { inputCurrency, outputCurrency, lastEditedField } = this.state;
if (!inputCurrency || !outputCurrency) { if (!inputCurrency || !outputCurrency) {
return; return;
} }
const editedValue = lastEditedField === INPUT ? this.state.inputValue : this.state.outputValue;
if (BN(editedValue).isZero()) {
return;
}
if (inputCurrency === outputCurrency) { if (inputCurrency === outputCurrency) {
this.setState({ this.setState({
inputValue: '', inputValue: '',
...@@ -457,10 +494,14 @@ class Send extends Component { ...@@ -457,10 +494,14 @@ class Send extends Component {
outputError, outputError,
recipient, recipient,
} = this.state; } = this.state;
const { web3 } = this.props;
const { selectors, account } = this.props; const { selectors, account } = this.props;
const { label: inputLabel } = selectors().getBalance(account, inputCurrency); const { label: inputLabel } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel } = selectors().getBalance(account, outputCurrency); const { label: outputLabel } = selectors().getBalance(account, outputCurrency);
const validRecipientAddress = web3 && web3.utils.isAddress(recipient);
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
let nextStepMessage; let nextStepMessage;
if (inputError || outputError) { if (inputError || outputError) {
...@@ -472,8 +513,14 @@ class Send extends Component { ...@@ -472,8 +513,14 @@ class Send extends Component {
} else if (!inputValue || !outputValue) { } else if (!inputValue || !outputValue) {
const missingCurrencyValue = !inputValue ? inputLabel : outputLabel; const missingCurrencyValue = !inputValue ? inputLabel : outputLabel;
nextStepMessage = `Enter a ${missingCurrencyValue} value to continue.`; 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) { } else if (!recipient) {
nextStepMessage = 'Enter a wallet address to send to.'; nextStepMessage = 'Enter a wallet address to send to.';
} else if (!validRecipientAddress) {
nextStepMessage = 'Please enter a valid wallet address recipient.';
} }
if (nextStepMessage) { if (nextStepMessage) {
...@@ -484,20 +531,129 @@ class Send extends Component { ...@@ -484,20 +531,129 @@ class Send extends Component {
) )
} }
const SLIPPAGE = 0.025; return [
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(2); <div
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(2); key="open-details"
className="swap__summary-wrapper swap__open-details-container"
onClick={() => this.setState({showSummaryModal: true})}
>
<span>Transaction Details</span>
<img src={DropdownBlue} />
</div>,
this.renderSummaryModal()
];
}
return ( renderSummaryModal() {
<div className="swap__summary-wrapper"> const {
inputValue,
inputCurrency,
inputError,
outputValue,
outputCurrency,
outputError,
recipient,
showSummaryModal,
inputAmountB,
lastEditedField,
} = this.state;
const { selectors, account } = this.props;
if (!this.state.showSummaryModal) {
return null;
}
const ALLOWED_SLIPPAGE = 0.025;
const TOKEN_ALLOWED_SLIPPAGE = 0.04;
const type = getSendType(inputCurrency, outputCurrency);
const { label: inputLabel, decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel, decimals: outputDecimals } = selectors().getBalance(account, outputCurrency);
const label = lastEditedField === INPUT ? outputLabel : inputLabel;
let minOutput;
let maxInput;
if (lastEditedField === INPUT) {
switch(type) {
case 'ETH_TO_TOKEN':
minOutput = BN(outputValue).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(5)
break;
case 'TOKEN_TO_ETH':
minOutput = BN(outputValue).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(5);
break;
case 'TOKEN_TO_TOKEN':
minOutput = BN(outputValue).multipliedBy(1 - TOKEN_ALLOWED_SLIPPAGE).toFixed(5);
break;
default:
break;
}
}
if (lastEditedField === OUTPUT) {
switch (type) {
case 'ETH_TO_TOKEN':
maxInput = BN(inputValue).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(5);
break;
case 'TOKEN_TO_ETH':
maxInput = BN(inputValue).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(5);
break;
case 'TOKEN_TO_TOKEN':
maxInput = BN(inputValue).multipliedBy(1 + TOKEN_ALLOWED_SLIPPAGE).toFixed(5);
break;
default:
break;
}
}
let description;
if (lastEditedField === INPUT) {
description = (
<div> <div>
You are selling {b(`${inputValue} ${inputLabel}`)} <div>
You are selling {b(`${inputValue} ${inputLabel}`)}.
</div>
<div className="send__last-summary-text">
<span className="swap__highlight-text">{recipient.slice(0, 6)}</span> will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${outputValue} ${outputLabel}`)}.
</div>
</div> </div>
<div className="send__last-summary-text"> );
<span className="swap__highlight-text">{recipient.slice(0, 6)}</span> will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${maxOutput} ${outputLabel}`)} } else {
description = (
<div>
<div>
You are selling between {b(`${inputValue} ${inputLabel}`)} to {b(`${maxInput} ${inputLabel}`)}.
</div>
<div className="send__last-summary-text">
<span className="swap__highlight-text">{recipient.slice(0, 6)}</span> will receive {b(`${outputValue} ${outputLabel}`)}.
</div>
</div> </div>
</div> );
) }
return (
<Modal key="modal" onClose={() => this.setState({ showSummaryModal: false })}>
<CSSTransitionGroup
transitionName="summary-modal"
transitionAppear={true}
transitionLeave={true}
transitionAppearTimeout={200}
transitionLeaveTimeout={200}
transitionEnterTimeout={200}
>
<div className="swap__summary-modal">
<div
key="open-details"
className="swap__open-details-container"
onClick={() => this.setState({showSummaryModal: false})}
>
<span>Transaction Details</span>
<img src={DropupBlue} />
</div>
{description}
</div>
</CSSTransitionGroup>
</Modal>
);
} }
renderExchangeRate() { renderExchangeRate() {
...@@ -547,13 +703,18 @@ class Send extends Component { ...@@ -547,13 +703,18 @@ class Send extends Component {
const { inputError, outputError, isValid } = this.validate(); const { inputError, outputError, isValid } = this.validate();
return ( return (
<div className="swap"> <div className="send">
<Header /> <Header />
<div <div
className={classnames('swap__content', { className={classnames('swap__content', {
'swap--inactive': !this.props.isConnected, 'swap--inactive': !this.props.isConnected,
})} })}
> >
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !this.props.isConnected,
})}
/>
<CurrencyInputPanel <CurrencyInputPanel
title="Input" title="Input"
description={lastEditedField === OUTPUT ? estimatedText : ''} description={lastEditedField === OUTPUT ? estimatedText : ''}
...@@ -598,17 +759,19 @@ class Send extends Component { ...@@ -598,17 +759,19 @@ class Send extends Component {
onChange={address => this.setState({recipient: address})} onChange={address => this.setState({recipient: address})}
/> />
{ this.renderExchangeRate() } { 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> </div>
<button { this.renderSummary() }
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
disabled={!isValid}
onClick={this.onSend}
>
Send
</button>
</div> </div>
); );
} }
......
...@@ -4,9 +4,14 @@ import PropTypes from 'prop-types'; ...@@ -4,9 +4,14 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import {BigNumber as BN} from "bignumber.js"; import {BigNumber as BN} from "bignumber.js";
import { selectors } from '../../ducks/web3connect'; import { selectors } from '../../ducks/web3connect';
import { CSSTransitionGroup } from "react-transition-group";
import Header from '../../components/Header'; import Header from '../../components/Header';
import NavigationTabs from '../../components/NavigationTabs';
import Modal from '../../components/Modal';
import CurrencyInputPanel from '../../components/CurrencyInputPanel'; import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel'; import OversizedPanel from '../../components/OversizedPanel';
import DropdownBlue from "../../assets/images/dropdown-blue.svg";
import DropupBlue from "../../assets/images/dropup-blue.svg";
import ArrowDown from '../../assets/images/arrow-down-blue.svg'; import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import EXCHANGE_ABI from '../../abi/exchange'; import EXCHANGE_ABI from '../../abi/exchange';
...@@ -28,10 +33,11 @@ class Swap extends Component { ...@@ -28,10 +33,11 @@ class Swap extends Component {
state = { state = {
inputValue: '', inputValue: '',
outputValue: '', outputValue: '',
inputCurrency: '', inputCurrency: 'ETH',
outputCurrency: '', outputCurrency: '',
inputAmountB: '', inputAmountB: '',
lastEditedField: '', lastEditedField: '',
showSummaryModal: false,
}; };
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
...@@ -46,6 +52,7 @@ class Swap extends Component { ...@@ -46,6 +52,7 @@ class Swap extends Component {
outputCurrency: '', outputCurrency: '',
inputAmountB: '', inputAmountB: '',
lastEditedField: '', lastEditedField: '',
showSummaryModal: false,
}); });
} }
...@@ -63,8 +70,11 @@ class Swap extends Component { ...@@ -63,8 +70,11 @@ class Swap extends Component {
let inputError = ''; let inputError = '';
let outputError = ''; let outputError = '';
let isValid = true; let isValid = true;
let isUnapproved = this.isUnapproved();
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency) { if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || isUnapproved) {
isValid = false; isValid = false;
} }
...@@ -85,13 +95,40 @@ class Swap extends Component { ...@@ -85,13 +95,40 @@ class Swap extends Component {
}; };
} }
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { inputCurrency, inputValue } = this.state;
if (!inputCurrency || inputCurrency === 'ETH') {
return false;
}
const { value: allowance, label, decmals } = selectors().getApprovals(
inputCurrency,
account,
exchangeAddresses.fromToken[inputCurrency]
);
if (label && allowance.isLessThan(BN(inputValue * 10 ** decimals || 0))) {
return true;
}
return false;
}
recalcForm() { recalcForm() {
const { inputCurrency, outputCurrency } = this.state; const { inputCurrency, outputCurrency, lastEditedField } = this.state;
if (!inputCurrency || !outputCurrency) { if (!inputCurrency || !outputCurrency) {
return; return;
} }
const editedValue = lastEditedField === INPUT ? this.state.inputValue : this.state.outputValue;
if (BN(editedValue).isZero()) {
return;
}
if (inputCurrency === outputCurrency) { if (inputCurrency === outputCurrency) {
this.setState({ this.setState({
inputValue: '', inputValue: '',
...@@ -446,12 +483,8 @@ class Swap extends Component { ...@@ -446,12 +483,8 @@ class Swap extends Component {
outputCurrency, outputCurrency,
} = this.state; } = this.state;
const { selectors, account } = this.props; const inputIsZero = BN(inputValue).isZero();
const { label: inputLabel } = selectors().getBalance(account, inputCurrency); const outputIsZero = BN(outputValue).isZero();
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);
if (!inputCurrency || !outputCurrency) { if (!inputCurrency || !outputCurrency) {
return ( return (
...@@ -469,12 +502,144 @@ class Swap extends Component { ...@@ -469,12 +502,144 @@ 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
key="open-details"
className="swap__summary-wrapper swap__open-details-container"
onClick={() => this.setState({showSummaryModal: true})}
>
<span>Transaction Details</span>
<img src={DropdownBlue} />
</div>,
this.renderSummaryModal()
];
}
renderSummaryModal() {
const {
inputValue,
inputCurrency,
inputError,
outputValue,
outputCurrency,
outputError,
showSummaryModal,
inputAmountB,
lastEditedField,
} = this.state;
const { selectors, account } = this.props;
if (!this.state.showSummaryModal) {
return null;
}
const ALLOWED_SLIPPAGE = 0.025;
const TOKEN_ALLOWED_SLIPPAGE = 0.04;
const type = getSwapType(inputCurrency, outputCurrency);
const { label: inputLabel, decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel, decimals: outputDecimals } = selectors().getBalance(account, outputCurrency);
const label = lastEditedField === INPUT ? outputLabel : inputLabel;
let minOutput;
let maxInput;
if (lastEditedField === INPUT) {
switch(type) {
case 'ETH_TO_TOKEN':
minOutput = BN(outputValue).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(5)
break;
case 'TOKEN_TO_ETH':
minOutput = BN(outputValue).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(5);
break;
case 'TOKEN_TO_TOKEN':
minOutput = BN(outputValue).multipliedBy(1 - TOKEN_ALLOWED_SLIPPAGE).toFixed(5);
break;
default:
break;
}
}
if (lastEditedField === OUTPUT) {
switch (type) {
case 'ETH_TO_TOKEN':
maxInput = BN(inputValue).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(5);
break;
case 'TOKEN_TO_ETH':
maxInput = BN(inputValue).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(5);
break;
case 'TOKEN_TO_TOKEN':
maxInput = BN(inputValue).multipliedBy(1 + TOKEN_ALLOWED_SLIPPAGE).toFixed(5);
break;
default:
break;
}
}
let description;
if (lastEditedField === INPUT) {
description = (
<div>
<div>
You are selling {b(`${inputValue} ${inputLabel}`)}.
</div>
<div className="send__last-summary-text">
You will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${outputValue} ${outputLabel}`)}.
</div>
</div>
);
} else {
description = (
<div>
<div>
You are selling between {b(`${inputValue} ${inputLabel}`)} to {b(`${maxInput} ${inputLabel}`)}.
</div>
<div className="send__last-summary-text">
You will receive {b(`${outputValue} ${outputLabel}`)}.
</div>
</div>
);
}
return ( return (
<div className="swap__summary-wrapper"> <Modal key="modal" onClose={() => this.setState({ showSummaryModal: false })}>
<div>You are selling {b(`${inputValue} ${inputLabel}`)}</div> <CSSTransitionGroup
<div>You will receive between {b(minOutput)} and {b(`${maxOutput} ${outputLabel}`)}</div> transitionName="summary-modal"
</div> transitionAppear={true}
) transitionLeave={true}
transitionAppearTimeout={200}
transitionLeaveTimeout={200}
transitionEnterTimeout={200}
>
<div className="swap__summary-modal">
<div
key="open-details"
className="swap__open-details-container"
onClick={() => this.setState({showSummaryModal: false})}
>
<span>Transaction Details</span>
<img src={DropupBlue} />
</div>
{description}
</div>
</CSSTransitionGroup>
</Modal>
);
} }
renderExchangeRate() { renderExchangeRate() {
...@@ -530,6 +695,11 @@ class Swap extends Component { ...@@ -530,6 +695,11 @@ class Swap extends Component {
'swap--inactive': !this.props.isConnected, 'swap--inactive': !this.props.isConnected,
})} })}
> >
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !this.props.isConnected,
})}
/>
<CurrencyInputPanel <CurrencyInputPanel
title="Input" title="Input"
description={lastEditedField === OUTPUT ? estimatedText : ''} description={lastEditedField === OUTPUT ? estimatedText : ''}
...@@ -565,17 +735,19 @@ class Swap extends Component { ...@@ -565,17 +735,19 @@ class Swap extends Component {
disableUnlock disableUnlock
/> />
{ this.renderExchangeRate() } { 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> </div>
<button { this.renderSummary() }
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
disabled={!isValid}
onClick={this.onSwap}
>
Swap
</button>
</div> </div>
); );
} }
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
&__content { &__content {
padding: 1rem .75rem; padding: 1rem .75rem;
margin-top: 1rem;
flex: 1 1 auto; flex: 1 1 auto;
height: 0; height: 0;
overflow-y: auto; overflow-y: auto;
...@@ -46,16 +45,52 @@ ...@@ -46,16 +45,52 @@
} }
&__summary-wrapper { &__summary-wrapper {
margin: 2rem 1rem 0; margin: 2rem 1rem;
color: #737373; color: #737373;
font-size: .75rem; font-size: .75rem;
text-align: center; text-align: center;
} }
&__summary-modal {
background-color: $white;
position: relative;
bottom: 12rem;
height: 12rem;
z-index: 2000;
padding: 1rem;
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
transition: 250ms ease-in-out;
.swap__open-details-container {
padding: 0.5rem 0;
margin-bottom: 1rem;
}
}
&__open-details-container {
@extend %row-nowrap;
align-items: center;
justify-content: space-between;
padding: .625rem 1rem;
font-size: .75rem;
color: $royal-blue;
img {
height: .75rem;
width: .75rem;
}
}
&__highlight-text { &__highlight-text {
color: $royal-blue; color: $royal-blue;
} }
&__cta-container {
display: flex;
margin-top: 1.5rem;
}
&__cta-btn { &__cta-btn {
@extend %primary-button; @extend %primary-button;
margin: 1rem auto; margin: 1rem auto;
...@@ -69,3 +104,11 @@ ...@@ -69,3 +104,11 @@
margin-top: 5px; margin-top: 5px;
} }
} }
.summary-modal-appear {
bottom: 0;
}
.summary-modal-appear.modal-container-appear-active {
bottom: 0;
}
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