From 8854d42fd99f3d494cdbd4ba4a038930f1e1720a Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 8 Sep 2017 15:26:51 -0400 Subject: [PATCH] Sidebar refactor / style update (#173) * Convert bootstrap to sass instead of checked in and less * Darken body, adjust header. * First pass at tab styles, each tab will need a lot of individual love tho. * Update footer to main site content, improve responsiveness. * Missing key added. * Fix dropdowns. * Convert GenerateWallet HTML over, still needs styling. * Send form. * Current rates styled. * CurrencySwap form styles. * SwapInfoHeader styled. * Finish up swap restyling, minor usability improvements for mobile. * Fix up notifications / alert customizations * Import v3 variables. * Fix notification spacing. * Align input height base with buttons. * Revert height base, add additional bootstrap overrides. * Grid overrides. * Move overrides to their own folder. Adjust naming. * Fix inconsistencies. * Style generate wallet pt 1. * Style generate wallet pt 2 * Style generate wallet pt 3 * Fix swap * Added some missing overries, fixed the fallout. * Remove header text, indicate alpha version. * Fix radio / checkbox weights. * Bind => arrow * Convert simpledropdown to proper form select, instead of weirdly implemented nonfuncitoning dropdown. * Fix token balances buttons, footr icons. * Break out files, style up account info. * Style up token balances. * Equivalent values styling. * Sidebar promos. * Fix up delete button and add custom form. * Even spacing. * Unlog * Convert Big types to Ether types * Fix test to expect Ether instead of Big --- common/actions/wallet.js | 5 +- common/assets/images/logo-coinbase.svg | 1 + common/assets/images/logo-ledger.svg | 1 + common/assets/images/logo-trezor.svg | 1 + common/assets/styles/etherwallet-custom.less | 36 ---- .../components/BalanceSidebar/AccountInfo.jsx | 105 ++++++++++ .../BalanceSidebar/AccountInfo.scss | 76 +++++++ .../BalanceSidebar/AddCustomTokenForm.jsx | 105 ---------- .../BalanceSidebar/BalanceSidebar.jsx | 192 ------------------ .../BalanceSidebar/EquivalentValues.jsx | 47 +++++ .../BalanceSidebar/EquivalentValues.scss | 37 ++++ common/components/BalanceSidebar/Promos.jsx | 104 ++++++++++ common/components/BalanceSidebar/Promos.scss | 90 ++++++++ .../BalanceSidebar/TokenBalances.jsx | 87 -------- .../TokenBalances/AddCustomTokenForm.jsx | 108 ++++++++++ .../{ => TokenBalances}/TokenRow.jsx | 10 +- .../TokenBalances/TokenRow.scss | 31 +++ .../BalanceSidebar/TokenBalances/index.jsx | 93 +++++++++ .../BalanceSidebar/TokenBalances/index.scss | 18 ++ common/components/BalanceSidebar/index.js | 3 - common/components/BalanceSidebar/index.jsx | 96 +++++++++ .../containers/Tabs/SendTransaction/index.jsx | 10 +- common/reducers/wallet.js | 10 +- common/sagas/wallet.js | 2 +- common/sass/styles/tab.scss | 10 + spec/reducers/wallet.spec.js | 3 +- 26 files changed, 838 insertions(+), 443 deletions(-) create mode 100644 common/assets/images/logo-coinbase.svg create mode 100644 common/assets/images/logo-ledger.svg create mode 100644 common/assets/images/logo-trezor.svg create mode 100644 common/components/BalanceSidebar/AccountInfo.jsx create mode 100644 common/components/BalanceSidebar/AccountInfo.scss delete mode 100644 common/components/BalanceSidebar/AddCustomTokenForm.jsx delete mode 100644 common/components/BalanceSidebar/BalanceSidebar.jsx create mode 100644 common/components/BalanceSidebar/EquivalentValues.jsx create mode 100644 common/components/BalanceSidebar/EquivalentValues.scss create mode 100644 common/components/BalanceSidebar/Promos.jsx create mode 100644 common/components/BalanceSidebar/Promos.scss delete mode 100644 common/components/BalanceSidebar/TokenBalances.jsx create mode 100644 common/components/BalanceSidebar/TokenBalances/AddCustomTokenForm.jsx rename common/components/BalanceSidebar/{ => TokenBalances}/TokenRow.jsx (85%) create mode 100644 common/components/BalanceSidebar/TokenBalances/TokenRow.scss create mode 100644 common/components/BalanceSidebar/TokenBalances/index.jsx create mode 100644 common/components/BalanceSidebar/TokenBalances/index.scss delete mode 100644 common/components/BalanceSidebar/index.js create mode 100644 common/components/BalanceSidebar/index.jsx diff --git a/common/actions/wallet.js b/common/actions/wallet.js index aa6ca64f..d5c3754e 100644 --- a/common/actions/wallet.js +++ b/common/actions/wallet.js @@ -1,6 +1,7 @@ // @flow import BaseWallet from 'libs/wallet/base'; import Big from 'bignumber.js'; +import { Wei } from 'libs/units'; /*** Unlock Private Key ***/ export type PrivateKeyUnlockParams = { @@ -58,10 +59,10 @@ export function setWallet(value: BaseWallet): SetWalletAction { /*** Set Balance ***/ export type SetBalanceAction = { type: 'WALLET_SET_BALANCE', - payload: Big + payload: Wei }; -export function setBalance(value: Big): SetBalanceAction { +export function setBalance(value: Wei): SetBalanceAction { return { type: 'WALLET_SET_BALANCE', payload: value diff --git a/common/assets/images/logo-coinbase.svg b/common/assets/images/logo-coinbase.svg new file mode 100644 index 00000000..486a5a40 --- /dev/null +++ b/common/assets/images/logo-coinbase.svg @@ -0,0 +1 @@ + diff --git a/common/assets/images/logo-ledger.svg b/common/assets/images/logo-ledger.svg new file mode 100644 index 00000000..b17fe522 --- /dev/null +++ b/common/assets/images/logo-ledger.svg @@ -0,0 +1 @@ + diff --git a/common/assets/images/logo-trezor.svg b/common/assets/images/logo-trezor.svg new file mode 100644 index 00000000..76efb7f3 --- /dev/null +++ b/common/assets/images/logo-trezor.svg @@ -0,0 +1 @@ + diff --git a/common/assets/styles/etherwallet-custom.less b/common/assets/styles/etherwallet-custom.less index 8236cbc8..4ab73d56 100755 --- a/common/assets/styles/etherwallet-custom.less +++ b/common/assets/styles/etherwallet-custom.less @@ -209,38 +209,6 @@ textarea { } } -.account-info { - .clearfix; - padding-left: 1em; - margin: 0; - li { - margin-bottom: 0; - list-style-type: none; - word-break: break-all; - } - table& { - font-weight: 200; - border-bottom: 0; - min-width: 200px; - td { - padding: 4px 5px; - line-height: 1; - } - td:first-child { - max-width: 115px; - word-wrap: break-word; - padding-left: 1em; - } - tr:nth-child(even) { - background-color: @gray-lightest; - } - tr:nth-last-child(2), - tr:last-child { - background-color: white !important; - } - } -} - input[type="text"] + .eye { cursor: pointer; &:before { @@ -322,10 +290,6 @@ input[type="password"] + .eye { text-align: right; } -.token-balances { - margin-bottom: 15px; -} - h2 a.isActive { color: #333; cursor: default; diff --git a/common/components/BalanceSidebar/AccountInfo.jsx b/common/components/BalanceSidebar/AccountInfo.jsx new file mode 100644 index 00000000..5c35e3aa --- /dev/null +++ b/common/components/BalanceSidebar/AccountInfo.jsx @@ -0,0 +1,105 @@ +// @flow +import './AccountInfo.scss'; +import React from 'react'; +import translate from 'translations'; +import { Identicon } from 'components/ui'; +import { formatNumber } from 'utils/formatters'; +import type Big from 'bignumber.js'; +import type { BaseWallet } from 'libs/wallet'; +import type { NetworkConfig } from 'config/data'; +import { Ether } from 'libs/units'; + +type Props = { + balance: Ether, + wallet: BaseWallet, + network: NetworkConfig +}; + +export default class AccountInfo extends React.Component { + props: Props; + + state = { + showLongBalance: false, + address: '' + }; + + componentDidMount() { + this.props.wallet.getAddress().then(addr => { + this.setState({ address: addr }); + }); + } + + toggleShowLongBalance = (e: SyntheticMouseEvent) => { + e.preventDefault(); + this.setState(state => { + return { + showLongBalance: !state.showLongBalance + }; + }); + }; + + render() { + const { network, balance } = this.props; + const { blockExplorer, tokenExplorer } = network; + const { address } = this.state; + + return ( +
+
+
+ {translate('sidebar_AccountAddr')} +
+
+
+ +
+
+ {address} +
+
+
+ +
+
+ {translate('sidebar_AccountBal')} +
+
    +
  • + + {this.state.showLongBalance + ? balance.toString() + : formatNumber(balance.amount)} + + {` ${network.name}`} +
  • +
+
+ + {(!!blockExplorer || !!tokenExplorer) && +
+
+ {translate('sidebar_TransHistory')} +
+ +
} +
+ ); + } +} diff --git a/common/components/BalanceSidebar/AccountInfo.scss b/common/components/BalanceSidebar/AccountInfo.scss new file mode 100644 index 00000000..cfc42bac --- /dev/null +++ b/common/components/BalanceSidebar/AccountInfo.scss @@ -0,0 +1,76 @@ +@import "common/sass/variables"; +@import "common/sass/mixins"; + +.AccountInfo { + &-section { + margin-top: $space * 1.5; + + &:first-child { + margin-top: 0; + } + + &-header { + margin-top: 0; + } + } + + &-address, + &-list { + padding-left: $space; + } + + &-address { + @include clearfix; + + &-icon { + float: left; + width: 44px; + height: 44px; + margin-right: $space-md; + } + + &-addr { + width: 100%; + word-wrap: break-word; + @include mono; + } + } + + &-list { + &-item { + margin-bottom: 0; + list-style-type: none; + word-break: break-all; + + &-clickable:hover { + cursor: pointer; + text-decoration: underline; + } + } + } +} + +.account-info { + padding-left: 1em; + margin: 0; + li { + } + table { + font-weight: 200; + border-bottom: 0; + min-width: 200px; + td { + padding: 4px 5px; + line-height: 1; + } + td:first-child { + max-width: 115px; + word-wrap: break-word; + padding-left: 1em; + } + tr:nth-last-child(2), + tr:last-child { + background-color: white !important; + } + } +} diff --git a/common/components/BalanceSidebar/AddCustomTokenForm.jsx b/common/components/BalanceSidebar/AddCustomTokenForm.jsx deleted file mode 100644 index 2100369f..00000000 --- a/common/components/BalanceSidebar/AddCustomTokenForm.jsx +++ /dev/null @@ -1,105 +0,0 @@ -// @flow -import React from 'react'; -import { isValidETHAddress, isPositiveIntegerOrZero } from 'libs/validators'; -import translate from 'translations'; - -export default class AddCustomTokenForm extends React.Component { - props: { - onSave: ({ address: string, symbol: string, decimal: number }) => void - }; - state = { - address: '', - symbol: '', - decimal: '' - }; - - render() { - return ( -
- - - - - - -
- {translate('x_Save')} -
-
- ); - } - - isValid() { - const { address, symbol, decimal } = this.state; - if (!isPositiveIntegerOrZero(parseInt(decimal))) { - return false; - } - if (!isValidETHAddress(address)) { - return false; - } - if (symbol === '') { - return false; - } - - return true; - } - - onFieldChange = (e: SyntheticInputEvent) => { - var name = e.target.name; - var value = e.target.value; - this.setState(state => { - var newState = Object.assign({}, state); - newState[name] = value; - return newState; - }); - }; - - onSave = () => { - if (!this.isValid()) { - return; - } - const { address, symbol, decimal } = this.state; - - this.props.onSave({ address, symbol, decimal: parseInt(decimal) }); - }; -} diff --git a/common/components/BalanceSidebar/BalanceSidebar.jsx b/common/components/BalanceSidebar/BalanceSidebar.jsx deleted file mode 100644 index 131a0894..00000000 --- a/common/components/BalanceSidebar/BalanceSidebar.jsx +++ /dev/null @@ -1,192 +0,0 @@ -// @flow -import React from 'react'; -import Big from 'bignumber.js'; -import { BaseWallet } from 'libs/wallet'; -import type { NetworkConfig } from 'config/data'; -import type { State } from 'reducers'; -import { connect } from 'react-redux'; -import { getWalletInst, getTokenBalances } from 'selectors/wallet'; -import type { TokenBalance } from 'selectors/wallet'; -import { getNetworkConfig } from 'selectors/config'; -import { Link } from 'react-router'; -import TokenBalances from './TokenBalances'; -import { formatNumber } from 'utils/formatters'; -import { Identicon } from 'components/ui'; -import translate from 'translations'; -import * as customTokenActions from 'actions/customTokens'; -import { showNotification } from 'actions/notifications'; - -type Props = { - wallet: BaseWallet, - balance: Big, - network: NetworkConfig, - tokenBalances: TokenBalance[], - rates: { [string]: number }, - showNotification: Function, - addCustomToken: typeof customTokenActions.addCustomToken, - removeCustomToken: typeof customTokenActions.removeCustomToken -}; - -export class BalanceSidebar extends React.Component { - props: Props; - state = { - showLongBalance: false, - address: '' - }; - - componentDidMount() { - this.props.wallet - .getAddress() - .then(addr => { - this.setState({ address: addr }); - }) - .catch(err => { - this.props.showNotification('danger', err); - }); - } - - render() { - const { wallet, balance, network, tokenBalances, rates } = this.props; - const { blockExplorer, tokenExplorer } = network; - const { address } = this.state; - if (!wallet) { - return null; - } - - return ( - - ); - } - - toggleShowLongBalance = (e: SyntheticMouseEvent) => { - e.preventDefault(); - this.setState(state => { - return { - showLongBalance: !state.showLongBalance - }; - }); - }; -} - -function mapStateToProps(state: State) { - return { - wallet: getWalletInst(state), - balance: state.wallet.balance, - tokenBalances: getTokenBalances(state), - network: getNetworkConfig(state), - rates: state.rates - }; -} - -export default connect(mapStateToProps, { - ...customTokenActions, - showNotification -})(BalanceSidebar); diff --git a/common/components/BalanceSidebar/EquivalentValues.jsx b/common/components/BalanceSidebar/EquivalentValues.jsx new file mode 100644 index 00000000..afec1345 --- /dev/null +++ b/common/components/BalanceSidebar/EquivalentValues.jsx @@ -0,0 +1,47 @@ +// @flow +import './EquivalentValues.scss'; +import React from 'react'; +import translate from 'translations'; +import { Link } from 'react-router'; +import { formatNumber } from 'utils/formatters'; +import type Big from 'bignumber.js'; +import { Ether } from 'libs/units'; + +const ratesKeys = ['BTC', 'REP', 'EUR', 'USD', 'GBP', 'CHF']; + +type Props = { + balance: Ether, + rates: { [string]: number } +}; + +export default class EquivalentValues extends React.Component { + props: Props; + + render() { + const { balance, rates } = this.props; + + return ( +
+
+ {translate('sidebar_Equiv')} +
+ + +
+ ); + } +} diff --git a/common/components/BalanceSidebar/EquivalentValues.scss b/common/components/BalanceSidebar/EquivalentValues.scss new file mode 100644 index 00000000..6b54c226 --- /dev/null +++ b/common/components/BalanceSidebar/EquivalentValues.scss @@ -0,0 +1,37 @@ +@import "common/sass/variables"; +@import "common/sass/mixins"; + +.EquivalentValues { + &-title { + margin-top: 0; + margin-bottom: $space; + } + + &-values { + list-style: none; + padding: 0; + @include clearfix; + + &-currency { + float: left; + width: 50%; + margin-bottom: $space-xs; + + &:nth-child(odd) { + padding-right: $space-sm; + } + &:nth-child(even) { + padding-left: $space-sm; + } + + &-label { + display: inline-block; + min-width: 36px; + } + &-value { + font-weight: 600; + @include mono; + } + } + } +} diff --git a/common/components/BalanceSidebar/Promos.jsx b/common/components/BalanceSidebar/Promos.jsx new file mode 100644 index 00000000..4e1b467a --- /dev/null +++ b/common/components/BalanceSidebar/Promos.jsx @@ -0,0 +1,104 @@ +// @flow +import './Promos.scss'; +import React from 'react'; +import { Link } from 'react-router'; + +const promos = [ + { + color: '#6e9a3e', + href: + 'https://myetherwallet.groovehq.com/knowledge_base/topics/protecting-yourself-and-your-funds', + isExternal: true, + texts: [
Learn more about protecting your funds.
], + images: [ + require('assets/images/logo-ledger.svg'), + require('assets/images/logo-trezor.svg') + ] + }, + { + color: '#2b71b1', + href: + 'https://buy.coinbase.com?code=a6e1bd98-6464-5552-84dd-b27f0388ac7d&address=0xA7DeFf12461661212734dB35AdE9aE7d987D648c&crypto_currency=ETH¤cy=USD', + isExternal: true, + texts: [ +

It’s now easier to get more ETH

, +
Buy ETH with USD
+ ], + images: [require('assets/images/logo-coinbase.svg')] + }, + { + color: '#006e79', + href: '/swap', + texts: [ +

It’s now easier to get more ETH

, +
Swap BTC <-> ETH
+ ], + images: [require('assets/images/logo-bity-white.svg')] + } +]; + +export default class Promos extends React.Component { + state: { activePromo: number }; + + state = { + activePromo: parseInt(Math.random() * promos.length) + }; + + _navigateToPromo = (idx: number) => { + this.setState({ activePromo: Math.max(0, Math.min(promos.length, idx)) }); + }; + + render() { + const { activePromo } = this.state; + const promo = promos[activePromo]; + + const promoContent = ( +
+
+ {promo.texts} +
+
+ {promo.images.map((img, idx) => )} +
+
+ ); + const promoEl = promo.isExternal + ? + {promoContent} + + : +
+ {promoContent} +
+ ; + + return ( +
+ {promoEl} +
+ {promos.map((promo, idx) => { + return ( +
+
+ ); + } +} diff --git a/common/components/BalanceSidebar/Promos.scss b/common/components/BalanceSidebar/Promos.scss new file mode 100644 index 00000000..8d7829fa --- /dev/null +++ b/common/components/BalanceSidebar/Promos.scss @@ -0,0 +1,90 @@ +@import "common/sass/variables"; +@import "common/sass/mixins"; + +.Promos { + &-promo { + position: relative; + height: 6rem; + display: block; + color: #fff; + text-decoration: none; + text-align: center; + transition-duration: 200ms; + @include clearfix; + + &:hover, + &:focus, + &:active { + color: #fff; + opacity: 0.85; + } + + &-inner { + position: absolute; + display: flex; + align-items: center; + top: 50%; + left: 0; + width: 100%; + transform: translateY(-50%); + } + + &-text, + &-images { + padding: 0 $space-sm; + } + + &-text { + flex: 1; + + p, + h4, + h5, + h6 { + margin: .15rem 0; + } + + p { + font-size: 0.8rem; + } + + h5 { + font-size: 1.3rem; + } + } + + &-images { + padding: 0 $space * 1.5; + + img { + display: block; + margin: 0 auto; + width: 100%; + max-width: 96px; + height: auto; + padding: $space-xs; + } + } + } + + &-nav { + text-align: center; + + &-btn { + @include reset-button; + display: inline-block; + margin: 0 $space-xs; + background: $gray-dark; + width: 12px; + height: 12px; + border: 3px solid $gray-lightest; + border-radius: 100%; + outline: none; + opacity: 0.6; + + &.is-active { + opacity: 1; + } + } + } +} diff --git a/common/components/BalanceSidebar/TokenBalances.jsx b/common/components/BalanceSidebar/TokenBalances.jsx deleted file mode 100644 index 1bedef14..00000000 --- a/common/components/BalanceSidebar/TokenBalances.jsx +++ /dev/null @@ -1,87 +0,0 @@ -// @flow -import React from 'react'; -import translate from 'translations'; -import TokenRow from './TokenRow'; -import AddCustomTokenForm from './AddCustomTokenForm'; -import type { TokenBalance } from 'selectors/wallet'; -import type { Token } from 'config/data'; - -type Props = { - tokens: TokenBalance[], - onAddCustomToken: (token: Token) => any, - onRemoveCustomToken: (symbol: string) => any -}; - -export default class TokenBalances extends React.Component { - props: Props; - state = { - showAllTokens: false, - showCustomTokenForm: false - }; - - render() { - const { tokens } = this.props; - return ( -
-
{translate('sidebar_TokenBal')}
- - - {tokens - .filter( - token => - !token.balance.eq(0) || - token.custom || - this.state.showAllTokens - ) - .map(token => - - )} - -
- - {!this.state.showAllTokens ? 'Show All Tokens' : 'Hide Tokens'} - {' '} - - - {translate('SEND_custom')} - - - {this.state.showCustomTokenForm && - } -
- ); - } - - toggleShowAllTokens = () => { - this.setState(state => { - return { - showAllTokens: !state.showAllTokens - }; - }); - }; - - toggleShowCustomTokenForm = () => { - this.setState(state => { - return { - showCustomTokenForm: !state.showCustomTokenForm - }; - }); - }; - - addCustomToken = (token: Token) => { - this.props.onAddCustomToken(token); - this.setState({ showCustomTokenForm: false }); - }; -} diff --git a/common/components/BalanceSidebar/TokenBalances/AddCustomTokenForm.jsx b/common/components/BalanceSidebar/TokenBalances/AddCustomTokenForm.jsx new file mode 100644 index 00000000..921bb69e --- /dev/null +++ b/common/components/BalanceSidebar/TokenBalances/AddCustomTokenForm.jsx @@ -0,0 +1,108 @@ +// @flow +import React from 'react'; +import classnames from 'classnames'; +import { isValidETHAddress, isPositiveIntegerOrZero } from 'libs/validators'; +import translate from 'translations'; + +export default class AddCustomTokenForm extends React.Component { + props: { + onSave: ({ address: string, symbol: string, decimal: number }) => void + }; + state = { + address: '', + symbol: '', + decimal: '' + }; + + render() { + const { address, symbol, decimal } = this.state; + const inputClasses = 'AddCustom-field-input form-control input-sm'; + const errors = this.getErrors(); + + const fields = [ + { + name: 'address', + value: address, + label: translate('TOKEN_Addr') + }, + { + name: 'symbol', + value: symbol, + label: translate('TOKEN_Symbol') + }, + { + name: 'decimal', + value: decimal, + label: translate('TOKEN_Dec') + } + ]; + + return ( +
+ {fields.map(field => { + return ( + + ); + })} + + +
+ ); + } + + getErrors() { + const { address, symbol, decimal } = this.state; + const errors = {}; + + if (!isPositiveIntegerOrZero(parseInt(decimal, 10))) { + errors.decimal = true; + } + if (!isValidETHAddress(address)) { + errors.address = true; + } + if (!symbol) { + errors.symbol = true; + } + + return errors; + } + + isValid() { + return !Object.keys(this.getErrors()).length; + } + + onFieldChange = (e: SyntheticInputEvent) => { + var name = e.target.name; + var value = e.target.value; + this.setState({ [name]: value }); + }; + + onSave = (ev: SyntheticInputEvent) => { + ev.preventDefault(); + if (!this.isValid()) { + return; + } + + const { address, symbol, decimal } = this.state; + this.props.onSave({ address, symbol, decimal: parseInt(decimal, 10) }); + }; +} diff --git a/common/components/BalanceSidebar/TokenRow.jsx b/common/components/BalanceSidebar/TokenBalances/TokenRow.jsx similarity index 85% rename from common/components/BalanceSidebar/TokenRow.jsx rename to common/components/BalanceSidebar/TokenBalances/TokenRow.jsx index fc630c74..048b705b 100644 --- a/common/components/BalanceSidebar/TokenRow.jsx +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.jsx @@ -1,4 +1,5 @@ // @flow +import './TokenRow.scss'; import React from 'react'; import Big from 'bignumber.js'; import { formatNumber } from 'utils/formatters'; @@ -19,24 +20,25 @@ export default class TokenRow extends React.Component { const { balance, symbol, custom } = this.props; const { showLongBalance } = this.state; return ( - + {!!custom && } {showLongBalance ? balance.toString() : formatNumber(balance)} - + {symbol} diff --git a/common/components/BalanceSidebar/TokenBalances/TokenRow.scss b/common/components/BalanceSidebar/TokenBalances/TokenRow.scss new file mode 100644 index 00000000..12f7031e --- /dev/null +++ b/common/components/BalanceSidebar/TokenBalances/TokenRow.scss @@ -0,0 +1,31 @@ +@import "common/sass/variables"; +@import "common/sass/mixins"; + +.TokenRow { + border-bottom: 1px solid $gray-lighter; + + &-balance, + &-symbol { + padding: $space-xs 0 $space-xs $space-md; + } + + &-balance { + @include mono; + + &-remove { + margin-left: -32px; + margin-right: 20px; + height: 12px; + cursor: pointer; + opacity: 0.4; + + &:hover { + opacity: 1; + } + } + } + + &-symbol { + font-weight: 300; + } +} diff --git a/common/components/BalanceSidebar/TokenBalances/index.jsx b/common/components/BalanceSidebar/TokenBalances/index.jsx new file mode 100644 index 00000000..eb3997c6 --- /dev/null +++ b/common/components/BalanceSidebar/TokenBalances/index.jsx @@ -0,0 +1,93 @@ +// @flow +import './index.scss'; +import React from 'react'; +import translate from 'translations'; +import TokenRow from './TokenRow'; +import AddCustomTokenForm from './AddCustomTokenForm'; +import type { TokenBalance } from 'selectors/wallet'; +import type { Token } from 'config/data'; + +type Props = { + tokens: TokenBalance[], + onAddCustomToken: (token: Token) => any, + onRemoveCustomToken: (symbol: string) => any +}; + +export default class TokenBalances extends React.Component { + props: Props; + state = { + showAllTokens: false, + showCustomTokenForm: false + }; + + render() { + const { tokens } = this.props; + const shownTokens = tokens.filter( + token => !token.balance.eq(0) || token.custom || this.state.showAllTokens + ); + + return ( +
+
+ {translate('sidebar_TokenBal')} +
+ + + {shownTokens.map(token => + + )} + +
+ +
+ {' '} + +
+ + {this.state.showCustomTokenForm && +
+ +
} +
+ ); + } + + toggleShowAllTokens = () => { + this.setState(state => { + return { + showAllTokens: !state.showAllTokens + }; + }); + }; + + toggleShowCustomTokenForm = () => { + this.setState(state => { + return { + showCustomTokenForm: !state.showCustomTokenForm + }; + }); + }; + + addCustomToken = (token: Token) => { + this.props.onAddCustomToken(token); + this.setState({ showCustomTokenForm: false }); + }; +} diff --git a/common/components/BalanceSidebar/TokenBalances/index.scss b/common/components/BalanceSidebar/TokenBalances/index.scss new file mode 100644 index 00000000..e75cb29b --- /dev/null +++ b/common/components/BalanceSidebar/TokenBalances/index.scss @@ -0,0 +1,18 @@ +@import "common/sass/variables"; + +.TokenBalances { + &-title { + margin-top: 0; + } + + &-rows { + width: 100%; + margin-bottom: $space; + } + + &-form { + margin-top: $space * 2; + padding-top: $space; + border-top: 1px solid $gray-lighter; + } +} diff --git a/common/components/BalanceSidebar/index.js b/common/components/BalanceSidebar/index.js deleted file mode 100644 index 2aa8b7c4..00000000 --- a/common/components/BalanceSidebar/index.js +++ /dev/null @@ -1,3 +0,0 @@ -// @flow - -export { default } from './BalanceSidebar'; diff --git a/common/components/BalanceSidebar/index.jsx b/common/components/BalanceSidebar/index.jsx new file mode 100644 index 00000000..503b7f3c --- /dev/null +++ b/common/components/BalanceSidebar/index.jsx @@ -0,0 +1,96 @@ +// @flow +import React from 'react'; +import Big from 'bignumber.js'; +import { BaseWallet } from 'libs/wallet'; +import type { NetworkConfig } from 'config/data'; +import type { State } from 'reducers'; +import { connect } from 'react-redux'; +import { getWalletInst, getTokenBalances } from 'selectors/wallet'; +import type { TokenBalance } from 'selectors/wallet'; +import { getNetworkConfig } from 'selectors/config'; +import * as customTokenActions from 'actions/customTokens'; +import { showNotification } from 'actions/notifications'; + +import AccountInfo from './AccountInfo'; +import Promos from './Promos'; +import TokenBalances from './TokenBalances'; +import EquivalentValues from './EquivalentValues'; +import { Ether } from 'libs/units'; + +type Props = { + wallet: BaseWallet, + balance: Ether, + network: NetworkConfig, + tokenBalances: TokenBalance[], + rates: { [string]: number }, + showNotification: Function, + addCustomToken: typeof customTokenActions.addCustomToken, + removeCustomToken: typeof customTokenActions.removeCustomToken +}; + +export class BalanceSidebar extends React.Component { + props: Props; + + render() { + const { wallet, balance, network, tokenBalances, rates } = this.props; + if (!wallet) { + return null; + } + + const blocks = [ + { + name: 'Account Info', + content: ( + + ) + }, + { + name: 'Promos', + isFullWidth: true, + content: + }, + { + name: 'Token Balances', + content: ( + + ) + }, + { + name: 'Equivalent Values', + content: + } + ]; + + return ( + + ); + } +} + +function mapStateToProps(state: State) { + return { + wallet: getWalletInst(state), + balance: state.wallet.balance, + tokenBalances: getTokenBalances(state), + network: getNetworkConfig(state), + rates: state.rates + }; +} + +export default connect(mapStateToProps, { + ...customTokenActions, + showNotification +})(BalanceSidebar); diff --git a/common/containers/Tabs/SendTransaction/index.jsx b/common/containers/Tabs/SendTransaction/index.jsx index 4a3beb0c..4af7210c 100644 --- a/common/containers/Tabs/SendTransaction/index.jsx +++ b/common/containers/Tabs/SendTransaction/index.jsx @@ -285,13 +285,7 @@ export class SendTransaction extends React.Component { {/* Sidebar */} {unlocked &&
-
-
- -
- -
-
+
} @@ -512,7 +506,7 @@ export class SendTransaction extends React.Component { function mapStateToProps(state: AppState) { return { wallet: state.wallet.inst, - balance: new Ether(state.wallet.balance), + balance: state.wallet.balance, tokenBalances: getTokenBalances(state), node: getNodeConfig(state), nodeLib: getNodeLib(state), diff --git a/common/reducers/wallet.js b/common/reducers/wallet.js index 35f0d927..739ccfce 100644 --- a/common/reducers/wallet.js +++ b/common/reducers/wallet.js @@ -10,10 +10,12 @@ import { toUnit } from 'libs/units'; import Big from 'bignumber.js'; import { getTxFromBroadcastTransactionStatus } from 'selectors/wallet'; import type { BroadcastTransactionStatus } from 'libs/transaction'; +import { Ether } from 'libs/units'; + export type State = { inst: ?BaseWallet, // in ETH - balance: Big, + balance: Ether, tokens: { [string]: Big }, @@ -22,18 +24,18 @@ export type State = { export const INITIAL_STATE: State = { inst: null, - balance: new Big(0), + balance: new Ether(0), tokens: {}, isBroadcasting: false, transactions: [] }; function setWallet(state: State, action: SetWalletAction): State { - return { ...state, inst: action.payload, balance: new Big(0), tokens: {} }; + return { ...state, inst: action.payload, balance: new Ether(0), tokens: {} }; } function setBalance(state: State, action: SetBalanceAction): State { - const ethBalance = toUnit(action.payload, 'wei', 'ether'); + const ethBalance = action.payload.toEther(); return { ...state, balance: ethBalance }; } diff --git a/common/sagas/wallet.js b/common/sagas/wallet.js index c730ab95..5a2d6f6a 100644 --- a/common/sagas/wallet.js +++ b/common/sagas/wallet.js @@ -39,7 +39,7 @@ function* updateAccountBalance(): Generator { const address = yield wallet.getAddress(); // network request let balance: Wei = yield apply(node, node.getBalance, [address]); - yield put(setBalance(balance.amount)); + yield put(setBalance(balance)); } catch (error) { yield put({ type: 'updateAccountBalance_error', error }); } diff --git a/common/sass/styles/tab.scss b/common/sass/styles/tab.scss index e7264585..208a0d31 100644 --- a/common/sass/styles/tab.scss +++ b/common/sass/styles/tab.scss @@ -10,6 +10,16 @@ min-height: 1.5rem; padding: 1.5rem 2rem; margin: 0 auto 1rem; + + &.is-full-width { + background: none; + box-shadow: none; + padding: 0; + } } } } + +.Block { + @extend .Tab-content-pane; +} diff --git a/spec/reducers/wallet.spec.js b/spec/reducers/wallet.spec.js index 8cfe198a..cec0d2e1 100644 --- a/spec/reducers/wallet.spec.js +++ b/spec/reducers/wallet.spec.js @@ -1,6 +1,7 @@ import { wallet, INITIAL_STATE } from 'reducers/wallet'; import * as walletActions from 'actions/wallet'; import Big from 'bignumber.js'; +import { Ether } from 'libs/units'; describe('wallet reducer', () => { it('should return the initial state', () => { @@ -12,7 +13,7 @@ describe('wallet reducer', () => { expect(wallet(undefined, walletActions.setWallet(walletInstance))).toEqual({ ...INITIAL_STATE, inst: walletInstance, - balance: new Big(0), + balance: new Ether(0), tokens: {} }); });