Commit 57a05471 authored by Chi Kei Chan's avatar Chi Kei Chan

Refactor CurrencyInputPanel

parent f44d0749
...@@ -3,10 +3,7 @@ import { drizzleConnect } from 'drizzle-react' ...@@ -3,10 +3,7 @@ import { drizzleConnect } from 'drizzle-react'
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { CSSTransitionGroup } from "react-transition-group"; import { CSSTransitionGroup } from "react-transition-group";
import classnames from 'classnames'; import classnames from 'classnames';
import {BigNumber as BN} from 'bignumber.js';
import Fuse from '../../helpers/fuse'; import Fuse from '../../helpers/fuse';
import { updateField } from '../../ducks/swap';
import { INSUFFICIENT_BALANCE } from '../../constants/currencyInputErrorTypes';
import Modal from '../Modal'; import Modal from '../Modal';
import TokenLogo from '../TokenLogo'; import TokenLogo from '../TokenLogo';
import SearchIcon from '../../assets/images/magnifying-glass.svg'; import SearchIcon from '../../assets/images/magnifying-glass.svg';
...@@ -35,29 +32,24 @@ class CurrencyInputPanel extends Component { ...@@ -35,29 +32,24 @@ class CurrencyInputPanel extends Component {
static propTypes = { static propTypes = {
title: PropTypes.string, title: PropTypes.string,
description: PropTypes.string, description: PropTypes.string,
extraText: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
initialized: PropTypes.bool,
onCurrencySelected: PropTypes.func, onCurrencySelected: PropTypes.func,
onValueChange: PropTypes.func, onValueChange: PropTypes.func,
tokenAddresses: PropTypes.shape({
address: PropTypes.array.isRequired,
}).isRequired,
exchangeAddresses: PropTypes.shape({ exchangeAddresses: PropTypes.shape({
fromToken: PropTypes.object.isRequired, fromToken: PropTypes.object.isRequired,
}).isRequired, }).isRequired,
errors: PropTypes.arrayOf(PropTypes.string), selectedTokens: PropTypes.array.isRequired,
addError: PropTypes.func, errorMessage: PropTypes.string,
removeError: PropTypes.func,
showSubButton: PropTypes.bool,
subButtonContent: PropTypes.node,
onSubButtonClick: PropTypes.func,
shouldValidateBalance: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
selectedTokens: [],
onCurrencySelected() {}, onCurrencySelected() {},
onSubButtonClick() {},
onValueChange() {}, onValueChange() {},
addError() {},
removeError() {},
errors: [],
}; };
static contextTypes = { static contextTypes = {
...@@ -70,67 +62,6 @@ class CurrencyInputPanel extends Component { ...@@ -70,67 +62,6 @@ class CurrencyInputPanel extends Component {
selectedTokenAddress: '', selectedTokenAddress: '',
}; };
getTokenData(address) {
const {
initialized,
contracts,
account,
} = this.props;
const { drizzle } = this.context;
const { web3 } = drizzle;
if (!initialized || !web3) {
return;
}
const balanceKey = drizzle.contracts[address].methods.balanceOf.cacheCall(account);
const decimalsKey = drizzle.contracts[address].methods.decimals.cacheCall();
const token = contracts[address];
const balance = token.balanceOf[balanceKey];
const decimals = token.decimals[decimalsKey];
if (!balance || !decimals) {
return;
}
return {
balance: balance.value,
decimals: decimals.value,
};
}
getBalance() {
const {
balance,
initialized,
} = this.props;
const { selectedTokenAddress } = this.state;
const { drizzle } = this.context;
const { web3 } = drizzle;
if (!selectedTokenAddress || !initialized || !web3 || !balance) {
return null;
}
if (selectedTokenAddress === 'ETH') {
return BN(web3.utils.fromWei(balance, 'ether')).toFixed(2);
}
const tokenData = this.getTokenData(selectedTokenAddress);
if (!tokenData) {
return null;
}
const tokenBalance = BN(tokenData.balance);
const denomination = Math.pow(10, tokenData.decimals);
const adjustedBalance = tokenBalance.dividedBy(denomination);
return adjustedBalance.toFixed(2);
}
createTokenList = () => { createTokenList = () => {
let tokens = this.props.tokenAddresses.addresses; let tokens = this.props.tokenAddresses.addresses;
let tokenList = [ { value: 'ETH', label: 'ETH', address: 'ETH' } ]; let tokenList = [ { value: 'ETH', label: 'ETH', address: 'ETH' } ];
...@@ -147,20 +78,6 @@ class CurrencyInputPanel extends Component { ...@@ -147,20 +78,6 @@ class CurrencyInputPanel extends Component {
return tokenList; return tokenList;
}; };
validate = (balance) => {
const { value, addError, removeError, errors, shouldValidateBalance } = this.props;
const hasInsufficientBalance = errors.indexOf(INSUFFICIENT_BALANCE) > -1;
const balanceIsLess = BN(value).isGreaterThan(BN(balance));
if (shouldValidateBalance) {
if (balanceIsLess && !hasInsufficientBalance) {
addError(INSUFFICIENT_BALANCE);
} else if (!balanceIsLess && hasInsufficientBalance) {
removeError(INSUFFICIENT_BALANCE);
}
}
};
onTokenSelect = (address) => { onTokenSelect = (address) => {
this.setState({ this.setState({
selectedTokenAddress: address || 'ETH', selectedTokenAddress: address || 'ETH',
...@@ -177,7 +94,6 @@ class CurrencyInputPanel extends Component { ...@@ -177,7 +94,6 @@ class CurrencyInputPanel extends Component {
// Add Token Contract // Add Token Contract
if (!this.props.contracts[address]) { if (!this.props.contracts[address]) {
// console.log(`Adding Token Contract - ${address}`);
const tokenConfig = { const tokenConfig = {
contractName: address, contractName: address,
web3Contract: new web3.eth.Contract(ERC20_ABI, address), web3Contract: new web3.eth.Contract(ERC20_ABI, address),
...@@ -193,7 +109,6 @@ class CurrencyInputPanel extends Component { ...@@ -193,7 +109,6 @@ class CurrencyInputPanel extends Component {
} }
if (!this.props.contracts[exchangeAddress]) { if (!this.props.contracts[exchangeAddress]) {
// console.log(`Adding Exchange Contract - ${exchangeAddress}`);
const exchangeConfig = { const exchangeConfig = {
contractName: exchangeAddress, contractName: exchangeAddress,
web3Contract: new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress), web3Contract: new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress),
...@@ -204,14 +119,6 @@ class CurrencyInputPanel extends Component { ...@@ -204,14 +119,6 @@ class CurrencyInputPanel extends Component {
} }
}; };
renderBalance(balance) {
if (balance === null) {
return null;
}
return `Balance: ${balance}`;
}
renderTokenList() { renderTokenList() {
const tokens = this.createTokenList(); const tokens = this.createTokenList();
const { searchQuery } = this.state; const { searchQuery } = this.state;
...@@ -284,61 +191,45 @@ class CurrencyInputPanel extends Component { ...@@ -284,61 +191,45 @@ class CurrencyInputPanel extends Component {
const { const {
title, title,
description, description,
errors, extraText,
showSubButton, errorMessage,
subButtonContent, value,
onSubButtonClick, onValueChange,
} = this.props; } = this.props;
const { selectedTokenAddress } = this.state; const { selectedTokenAddress } = this.state;
const balance = this.getBalance();
this.validate(balance);
const hasInsufficientBalance = errors.indexOf(INSUFFICIENT_BALANCE) > -1;
return ( return (
<div className="currency-input-panel"> <div className="currency-input-panel">
<div className={ <div className={classnames('currency-input-panel__container', {
classnames('currency-input-panel__container', 'currency-input-panel__container--error': errorMessage,
{ 'currency-input-panel__container--error': hasInsufficientBalance } })}>
)
}>
<div className="currency-input-panel__label-row"> <div className="currency-input-panel__label-row">
<div className="currency-input-panel__label-container"> <div className="currency-input-panel__label-container">
<span className="currency-input-panel__label">{title}</span> <span className="currency-input-panel__label">{title}</span>
<span className="currency-input-panel__label-description">{description}</span> <span className="currency-input-panel__label-description">{description}</span>
</div> </div>
<span className={ <span className={classnames('currency-input-panel__extra-text', {
classnames('currency-input-panel__extra-text', 'currency-input-panel__extra-text--error': errorMessage,
{ 'currency-input-panel__extra-text--error': hasInsufficientBalance } })}>
) {extraText}
}>
{this.renderBalance(balance)}
</span> </span>
</div> </div>
<div className="currency-input-panel__input-row"> <div className="currency-input-panel__input-row">
<input <input
type="number" type="number"
className={ className={classnames('currency-input-panel__input',{
classnames('currency-input-panel__input', 'currency-input-panel__input--error': errorMessage,
{ 'currency-input-panel__input--error': hasInsufficientBalance } })}
)
}
placeholder="0.0" placeholder="0.0"
onChange={e => this.props.onValueChange(e.target.value)} onChange={e => onValueChange(e.target.value)}
value={this.props.value} value={value}
/> />
{ {/*<button*/}
showSubButton {/*className='currency-input-panel__sub-currency-select'*/}
? ( {/*>*/}
<button {/*Unlock*/}
onClick={onSubButtonClick} {/*</button>*/}
className={classnames("currency-input-panel__sub-currency-select")}
>
{ subButtonContent }
</button>
)
: null
}
<button <button
className={classnames("currency-input-panel__currency-select", { className={classnames("currency-input-panel__currency-select", {
'currency-input-panel__currency-select--selected': selectedTokenAddress, 'currency-input-panel__currency-select--selected': selectedTokenAddress,
...@@ -355,7 +246,7 @@ class CurrencyInputPanel extends Component { ...@@ -355,7 +246,7 @@ class CurrencyInputPanel extends Component {
) )
: null : null
} }
{ TOKEN_ADDRESS_TO_LABEL[selectedTokenAddress]|| 'Select a token' } { TOKEN_ADDRESS_TO_LABEL[selectedTokenAddress] || 'Select a token' }
<span className="currency-input-panel__dropdown-icon" /> <span className="currency-input-panel__dropdown-icon" />
</button> </button>
</div> </div>
...@@ -368,23 +259,9 @@ class CurrencyInputPanel extends Component { ...@@ -368,23 +259,9 @@ class CurrencyInputPanel extends Component {
export default drizzleConnect( export default drizzleConnect(
CurrencyInputPanel, CurrencyInputPanel,
state => { state => ({
const { exchangeAddresses: state.addresses.exchangeAddresses,
drizzleStatus: { initialized }, tokenAddresses: state.addresses.tokenAddresses,
accounts, contracts: state.contracts,
accountBalances,
} = state;
return {
tokenAddresses: state.addresses.tokenAddresses,
exchangeAddresses: state.addresses.exchangeAddresses,
initialized,
balance: accountBalances[accounts[0]] || null,
account: accounts[0],
contracts: state.contracts,
};
},
dispatch => ({
updateField: (name, value) => dispatch(updateField({ name, value })),
}), }),
); );
...@@ -49,11 +49,10 @@ export const approveExchange = async opts => { ...@@ -49,11 +49,10 @@ export const approveExchange = async opts => {
const decimals = await getDecimals({ address: currency, drizzleCtx, contractStore }); const decimals = await getDecimals({ address: currency, drizzleCtx, contractStore });
const approvals = BN(10 ** decimals).multipliedBy(BN(10 ** 8)).toFixed(0); const approvals = BN(10 ** decimals).multipliedBy(BN(10 ** 8)).toFixed(0);
return drizzleCtx.contracts[currency].methods.approve.cacheSend( return drizzleCtx.contracts[currency].methods
inputExchange, .approve(inputExchange, web3.utils.toHex(approvals))
web3.utils.toHex(approvals), .send({ from: account })
{ from: account } // .then((e, d) => console.log(e, d));
);
}; };
export const getApprovalTxStatus = opts => { export const getApprovalTxStatus = opts => {
......
import React, { Component } from 'react';
import { drizzleConnect } from 'drizzle-react';
import PropTypes from 'prop-types';
import classnames from "classnames";
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import ModeSelector from './ModeSelector';
import "./pool.scss";
class AddLiquidity extends Component {
static propTypes = {
currentAddress: PropTypes.string,
isConnected: PropTypes.bool.isRequired,
};
render() {
return (
<div
className={classnames('swap__content', {
'swap--inactive': !this.props.isConnected,
})}
>
<ModeSelector />
<CurrencyInputPanel
title="Deposit"
extraText="Balance: 0.03141"
/>
<OversizedPanel>
<div className="swap__down-arrow-background">
<img className="swap__down-arrow" src={ArrowDown} />
</div>
</OversizedPanel>
<CurrencyInputPanel
title="Deposit"
description="(estimated)"
extraText="Balance: 0.0"
/>
<OversizedPanel hideBottom>
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">Exchange Rate</span>
<span>1 ETH = 1283.878 BAT</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">Current Pool Size</span>
<span>321 ETH / 321,000 BAT</span>
</div>
</div>
</OversizedPanel>
<div className="swap__summary-wrapper">
<div>You are adding between {b`212000.00 - 216000.00 BAT`} + {b`166.683543 ETH`} into the liquidity pool.</div>
<div className="pool__last-summary-text">You will receive between {b`66%`} and {b`67%`} of the BAT/ETH pool tokens.</div>
</div>
<div className="pool__cta-container">
<button
className={classnames('pool__cta-btn', {
'swap--inactive': !this.props.isConnected,
'pool__cta-btn--inactive': !this.props.isValid,
})}
disabled={!this.props.isValid}
onClick={this.onSwap}
>
Swap
</button>
</div>
</div>
);
}
}
export default drizzleConnect(
AddLiquidity,
(state, ownProps) => ({
currentAddress: state.accounts[0],
isConnected: !!(state.drizzleStatus.initialized && state.accounts[0]),
}),
)
function b(text) {
return <span className="swap__highlight-text">{text}</span>
}
import React, { Component } from 'react';
import { drizzleConnect } from 'drizzle-react';
import OversizedPanel from "../../components/OversizedPanel";
import Dropdown from "../../assets/images/dropdown.svg";
import Modal from "../../components/Modal";
import {CSSTransitionGroup} from "react-transition-group";
class ModeSelector extends Component {
state = {
isShowingModal: false,
};
renderModal() {
if (!this.state.isShowingModal) {
return;
}
return (
<Modal onClose={() => this.setState({ isShowingModal: false })}>
<CSSTransitionGroup
transitionName="pool-modal"
transitionAppear={true}
transitionLeave={true}
transitionAppearTimeout={200}
transitionLeaveTimeout={200}
transitionEnterTimeout={200}
>
<div className="pool-modal">
<div
className="pool-modal__item"
onClick={() => this.setState({ isShowingModal: false })}
>
Add Liquidity
</div>
<div
className="pool-modal__item"
onClick={() => this.setState({ isShowingModal: false })}
>
Remove Liquidity
</div>
</div>
</CSSTransitionGroup>
</Modal>
);
}
render() {
return (
<OversizedPanel hideTop>
<div
className="pool__liquidity-container"
onClick={() => this.setState({ isShowingModal: true })}
>
<span className="pool__liquidity-label">Add Liquidity</span>
<img src={Dropdown} />
</div>
{this.renderModal()}
</OversizedPanel>
)
}
}
export default drizzleConnect(ModeSelector);
import React, { Component } from 'react'; import React, { Component } from 'react';
import { drizzleConnect } from 'drizzle-react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from "classnames"; import { withRouter } from 'react-router-dom';
import { drizzleConnect } from 'drizzle-react';
import Header from '../../components/Header'; import Header from '../../components/Header';
import CurrencyInputPanel from '../../components/CurrencyInputPanel'; import AddLiquidity from './AddLiquidity';
import OversizedPanel from '../../components/OversizedPanel';
import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import Dropdown from '../../assets/images/dropdown.svg';
import "./pool.scss"; import "./pool.scss";
function b(text) { const ADD_LIQUIDITY = 'Add Liquidity';
return <span className="swap__highlight-text">{text}</span> const REMOVE_LIQUIDITY = 'Remove Liquidity';
}
class Pool extends Component { class Pool extends Component {
static propTypes = { static propTypes = {
...@@ -24,59 +18,23 @@ class Pool extends Component { ...@@ -24,59 +18,23 @@ class Pool extends Component {
isConnected: PropTypes.bool.isRequired, isConnected: PropTypes.bool.isRequired,
}; };
state = {
selectedMode: ADD_LIQUIDITY,
};
renderContent() {
switch (this.state.selectedMode) {
case ADD_LIQUIDITY:
default:
return <AddLiquidity />
}
}
render() { render() {
return ( return (
<div className="pool"> <div className="pool">
<Header /> <Header />
<div { this.renderContent() }
className={classnames('swap__content', {
'swap--inactive': !this.props.isConnected,
})}
>
<OversizedPanel hideTop>
<div className="pool__liquidity-container">
<span className="pool__liquidity-label">Add Liquidity</span>
<img src={Dropdown} />
</div>
</OversizedPanel>
<CurrencyInputPanel
title="Input"
extraText="Balance: 0.03141"
/>
<OversizedPanel>
<div className="swap__down-arrow-background">
<img className="swap__down-arrow" src={ArrowDown} />
</div>
</OversizedPanel>
<CurrencyInputPanel
title="Output"
description="(estimated)"
extraText="Balance: 0.0"
/>
<OversizedPanel hideBottom>
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">Exchange Rate</span>
<span>1 ETH = 1283.878 BAT</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">Current Pool Size</span>
<span>321 ETH / 321,000 BAT</span>
</div>
</div>
</OversizedPanel>
<div className="swap__summary-wrapper">
<div>You are adding between {b`212000.00 - 216000.00 BAT`} + {b`166.683543 ETH`} into the liquidity pool.</div>
<div className="pool__last-summary-text">You will receive between {b`66%`} and {b`67%`} of the BAT/ETH pool tokens.</div>
</div>
</div>
<button
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
>
Add Liquidity
</button>
</div> </div>
); );
} }
......
...@@ -8,8 +8,10 @@ ...@@ -8,8 +8,10 @@
&__liquidity-container { &__liquidity-container {
@extend %row-nowrap; @extend %row-nowrap;
align-items: center; align-items: center;
padding: 1rem;
font-size: .75rem; font-size: .75rem;
padding: .625rem 1rem;
font-size: .75rem;
color: $dove-gray;
img { img {
height: .75rem; height: .75rem;
...@@ -43,4 +45,55 @@ ...@@ -43,4 +45,55 @@
width: 0; width: 0;
color: $chalice-gray; color: $chalice-gray;
} }
&__cta-container {
display: flex;
}
&__cta-btn {
@extend %primary-button;
margin: 2rem auto 0;
&--inactive {
background: $mercury-gray;
}
}
}
.pool-modal {
background-color: $white;
position: relative;
bottom: 7.875rem;
width: 100%;
height: 7.875rem;
z-index: 2000;
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
transition: 250ms ease-in-out;
padding: 1rem 0 .5rem;
&__item {
padding: 1rem;
font-size: 1rem;
&:hover {
background-color: $concrete-gray;
.token-modal__token-label {
color: $black;
}
}
&:active {
background-color: darken($concrete-gray, 1);
}
}
}
.pool-modal-appear {
bottom: 0;
}
.pool-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