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
This commit is contained in:
parent
38dd22953a
commit
8854d42fd9
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg width="579" height="126" viewBox="0 0 579 126" xmlns="http://www.w3.org/2000/svg"><g fill="#ffffff" fill-rule="evenodd"><path d="M37.752 125.873c-18.928 0-37.383-13.566-37.383-44.324 0-30.759 18.455-44.167 37.383-44.167 9.307 0 16.563 2.367 21.768 5.837L53.841 55.68c-3.47-2.524-8.675-4.101-13.88-4.101-11.357 0-21.768 8.991-21.768 29.812s10.726 29.97 21.768 29.97c5.205 0 10.41-1.578 13.88-4.101l5.679 12.776c-5.363 3.628-12.461 5.837-21.768 5.837M102.898 125.873c-24.133 0-37.383-19.087-37.383-44.324 0-25.238 13.25-44.167 37.383-44.167 24.134 0 37.384 18.929 37.384 44.167 0 25.237-13.25 44.324-37.384 44.324zm0-74.768c-13.407 0-20.032 11.988-20.032 30.286 0 18.297 6.625 30.443 20.032 30.443 13.408 0 20.033-12.146 20.033-30.443 0-18.298-6.625-30.286-20.033-30.286zM163.468 23.659c-5.678 0-10.253-4.416-10.253-9.779 0-5.363 4.575-9.78 10.253-9.78s10.253 4.417 10.253 9.78c0 5.363-4.575 9.779-10.253 9.779zm-8.675 15.459h17.351v85.02h-17.351v-85.02zM240.443 124.137V67.352c0-9.937-5.994-16.089-17.824-16.089-6.309 0-12.146 1.104-15.616 2.524v70.35H189.81V43.376c8.518-3.47 19.402-5.994 32.651-5.994 23.819 0 35.333 10.411 35.333 28.393v58.362h-17.351M303.536 125.873c-11.042 0-21.925-2.682-28.55-5.994V.314h17.193v41.012c4.101-1.893 10.726-3.47 16.562-3.47 21.926 0 36.753 15.773 36.753 41.8 0 32.02-16.563 46.217-41.958 46.217zm2.208-74.61c-4.732 0-10.253 1.104-13.565 2.84v55.838c2.524 1.104 7.414 2.208 12.303 2.208 13.723 0 23.819-9.464 23.819-31.231 0-18.613-8.834-29.655-22.557-29.655zM392.341 125.873c-24.449 0-36.752-9.938-36.752-26.658 0-23.66 25.237-27.919 50.948-29.339v-5.363c0-10.726-7.098-14.512-17.982-14.512-8.044 0-17.824 2.524-23.502 5.206l-4.417-11.831c6.783-2.997 18.297-5.994 29.654-5.994 20.348 0 32.652 7.887 32.652 28.866v53.631c-6.152 3.312-18.613 5.994-30.601 5.994zm14.196-44.482c-17.351.946-34.702 2.366-34.702 17.509 0 8.99 6.941 14.511 20.033 14.511 5.521 0 11.988-.946 14.669-2.208V81.391zM461.743 125.873c-9.937 0-20.348-2.682-26.499-5.994l5.836-13.25c4.416 2.681 13.723 5.52 20.19 5.52 9.306 0 15.458-4.574 15.458-11.672 0-7.729-6.467-10.726-15.142-13.881-11.358-4.259-24.134-9.464-24.134-25.395 0-14.039 10.884-23.819 29.812-23.819 10.253 0 18.771 2.524 24.765 5.994l-5.364 11.988c-3.785-2.366-11.356-5.047-17.508-5.047-8.991 0-14.039 4.732-14.039 10.884 0 7.729 6.31 10.41 14.67 13.565 11.83 4.417 24.922 9.306 24.922 25.869 0 15.3-11.672 25.238-32.967 25.238M578.625 81.233l-56.47 7.887c1.735 15.3 11.673 23.029 26.027 23.029 8.517 0 17.666-2.05 23.502-5.205l5.048 12.935c-6.625 3.47-17.982 5.994-29.654 5.994-26.816 0-41.801-17.194-41.801-44.324 0-26.027 14.512-44.167 38.33-44.167 22.083 0 35.175 14.512 35.175 37.384 0 2.05 0 4.259-.157 6.467zm-35.333-31.232c-13.25 0-21.925 10.096-22.241 27.762l41.169-5.679c-.158-14.827-7.571-22.083-18.928-22.083z"/></g></svg>
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1916.3 516.8" width="2500" height="674"><style>.st0{fill:#fff}</style><g id="squares_1_"><path class="st0" d="M578.2 392.7V24.3h25.6v344.1h175.3v24.3H578.2zm327.5 5.1c-39.7 0-70.4-12.8-93.4-37.1-21.7-24.3-33.3-58.8-33.3-103.6 0-43.5 10.2-79.3 32-104.9 21.7-26.9 49.9-39.7 87-39.7 32 0 57.6 11.5 76.8 33.3 19.2 23 28.1 53.7 28.1 92.1v20.5H804.6c0 37.1 9 66.5 26.9 85.7 16.6 20.5 42.2 29.4 74.2 29.4 15.3 0 29.4-1.3 40.9-3.8 11.5-2.6 26.9-6.4 44.8-14.1v24.3c-15.3 6.4-29.4 11.5-42.2 14.1-14.3 2.6-28.9 3.9-43.5 3.8zM898 135.6c-26.9 0-47.3 9-64 25.6-15.3 17.9-25.6 42.2-28.1 75.5h168.9c0-32-6.4-56.3-20.5-74.2-12.8-18-32-26.9-56.3-26.9zm238-21.8c19.2 0 37.1 3.8 51.2 10.2 14.1 7.7 26.9 19.2 38.4 37.1h1.3c-1.3-21.7-1.3-42.2-1.3-62.7V0h24.3v392.7h-16.6l-6.4-42.2c-20.5 30.7-51.2 47.3-89.6 47.3s-66.5-11.5-87-35.8c-20.5-23-29.4-57.6-29.4-102.3 0-47.3 10.2-83.2 29.4-108.7 19.2-25.6 48.6-37.2 85.7-37.2zm0 21.8c-29.4 0-52.4 10.2-67.8 32-15.3 20.5-23 51.2-23 92.1 0 78 30.7 116.4 90.8 116.4 30.7 0 53.7-9 67.8-26.9 14.1-17.9 21.7-47.3 21.7-89.6v-3.8c0-42.2-7.7-72.9-21.7-90.8-12.8-20.5-35.8-29.4-67.8-29.4zm379.9-16.6v17.9l-56.3 3.8c15.3 19.2 23 39.7 23 61.4 0 26.9-9 47.3-26.9 64-17.9 16.6-40.9 24.3-70.4 24.3-12.8 0-21.7 0-25.6-1.3-10.2 5.1-17.9 11.5-23 17.9-5.1 7.7-7.7 14.1-7.7 23s3.8 15.3 10.2 19.2c6.4 3.8 17.9 6.4 33.3 6.4h47.3c29.4 0 52.4 6.4 67.8 17.9s24.3 29.4 24.3 53.7c0 29.4-11.5 51.2-34.5 66.5-23 15.3-56.3 23-99.8 23-34.5 0-61.4-6.4-80.6-20.5-19.2-12.8-28.1-32-28.1-55 0-19.2 6.4-34.5 17.9-47.3s28.1-20.5 47.3-25.6c-7.7-3.8-15.3-9-19.2-15.3-5-6.2-7.7-13.8-7.7-21.7 0-17.9 11.5-34.5 34.5-48.6-15.3-6.4-28.1-16.6-37.1-30.7-9-14.1-12.8-30.7-12.8-48.6 0-26.9 9-49.9 25.6-66.5 17.9-16.6 40.9-24.3 70.4-24.3 17.9 0 32 1.3 42.2 5.1h85.7v1.3h.2zm-222.6 319.8c0 37.1 28.1 56.3 84.4 56.3 71.6 0 107.5-23 107.5-69.1 0-16.6-5.1-28.1-16.6-35.8-11.5-7.7-29.4-11.5-55-11.5h-44.8c-49.9 1.2-75.5 20.4-75.5 60.1zm21.8-235.4c0 21.7 6.4 37.1 19.2 49.9 12.8 11.5 29.4 17.9 51.2 17.9 23 0 40.9-6.4 52.4-17.9 12.8-11.5 17.9-28.1 17.9-49.9 0-23-6.4-40.9-19.2-52.4-12.8-11.5-29.4-17.9-52.4-17.9-21.7 0-39.7 6.4-51.2 19.2-12.8 11.4-17.9 29.3-17.9 51.1z"/><path class="st0" d="M1640 397.8c-39.7 0-70.4-12.8-93.4-37.1-21.7-24.3-33.3-58.8-33.3-103.6 0-43.5 10.2-79.3 32-104.9 21.7-26.9 49.9-39.7 87-39.7 32 0 57.6 11.5 76.8 33.3 19.2 23 28.1 53.7 28.1 92.1v20.5h-197c0 37.1 9 66.5 26.9 85.7 16.6 20.5 42.2 29.4 74.2 29.4 15.3 0 29.4-1.3 40.9-3.8 11.5-2.6 26.9-6.4 44.8-14.1v24.3c-15.3 6.4-29.4 11.5-42.2 14.1-14.1 2.6-28.2 3.8-44.8 3.8zm-6.4-262.2c-26.9 0-47.3 9-64 25.6-15.3 17.9-25.6 42.2-28.1 75.5h168.9c0-32-6.4-56.3-20.5-74.2-12.8-18-32-26.9-56.3-26.9zm245.6-21.8c11.5 0 24.3 1.3 37.1 3.8l-5.1 24.3c-11.8-2.6-23.8-3.9-35.8-3.8-23 0-42.2 10.2-57.6 29.4-15.3 20.5-23 44.8-23 75.5v149.7h-25.6V119h21.7l2.6 49.9h1.3c11.5-20.5 23-34.5 35.8-42.2 15.4-9 30.7-12.9 48.6-12.9zM333.9 12.8h-183v245.6h245.6V76.7c.1-34.5-28.1-63.9-62.6-63.9zm-239.2 0H64c-34.5 0-64 28.1-64 64v30.7h94.7V12.8zM0 165h94.7v94.7H0V165zm301.9 245.6h30.7c34.5 0 64-28.1 64-64V316h-94.7v94.6zm-151-94.6h94.7v94.7h-94.7V316zM0 316v30.7c0 34.5 28.1 64 64 64h30.7V316H0z"/></g></svg>
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="2568" height="723" viewBox="0 0 2568 723" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="#FFF"><path d="M249 0C149.9 0 69.7 80.2 69.7 179.3v67.2C34.9 252.8 0 261.2 0 272.1v350.7s0 9.7 10.9 14.3c39.5 16 194.9 71 230.6 83.6 4.6 1.7 5.9 1.7 7.1 1.7 1.7 0 2.5 0 7.1-1.7 35.7-12.6 191.5-67.6 231-83.6 10.1-4.2 10.5-13.9 10.5-13.9V272.1c0-10.9-34.4-19.7-69.3-25.6v-67.2C428.4 80.2 347.7 0 249 0zm0 85.7c58.4 0 93.7 35.3 93.7 93.7v58.4c-65.5-4.6-121.4-4.6-187.3 0v-58.4c0-58.5 35.3-93.7 93.6-93.7zm-.4 238.1c81.5 0 149.9 6.3 149.9 17.6v218.8c0 3.4-.4 3.8-3.4 5-2.9 1.3-139 50.4-139 50.4s-5.5 1.7-7.1 1.7c-1.7 0-7.1-2.1-7.1-2.1s-136.1-49.1-139-50.4c-2.9-1.3-3.4-1.7-3.4-5V341c-.8-11.3 67.6-17.2 149.1-17.2zM728.547 562.528V322.922H641V237h272.962v85.922h-86.686v239.606zM1134.394 562.528l-44.92-102.36h-35.745v102.36H955V237h173.755c76.27 0 117.175 50.56 117.175 111.536 0 56.198-32.495 85.922-58.587 98.729l58.97 115.168h-111.919v.095zm11.66-213.992c0-17.681-15.674-25.327-32.113-25.327h-60.212v51.419h60.212c16.44-.382 32.113-8.028 32.113-26.092zM1298 562.528V237h246.87v85.922h-148.523v32.113h144.891v85.922h-144.891v35.745h148.523v85.826zM1596 563.528v-78.275l124.056-161.331H1596V238h254.038v77.511L1725.6 477.702h128.07v85.922l-257.67-.096zM1878 400.594C1878 300.623 1955.511 232 2056.247 232c100.354 0 178.248 68.24 178.248 168.594 0 99.972-77.512 168.212-178.248 168.212-100.736 0-178.247-68.24-178.247-168.212zm256.141 0c0-45.398-30.87-81.525-78.276-81.525-47.405 0-78.276 36.127-78.276 81.525s30.87 81.526 78.276 81.526c47.788 0 78.276-36.128 78.276-81.526zM2455.394 563.528l-44.92-102.36h-35.745v102.36H2276V238h173.755c76.27 0 117.175 50.56 117.175 111.536 0 56.198-32.495 85.922-58.587 98.729l58.97 115.168h-111.919v.095zm12.043-214.374c0-17.682-15.675-25.328-32.113-25.328h-60.213v51.42h60.213c16.534-.383 32.113-8.029 32.113-26.092z"/></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -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;
|
||||
|
|
|
@ -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 (
|
||||
<div className="AccountInfo">
|
||||
<div className="AccountInfo-section">
|
||||
<h5 className="AccountInfo-section-header">
|
||||
{translate('sidebar_AccountAddr')}
|
||||
</h5>
|
||||
<div className="AccountInfo-address">
|
||||
<div className="AccountInfo-address-icon">
|
||||
<Identicon address={address} size="100%" />
|
||||
</div>
|
||||
<div className="AccountInfo-address-addr">
|
||||
{address}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="AccountInfo-section">
|
||||
<h5 className="AccountInfo-section-header">
|
||||
{translate('sidebar_AccountBal')}
|
||||
</h5>
|
||||
<ul className="AccountInfo-list">
|
||||
<li className="AccountInfo-list-item">
|
||||
<span
|
||||
className="AccountInfo-list-item-clickable mono wrap"
|
||||
onClick={this.toggleShowLongBalance}
|
||||
title={`${balance.toString()}`}
|
||||
>
|
||||
{this.state.showLongBalance
|
||||
? balance.toString()
|
||||
: formatNumber(balance.amount)}
|
||||
</span>
|
||||
{` ${network.name}`}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{(!!blockExplorer || !!tokenExplorer) &&
|
||||
<div className="AccountInfo-section">
|
||||
<h5 className="AccountInfo-section-header">
|
||||
{translate('sidebar_TransHistory')}
|
||||
</h5>
|
||||
<ul className="AccountInfo-list">
|
||||
{!!blockExplorer &&
|
||||
<li className="AccountInfo-list-item">
|
||||
<a href={blockExplorer.address(address)} target="_blank">
|
||||
{`${network.name} (${blockExplorer.name})`}
|
||||
</a>
|
||||
</li>}
|
||||
{!!tokenExplorer &&
|
||||
<li className="AccountInfo-list-item">
|
||||
<a href={tokenExplorer.address(address)} target="_blank">
|
||||
{`Tokens (${tokenExplorer.name})`}
|
||||
</a>
|
||||
</li>}
|
||||
</ul>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<div className="custom-token-fields">
|
||||
<label>
|
||||
{translate('TOKEN_Addr')}
|
||||
</label>
|
||||
<input
|
||||
className={
|
||||
'form-control input-sm ' +
|
||||
(isValidETHAddress(this.state.address) ? 'is-valid' : 'is-invalid')
|
||||
}
|
||||
type="text"
|
||||
name="address"
|
||||
value={this.state.address}
|
||||
onChange={this.onFieldChange}
|
||||
/>
|
||||
<label>
|
||||
{translate('TOKEN_Symbol')}
|
||||
</label>
|
||||
<input
|
||||
className={
|
||||
'form-control input-sm ' +
|
||||
(this.state.symbol !== '' ? 'is-valid' : 'is-invalid')
|
||||
}
|
||||
type="text"
|
||||
name="symbol"
|
||||
value={this.state.symbol}
|
||||
onChange={this.onFieldChange}
|
||||
/>
|
||||
<label>
|
||||
{translate('TOKEN_Dec')}
|
||||
</label>
|
||||
<input
|
||||
className={
|
||||
'form-control input-sm ' +
|
||||
(isPositiveIntegerOrZero(parseInt(this.state.decimal))
|
||||
? 'is-valid'
|
||||
: 'is-invalid')
|
||||
}
|
||||
type="text"
|
||||
name="decimal"
|
||||
value={this.state.decimal}
|
||||
onChange={this.onFieldChange}
|
||||
/>
|
||||
<div
|
||||
className={`btn btn-primary btn-sm ${this.isValid()
|
||||
? ''
|
||||
: 'disabled'}`}
|
||||
onClick={this.onSave}
|
||||
>
|
||||
{translate('x_Save')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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) });
|
||||
};
|
||||
}
|
|
@ -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 (
|
||||
<aside>
|
||||
<h5>
|
||||
{translate('sidebar_AccountAddr')}
|
||||
</h5>
|
||||
<ul className="account-info">
|
||||
<Identicon address={address} />
|
||||
<span className="mono wrap">
|
||||
{address}
|
||||
</span>
|
||||
</ul>
|
||||
<hr />
|
||||
<h5>
|
||||
{translate('sidebar_AccountBal')}
|
||||
</h5>
|
||||
<ul
|
||||
className="account-info point"
|
||||
onDoubleClick={this.toggleShowLongBalance}
|
||||
title={`${balance.toString()} (Double-Click)`}
|
||||
>
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
{this.state.showLongBalance
|
||||
? balance.toString()
|
||||
: formatNumber(balance)}
|
||||
</span>
|
||||
{` ${network.name}`}
|
||||
</li>
|
||||
</ul>
|
||||
<TokenBalances
|
||||
tokens={tokenBalances}
|
||||
onAddCustomToken={this.props.addCustomToken}
|
||||
onRemoveCustomToken={this.props.removeCustomToken}
|
||||
/>
|
||||
<hr />
|
||||
{(!!blockExplorer || !!tokenExplorer) &&
|
||||
<div>
|
||||
<h5>
|
||||
{translate('sidebar_TransHistory')}
|
||||
</h5>
|
||||
<ul className="account-info">
|
||||
{!!blockExplorer &&
|
||||
<li>
|
||||
<a href={blockExplorer.address(address)} target="_blank">
|
||||
{`${network.name} (${blockExplorer.name})`}
|
||||
</a>
|
||||
</li>}
|
||||
{!!tokenExplorer &&
|
||||
<li>
|
||||
<a href={tokenExplorer.address(address)} target="_blank">
|
||||
{`Tokens (${tokenExplorer.name})`}
|
||||
</a>
|
||||
</li>}
|
||||
</ul>
|
||||
</div>}
|
||||
<hr />
|
||||
{!!Object.keys(rates).length &&
|
||||
<section>
|
||||
<h5>
|
||||
{translate('sidebar_Equiv')}
|
||||
</h5>
|
||||
<ul className="account-info">
|
||||
{rates['BTC'] &&
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
{formatNumber(balance.times(rates['BTC']))}
|
||||
</span>{' '}
|
||||
BTC
|
||||
</li>}
|
||||
{rates['REP'] &&
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
{formatNumber(balance.times(rates['REP']), 2)}
|
||||
</span>{' '}
|
||||
REP
|
||||
</li>}
|
||||
{rates['EUR'] &&
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
€{formatNumber(balance.times(rates['EUR']), 2)}
|
||||
</span>
|
||||
{' EUR'}
|
||||
</li>}
|
||||
{rates['USD'] &&
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
${formatNumber(balance.times(rates['USD']), 2)}
|
||||
</span>
|
||||
{' USD'}
|
||||
</li>}
|
||||
{rates['GBP'] &&
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
£{formatNumber(balance.times(rates['GBP']), 2)}
|
||||
</span>
|
||||
{' GBP'}
|
||||
</li>}
|
||||
{rates['CHF'] &&
|
||||
<li>
|
||||
<span className="mono wrap">
|
||||
{formatNumber(balance.times(rates['CHF']), 2)}
|
||||
</span>{' '}
|
||||
CHF
|
||||
</li>}
|
||||
</ul>
|
||||
<Link to={'swap'} className="btn btn-primary btn-sm">
|
||||
Swap via bity
|
||||
</Link>
|
||||
</section>}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
|
@ -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 (
|
||||
<div className="EquivalentValues">
|
||||
<h5 className="EquivalentValues-title">
|
||||
{translate('sidebar_Equiv')}
|
||||
</h5>
|
||||
|
||||
<ul className="EquivalentValues-values">
|
||||
{ratesKeys.map(key => {
|
||||
if (!rates[key]) return null;
|
||||
return (
|
||||
<li className="EquivalentValues-values-currency" key={key}>
|
||||
<span className="EquivalentValues-values-currency-label">
|
||||
{key}:
|
||||
</span>
|
||||
<span className="EquivalentValues-values-currency-value">
|
||||
{' '}{formatNumber(balance.amount.times(rates[key]))}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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: [<h6 key="1">Learn more about protecting your funds.</h6>],
|
||||
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: [
|
||||
<p key="1">It’s now easier to get more ETH</p>,
|
||||
<h5 key="2">Buy ETH with USD</h5>
|
||||
],
|
||||
images: [require('assets/images/logo-coinbase.svg')]
|
||||
},
|
||||
{
|
||||
color: '#006e79',
|
||||
href: '/swap',
|
||||
texts: [
|
||||
<p key="1">It’s now easier to get more ETH</p>,
|
||||
<h5 key="2">Swap BTC <-> ETH</h5>
|
||||
],
|
||||
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 = (
|
||||
<div className="Promos-promo-inner">
|
||||
<div className="Promos-promo-text">
|
||||
{promo.texts}
|
||||
</div>
|
||||
<div className="Promos-promo-images">
|
||||
{promo.images.map((img, idx) => <img src={img} key={idx} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const promoEl = promo.isExternal
|
||||
? <a
|
||||
className="Promos-promo"
|
||||
key={promo.href}
|
||||
href={promo.href}
|
||||
style={{ backgroundColor: promo.color }}
|
||||
>
|
||||
{promoContent}
|
||||
</a>
|
||||
: <Link
|
||||
className="Promos-promo"
|
||||
key={promo.href}
|
||||
to={promo.href}
|
||||
style={{ backgroundColor: promo.color }}
|
||||
>
|
||||
<div className="Promos-promo-inner">
|
||||
{promoContent}
|
||||
</div>
|
||||
</Link>;
|
||||
|
||||
return (
|
||||
<div className="Promos">
|
||||
{promoEl}
|
||||
<div className="Promos-nav">
|
||||
{promos.map((promo, idx) => {
|
||||
return (
|
||||
<button
|
||||
className={`Promos-nav-btn ${idx === activePromo
|
||||
? 'is-active'
|
||||
: ''}`}
|
||||
key={idx}
|
||||
onClick={() => this._navigateToPromo(idx)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<section className="token-balances">
|
||||
<h5>{translate('sidebar_TokenBal')}</h5>
|
||||
<table className="account-info">
|
||||
<tbody>
|
||||
{tokens
|
||||
.filter(
|
||||
token =>
|
||||
!token.balance.eq(0) ||
|
||||
token.custom ||
|
||||
this.state.showAllTokens
|
||||
)
|
||||
.map(token =>
|
||||
<TokenRow
|
||||
key={token.symbol}
|
||||
balance={token.balance}
|
||||
symbol={token.symbol}
|
||||
custom={token.custom}
|
||||
onRemove={this.props.onRemoveCustomToken}
|
||||
/>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<a
|
||||
className="btn btn-default btn-xs"
|
||||
onClick={this.toggleShowAllTokens}
|
||||
>
|
||||
{!this.state.showAllTokens ? 'Show All Tokens' : 'Hide Tokens'}
|
||||
</a>{' '}
|
||||
<a
|
||||
className="btn btn-default btn-xs"
|
||||
onClick={this.toggleShowCustomTokenForm}
|
||||
>
|
||||
<span>
|
||||
{translate('SEND_custom')}
|
||||
</span>
|
||||
</a>
|
||||
{this.state.showCustomTokenForm &&
|
||||
<AddCustomTokenForm onSave={this.addCustomToken} />}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
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 });
|
||||
};
|
||||
}
|
|
@ -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 (
|
||||
<form className="AddCustom" onSubmit={this.onSave}>
|
||||
{fields.map(field => {
|
||||
return (
|
||||
<label className="AddCustom-field form-group" key={field.name}>
|
||||
<span className="AddCustom-field-label">
|
||||
{field.label}
|
||||
</span>
|
||||
<input
|
||||
className={classnames(
|
||||
inputClasses,
|
||||
errors[field.name] ? 'is-invalid' : 'is-valid'
|
||||
)}
|
||||
type="text"
|
||||
name={field.name}
|
||||
value={field.value}
|
||||
onChange={this.onFieldChange}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
|
||||
<button
|
||||
className="btn btn-primary btn-sm btn-block"
|
||||
disabled={!this.isValid()}
|
||||
>
|
||||
{translate('x_Save')}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
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) });
|
||||
};
|
||||
}
|
|
@ -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 (
|
||||
<tr>
|
||||
<tr className="TokenRow">
|
||||
<td
|
||||
className="mono wrap point"
|
||||
className="TokenRow-balance"
|
||||
title={`${balance.toString()} (Double-Click)`}
|
||||
onDoubleClick={this.toggleShowLongBalance}
|
||||
>
|
||||
{!!custom &&
|
||||
<img
|
||||
src={removeIcon}
|
||||
className="token-remove"
|
||||
className="TokenRow-balance-remove"
|
||||
title="Remove Token"
|
||||
onClick={this.onRemove}
|
||||
tabIndex="0"
|
||||
/>}
|
||||
<span>
|
||||
{showLongBalance ? balance.toString() : formatNumber(balance)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td className="TokenRow-symbol">
|
||||
{symbol}
|
||||
</td>
|
||||
</tr>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 (
|
||||
<section className="TokenBalances">
|
||||
<h5 className="TokenBalances-title">
|
||||
{translate('sidebar_TokenBal')}
|
||||
</h5>
|
||||
<table className="TokenBalances-rows">
|
||||
<tbody>
|
||||
{shownTokens.map(token =>
|
||||
<TokenRow
|
||||
key={token.symbol}
|
||||
balance={token.balance}
|
||||
symbol={token.symbol}
|
||||
custom={token.custom}
|
||||
onRemove={this.props.onRemoveCustomToken}
|
||||
/>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="TokenBalances-buttons">
|
||||
<button
|
||||
className="btn btn-default btn-xs"
|
||||
onClick={this.toggleShowAllTokens}
|
||||
>
|
||||
{!this.state.showAllTokens ? 'Show All Tokens' : 'Hide Tokens'}
|
||||
</button>{' '}
|
||||
<button
|
||||
className="btn btn-default btn-xs"
|
||||
onClick={this.toggleShowCustomTokenForm}
|
||||
>
|
||||
<span>
|
||||
{translate('SEND_custom')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{this.state.showCustomTokenForm &&
|
||||
<div className="TokenBalances-form">
|
||||
<AddCustomTokenForm onSave={this.addCustomToken} />
|
||||
</div>}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
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 });
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
// @flow
|
||||
|
||||
export { default } from './BalanceSidebar';
|
|
@ -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: (
|
||||
<AccountInfo wallet={wallet} balance={balance} network={network} />
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Promos',
|
||||
isFullWidth: true,
|
||||
content: <Promos />
|
||||
},
|
||||
{
|
||||
name: 'Token Balances',
|
||||
content: (
|
||||
<TokenBalances
|
||||
tokens={tokenBalances}
|
||||
onAddCustomToken={this.props.addCustomToken}
|
||||
onRemoveCustomToken={this.props.removeCustomToken}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'Equivalent Values',
|
||||
content: <EquivalentValues balance={balance} rates={rates} />
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<aside>
|
||||
{blocks.map(block =>
|
||||
<section
|
||||
className={`Block ${block.isFullWidth ? 'is-full-width' : ''}`}
|
||||
key={block.name}
|
||||
>
|
||||
{block.content}
|
||||
</section>
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
|
@ -285,13 +285,7 @@ export class SendTransaction extends React.Component {
|
|||
{/* Sidebar */}
|
||||
{unlocked &&
|
||||
<section className="col-sm-4">
|
||||
<div className="Tab-content-pane">
|
||||
<div>
|
||||
<BalanceSidebar />
|
||||
<hr />
|
||||
<Donate onDonate={this.onNewTx} />
|
||||
</div>
|
||||
</div>
|
||||
</section>}
|
||||
</div>
|
||||
|
||||
|
@ -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),
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ function* updateAccountBalance(): Generator<Yield, Return, Next> {
|
|||
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 });
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: {}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue