Confirm TX Modal Upgrades (#928)

* Wipe tx modal clean & Update the subcomponents

* Add Amounts & Address styles

* Forgot to pass onlyIncludeLoader to GasLimitLoading component

* Add currency conversion

* Update styles

* Change SENDModal_Yes & _No messages

* Add visual summary

* Update fonts & add Roboto Mono

* Add details to tx-modal

* Display contract addr when sending tokens

* Add inline styles back to identicon (for paper wallet)

* Remove inline styles

* Update USD conversion conditions

* Display token to usd conversion

* Update styles

* Update modal styles

* Animate modals

* Add a fade effect when modal overflows

* Improve styles for mobile

* Remove dead code

* Update unlockHeader close button

* Update text overflow fade styles

* Fix invalid inline css prop

* Fix issue with 'isToken' condition

* Add table layout & update styles

* Remove unsupported styles

* Remove formatting diff

* update styles

* Update tx modal fixes (#999)

* chore(package): update @types/lodash to version 4.14.101 (#992)

* ENS Resolving (#942)

* Refactor BaseNode to be an interface INode

* Initial contract commit

* Remove redundant fallback ABI function

* First working iteration of Contract generator to be used in ENS branch

* Hide abi to clean up logging output

* Strip 0x prefix from output decode

* Handle unnamed output params

* Implement ability to supply output mappings to ABI functions

* Fix null case in outputMapping

* Add flow typing

* Add .call method to functions

* Partial commit for type refactor

* Temp contract type fix -- waiting for NPM modularization

* Remove empty files

* Cleanup contract

* Add call request to node interface

* Fix output mapping types

* Revert destructuring overboard

* Add sendCallRequest to rpcNode class and add typing

* Use enum for selecting ABI methods

* Add transaction capability to contracts

* Cleanup privaite/public members

* Remove broadcasting step from a contract transaction

* Cleanup uneeded types

* Refactor ens-base to typescript and add typings for ENS smart contracts

* Migrate ens-name-search to TS

* Add IResolveDomainRequest

* Fix rest of TSC errors

* Add definition file for bn.js

* Remove types-bn

* Fix some typings

* make isBN a static property

* progress commit -- swap out bignumber.js for bn.js

* Swap out bignumber for bn in vendor

* Change modn to number return

* Start to strip out units lib for a string manipulation based lib

* Convert codebase to only base units

* Get rid of useless component

* Handle only wei in values

* Use unit conversion in sidebar

* Automatically strip hex prefix, and  handle decimal edge case

* Handle base 16 wei in transactions

* Make a render callback component for dealing with unit conversion

* Switch contracts to use bn.js, and get transaction values from signedTx instead of state

* Get send transaction  working with bn.js

* Remove redundant hex stripping,  return base value of tokens

* Cleanup unit file

* Re-implement toFixed for strings

* Use formatNumber in codebase

* Cleanup code

* Undo package test changes

* Update snapshot and remove console logs

* Use TokenValue / Wei more consistently where applicable

* Add typing to deterministicWallets, fix confirmation modal, make UnitDisplay more flexible

* Split different ENS modes into their own components

* Fix Abi typedef

* Remove redundant moment type package

* Add Aux helper component

* Split out resolve components

* Make 'to' parameter optional

* Change import type

* Change typing to be base domain request

* Split handling of resolving into object handler

* Fix countdown component

* Adjust element spacing

* Implement reveal search functionality

* Add unit display for highest bidder

* Fill out forbidden/NYA modes

* ENS wallet component skeleton

* Clean up prop handling in UnitDisplay

* Change instanceof to typeof check, change boolean of displayBalance

* Add ENS wallet component

* Cleanup spacing

* Convert ConfModal for bidding in ENS

* Make ui component for placing bids

* Fix destructure in placeBid

* Pass through entire wallet

* Remove text center

* Display inline notification ENS isValid & add some ENS tests

* Add export of Aux

* Reformat with prettier

* progress...

* Add ENSUnlockLayout

* Add RevealBid component

* organize NameResolve components

* Merge ENS with transaction-refactor changes

* Fix address resolution

* Update styles

* convert ens name to lowercase before checking

* Add overflow-y:scroll to table

* update ens snapshots & tests

* cast 'undefined' state argument as any for testing

* clean up components

* Connect unitconverter to redux state

* remove unnecessary type assertion

* fix spinner size

* remove old bidmodal

* validate bidmask before opening modal

* progress...

* Update styles

* Add saga / actions for placing a bid

* Update types & clean up dead code

* Delete old test

* Dispatch PlaceBidRequested acitons

* Progress commit -- get ENS bidding ready for tx generation via sagas

* Seperate ENS action creators and types

* Add reducer & actions for ENS fields

* Add preliminary sagas for bid mask and bid value

* Fix ts errors

* Get bidding fields connected with some validation

* Clean up generate bid

* Hook up generate bid to redux state

* Get bid data generation working

* Add support for bidding on already open auctions

* Move bid generation states to redux, improve default field values

* Remove generate bid component

* Throttle bid generation

* Progress commit -- Bid Modal

* Hook bidmodal component up to bidding component

* Update template modal to handle custom confirm behavior

* Remove old redux bidding actions, add new one for downloaded bids

* Save downloaded bids to local storage

* Finish bidding modal

* Fix gas estimation bug

* Fix typing

* Remove bidding related functionality

* Get passing unit tests

* Make previous test more comprehensive

* Fix ts errors

* Remove commented code

* Fix invalid return

* Remove implementation of revealing bid

* Update snapshot

* Fix tests

* Delegate bidding to V3

* Update react-markdown to the latest version 🚀 (#986)

* fix(package): update react-markdown to version 3.1.5

* Fix tsc errors, match original behaviour of V2 as closely as possible

* Add tooltip to gas slider (#997)

* Prevent invalid gas price states (#996)

* Slider using value instead of raw to prevent errors. Dont show empty gas price as invalid. Clamp slider values to min / max on mount.

* Remove gas price from local storage.

* Update @types/react to the latest version 🚀 (#912)

* chore(package): update @types/react to version 16.0.35

* Add stricter typing via function overloads

* Fix rest of aria translations

* Make implementation of confirmation modal template

* Address github comments for #928

* Make modal state setting more explicit

* Fix infinite loop of state setting on modal

* Fix transaction rebroadcasting for modal display
This commit is contained in:
James Prado 2018-02-06 23:39:24 -05:00 committed by Daniel Ternyak
parent e7633c6d2f
commit 0da409cd12
68 changed files with 942 additions and 693 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
<g class="nc-icon-wrapper" fill="#000000">
<path d="M38 12.83L35.17 10 24 21.17 12.83 10 10 12.83 21.17 24 10 35.17 12.83 38 24 26.83 35.17 38 38 35.17 26.83 24z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 48 48" xml:space="preserve" width="48" height="48"><g class="nc-icon-wrapper"><path fill="#0e97c0" d="M34,27h-8V3c0-0.55228-0.44772-1-1-1h-2c-0.55228,0-1,0.44772-1,1v24h-8 c-0.375,0-0.71777,0.20947-0.88965,0.54248c-0.1709,0.33301-0.1416,0.73389,0.07617,1.03857l10,14 C23.37402,42.84424,23.67676,43,24,43s0.62598-0.15576,0.81348-0.41895l10-14c0.21777-0.30469,0.24707-0.70557,0.07617-1.03857 C34.71777,27.20947,34.375,27,34,27z"></path></g></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@ -1,23 +0,0 @@
@font-face {
font-family: 'Lato';
src: url('../fonts/Lato-Light.woff2') format('woff2'),
url('../fonts/Lato-Light.woff') format('woff');
font-style: normal;
font-weight: 300;
}
@font-face {
font-family: 'Lato';
src: url('../fonts/Lato-Regular.woff2') format('woff2'),
url('../fonts/Lato-Regular.woff') format('woff');
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: 'Lato';
src: url('../fonts/Lato-Bold.woff2') format('woff2'),
url('../fonts/Lato-Bold.woff') format('woff');
font-style: normal;
font-weight: 700;
}

View File

@ -1,8 +1,7 @@
@import "etherwallet-variables.less";
@import "etherwallet-fonts.less";
@import 'etherwallet-variables.less';
// Core variables and mixins
@import "bootstrap/mixins.less";
@import 'bootstrap/mixins.less';
// Utility classes
@import "etherwallet-custom.less";
@import "etherwallet-ext-custom.less";
@import "etherwallet-utilities.less";
@import 'etherwallet-custom.less';
@import 'etherwallet-ext-custom.less';
@import 'etherwallet-utilities.less';

View File

@ -3,19 +3,19 @@
@ether-blue: #0e97c0;
@space-xs: 0.25rem;
@space-sm: 0.50rem;
@space-sm: 0.5rem;
@space-md: 0.75rem;
@space: 1.00rem;
@space-lg: 1.50rem;
@space-xl: 2.00rem;
@space: 1rem;
@space-lg: 1.5rem;
@space-xl: 2rem;
@gray-base: #000;
@gray-darker: lighten(@gray-base, 13.5%);
@gray-dark: lighten(@gray-base, 20%);
@gray: #737373;
@gray-light: #9A9A9A;
@gray-lighter: #ECECEC;
@gray-lightest: #FAFAFA;
@gray-light: #9a9a9a;
@gray-lighter: #ececec;
@gray-lightest: #fafafa;
@brand-primary: @ether-blue;
@brand-success: #5dba5a;
@ -34,8 +34,8 @@
// Typography
@font-family-sans-serif: 'Lato', sans-serif;
@font-family-serif: Georgia, "Times New Roman", Times, serif;
@font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
@font-family-serif: Georgia, 'Times New Roman', Times, serif;
@font-family-monospace: 'Roboto Mono', Menlo, Monaco, Consolas, 'Courier New', monospace;
@font-family-base: @font-family-sans-serif;
@base: 15;
@ -44,14 +44,14 @@
@font-size-pixels-sm: @base+px; // for small screens
@font-size-large-bump: 2.25rem; // 33.75
@font-size-large: 1.90rem; // 28.5
@font-size-medium-bump: 1.50rem; // 22.5
@font-size-medium: 1.30rem; // 19.5
@font-size-large: 1.9rem; // 28.5
@font-size-medium-bump: 1.5rem; // 22.5
@font-size-medium: 1.3rem; // 19.5
@font-size-bump-more: 1.15rem; // 17.25
@font-size-bump: 1.07rem; // 16.05
@font-size-base: 1.00rem; // 15
@font-size-base: 1rem; // 15
@font-size-small: 0.92rem; // 13.8
@font-size-xs: 0.80rem; // 12
@font-size-xs: 0.8rem; // 12
@font-size-h1: @font-size-large-bump;
@font-size-h2: @font-size-large;
@ -158,7 +158,7 @@
@cursor-disabled: default;
@dropdown-bg: #fff;
@dropdown-border: rgba(0, 0, 0, .15);
@dropdown-border: rgba(0, 0, 0, 0.15);
@dropdown-fallback-border: @gray-lighter;
@dropdown-divider-bg: #e5e5e5;
@ -240,7 +240,7 @@
@tooltip-max-width: 200px;
@tooltip-color: #fff;
@tooltip-bg: #000;
@tooltip-opacity: .9;
@tooltip-opacity: 0.9;
@tooltip-arrow-width: @space-sm;
@tooltip-arrow-color: @tooltip-bg;
@ -261,11 +261,11 @@
@modal-title-line-height: @line-height-base;
@modal-content-bg: #fff;
@modal-content-border-color: rgba(0, 0, 0, .2);
@modal-content-border-color: rgba(0, 0, 0, 0.2);
@modal-content-fallback-border-color: #999;
@modal-backdrop-bg: #000;
@modal-backdrop-opacity: .5;
@modal-backdrop-opacity: 0.5;
@modal-header-border-color: #e5e5e5;
@modal-footer-border-color: @modal-header-border-color;

View File

@ -0,0 +1,12 @@
import { Body } from './components';
import {
ConfirmationModalTemplate,
OwnProps as TemplateProps
} from 'components/ConfirmationModalTemplate';
import React from 'react';
import { Omit } from 'react-redux';
type Props = Omit<TemplateProps, 'Body'>;
export const ConfirmationModal: React.SFC<Props> = props => (
<ConfirmationModalTemplate Body={<Body />} {...props} />
);

View File

@ -1,48 +0,0 @@
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { UnitDisplay } from 'components/ui';
import { Wei, TokenValue } from 'libs/units';
import ERC20 from 'libs/erc20';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getDecimal, getUnit } from 'selectors/transaction';
import { getNetworkConfig } from 'selectors/config';
interface StateProps {
unit: string;
decimal: number;
network: AppState['config']['network'];
}
class AmountClass extends Component<StateProps> {
public render() {
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { value, data } = getTransactionFields(transactionInstance);
const { decimal, unit, network } = this.props;
const isToken = unit !== 'ether';
const handledValue = isToken
? TokenValue(ERC20.transfer.decodeInput(data)._value)
: Wei(value);
return (
<UnitDisplay
decimal={decimal}
value={handledValue}
symbol={isToken ? unit : network.unit}
checkOffline={false}
/>
);
}}
/>
);
}
}
export const Amount = connect((state: AppState) => ({
decimal: getDecimal(state),
unit: getUnit(state),
network: getNetworkConfig(state)
}))(AmountClass);

View File

@ -0,0 +1,12 @@
@import 'common/sass/variables';
.tx-modal-body {
margin: auto;
max-width: 35rem;
width: 100%;
}
.tx-modal-testnet-warn {
text-align: center;
margin: 0;
}

View File

@ -0,0 +1,59 @@
import { Addresses } from './components/Addresses';
import { Amounts } from './components/Amounts';
import { Details } from './components/Details';
import React from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import './Body.scss';
import { getNetworkConfig } from 'selectors/config';
interface State {
showDetails: boolean;
}
interface StateProps {
network: AppState['config']['network'];
}
class BodyClass extends React.Component<StateProps, State> {
public state: State = {
showDetails: false
};
public toggleDetails = () => {
this.setState({
showDetails: !this.state.showDetails
});
};
public render() {
const { showDetails } = this.state;
return (
<div className="tx-modal-body">
{this.props.network.isTestnet && (
<p className="tx-modal-testnet-warn small">Testnet Transaction</p>
)}
<Addresses />
<Amounts />
<button
className={`tx-modal-details-button ${
showDetails ? 'tx-modal-details-button--open' : ''
}`}
onClick={this.toggleDetails}
>
Details
</button>
{showDetails && <Details />}
</div>
);
}
}
const mapStateToProps = (state: AppState): StateProps => {
return {
network: getNetworkConfig(state)
};
};
export const Body = connect(mapStateToProps)(BodyClass);

View File

@ -0,0 +1,84 @@
@import 'common/sass/variables';
.tx-modal-address {
display: flex;
flex-direction: column;
margin: auto;
padding: 1rem 0;
align-items: center;
.Identicon {
margin-right: 1rem;
}
&-from,
&-tkn-contract,
&-to,
&-send-amount {
display: flex;
align-items: center;
margin: 1rem 0rem;
width: inherit;
&-icon {
margin-right: 1rem;
}
&:first-child {
margin-top: 0;
}
&-content {
width: inherit;
}
&-title {
margin: 0;
margin-right: 16px;
padding-bottom: 0.25rem;
}
&-address {
margin: 0;
font-family: $font-family-monospace;
font-weight: 400;
opacity: 0.54;
word-wrap: break-word;
}
}
&-tkn-contract {
&-icon {
> img {
transform: scale(0.75);
}
}
&-title {
margin: 0;
padding: 0;
font-weight: 400;
color: rgba(0, 0, 0, 0.54);
}
&-link {
font-family: $font-family-monospace;
font-weight: 400;
}
}
&-send-amount {
> img {
height: 3rem;
width: 3rem;
transform: scale(0.75);
}
}
@media screen and (max-width: 525px) {
width: inherit;
.Identicon {
display: none;
}
&-tkn-contract {
&-icon {
display: none;
}
&-link {
width: inherit;
word-wrap: break-word;
}
}
}
}

View File

@ -0,0 +1,80 @@
import React, { Component } from 'react';
import ERC20 from 'libs/erc20';
import { Identicon } from 'components/ui';
import arrow from 'assets/images/tail-triangle-down.svg';
import './Addresses.scss';
import { ETHAddressExplorer } from 'config';
import { connect } from 'react-redux';
import { SerializedTransaction } from 'components/renderCbs';
import { AppState } from 'reducers';
import { getFrom, getUnit, isEtherTransaction } from 'selectors/transaction';
interface StateProps {
from: AppState['transaction']['meta']['from'];
unit: AppState['transaction']['meta']['unit'];
isToken: boolean;
}
const size = '3rem';
class AddressesClass extends Component<StateProps> {
public render() {
const { from, isToken, unit } = this.props;
return (
<SerializedTransaction
withSerializedTransaction={(_, { to, data }) => {
const toFormatted = isToken ? ERC20.transfer.decodeInput(data)._to : to;
return (
<div className="tx-modal-address">
<div className="tx-modal-address-from">
{from && (
<Identicon className="tx-modal-address-from-icon" size={size} address={from} />
)}
<div className="tx-modal-address-from-content">
<h5 className="tx-modal-address-from-title">From </h5>
<h5 className="tx-modal-address-from-address small">{from}</h5>
</div>
</div>
{isToken && (
<div className="tx-modal-address-tkn-contract">
<div className="tx-modal-address-tkn-contract-icon">
<img src={arrow} alt="arrow" />
</div>
<div className="tx-modal-address-tkn-contract-content">
<p className="tx-modal-address-tkn-contract-title">via the {unit} contract</p>
<a
className="small tx-modal-address-tkn-contract-link"
href={ETHAddressExplorer(to)}
>
{to}
</a>
</div>
</div>
)}
<div className="tx-modal-address-to">
<Identicon
className="tx-modal-address-from-icon"
size={size}
address={toFormatted}
/>
<div className="tx-modal-address-to-content">
<h5 className="tx-modal-address-to-title">To </h5>
<h5 className="small tx-modal-address-to-address">{toFormatted}</h5>
</div>
</div>
</div>
);
}}
/>
);
}
}
const mapStateToProps = (state: AppState): StateProps => ({
from: getFrom(state),
isToken: !isEtherTransaction(state),
unit: getUnit(state)
});
export const Addresses = connect(mapStateToProps)(AddressesClass);

View File

@ -0,0 +1,48 @@
@import 'common/sass/variables';
.tx-modal-amount {
width: inherit;
border-collapse: separate;
padding-top: 1rem;
padding-bottom: 0;
&-send,
&-fee,
&-total {
font-size: 1.15rem;
@media screen and (max-width: 525px) {
font-size: 1rem;
}
> td {
padding: 0.5rem 0;
text-align: right;
opacity: 0.54;
&:not(:first-child) {
padding-left: 0.5rem;
}
&:first-child {
text-align: left;
opacity: 1;
}
&:last-child {
font-size: 85%;
}
}
}
&-fee {
> td {
padding-bottom: 1rem;
}
}
&-total {
background-image: linear-gradient(to right, #c2cfd6 33%, #fff0 0%);
background-position: top;
background-size: 5px 1px;
background-repeat: repeat-x;
> td {
padding-top: 1rem;
padding-bottom: 1.5rem;
}
}
}

View File

@ -0,0 +1,114 @@
import React, { Component } from 'react';
import { UnitDisplay } from 'components/ui';
import './Amounts.scss';
import { AppState } from 'reducers';
import { getAllUSDValuesFromSerializedTx, AllUSDValues } from 'selectors/rates';
import { SerializedTxParams, getParamsFromSerializedTx } from 'selectors/transaction';
import { connect } from 'react-redux';
import { getNetworkConfig } from 'selectors/config';
import { NetworkConfig } from 'config';
interface StateProps extends SerializedTxParams, AllUSDValues {
network: NetworkConfig;
}
class AmountsClass extends Component<StateProps> {
public render() {
const {
unit,
decimal,
feeUSD,
totalUSD,
valueUSD,
isToken,
currentValue,
fee,
total,
network
} = this.props;
const showConversion = valueUSD && totalUSD && feeUSD;
return (
<table className="tx-modal-amount">
<tbody>
<tr className="tx-modal-amount-send">
<td>You'll Send</td>
<td>
<UnitDisplay
value={currentValue}
decimal={decimal}
displayShortBalance={6}
symbol={unit}
/>
</td>
{showConversion && (
<td>
$<UnitDisplay
value={valueUSD}
unit="ether"
displayShortBalance={2}
displayTrailingZeroes={true}
checkOffline={true}
/>
</td>
)}
</tr>
<tr className="tx-modal-amount-fee">
<td>Transaction Fee</td>
<td>
<UnitDisplay
value={fee}
unit={'ether'}
displayShortBalance={false}
symbol={network.unit}
/>
</td>
{showConversion && (
<td>
$<UnitDisplay
value={feeUSD}
unit="ether"
displayShortBalance={2}
displayTrailingZeroes={true}
checkOffline={true}
/>
</td>
)}
</tr>
{!isToken && (
<tr className="tx-modal-amount-total">
<td>Total</td>
<td>
<UnitDisplay
value={total}
decimal={decimal}
displayShortBalance={false}
symbol={network.unit}
/>
</td>
{showConversion && (
<td>
$<UnitDisplay
value={totalUSD}
unit="ether"
displayShortBalance={2}
displayTrailingZeroes={true}
checkOffline={true}
/>
</td>
)}
</tr>
)}
</tbody>
</table>
);
}
}
const mapStateToProps = (state: AppState): StateProps => ({
...getParamsFromSerializedTx(state),
...getAllUSDValuesFromSerializedTx(state),
network: getNetworkConfig(state)
});
export const Amounts = connect(mapStateToProps)(AmountsClass);

View File

@ -0,0 +1,21 @@
@import 'common/sass/variables';
.tx-modal-details {
margin-top: 1rem;
&-network-info {
text-align: center;
font-weight: 400;
color: rgba(0, 0, 0, 0.54);
}
&-button {
display: block;
margin: auto;
padding: 8px 32px;
border: none;
border-radius: 2px;
transition: background-color 300ms;
&:hover {
background-color: rgba(153, 153, 153, 0.12);
}
}
}

View File

@ -0,0 +1,44 @@
import React, { Component } from 'react';
import Code from 'components/ui/Code';
import './Details.scss';
import { SerializedTransaction } from 'components/renderCbs';
import { AppState } from 'reducers';
import { getNodeConfig } from 'selectors/config';
import { NodeConfig } from 'config';
import { connect } from 'react-redux';
import { TokenValue } from 'libs/units';
interface StateProps {
node: NodeConfig;
}
class DetailsClass extends Component<StateProps> {
public render() {
const { node: { network, service } } = this.props;
return (
<div className="tx-modal-details">
<p className="tx-modal-details-network-info">
Interacting with the {network} network provided by {service}
</p>
<SerializedTransaction
withSerializedTransaction={(_, fields) => {
const { chainId, data, to, ...convertRestToBase10 } = fields;
const base10Fields = Object.entries(convertRestToBase10).reduce(
(convertedFields, [currName, currValue]) => ({
...convertedFields,
[currName]: TokenValue(currValue).toString()
}),
{} as typeof convertRestToBase10
);
return <Code>{JSON.stringify({ chainId, data, to, ...base10Fields }, null, 2)} </Code>;
}}
/>
</div>
);
}
}
const mapStateToProps = (state: AppState) => ({ node: getNodeConfig(state) });
export const Details = connect(mapStateToProps)(DetailsClass);

View File

@ -0,0 +1 @@
export * from './Body';

View File

@ -1,44 +0,0 @@
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import ERC20 from 'libs/erc20';
import { From } from '../From';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getUnit } from 'selectors/transaction';
import { AppState } from 'reducers';
interface StateProps {
unit: string;
}
class AddressesClass extends Component<StateProps> {
public render() {
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { to, data } = getTransactionFields(transactionInstance);
return (
<React.Fragment>
<li className="ConfModal-details-detail">
You are sending from <From withFrom={from => <code>{from}</code>} />
</li>
<li className="ConfModal-details-detail">
You are sending to{' '}
<code>
{this.props.unit === 'ether' ? to : ERC20.transfer.decodeInput(data)._to}
</code>
</li>
</React.Fragment>
);
}}
/>
);
}
}
export const Addresses = connect((state: AppState) => ({ unit: getUnit(state) }))(AddressesClass);
//got duplication here

View File

@ -1,18 +0,0 @@
import { TransactionFee } from './components';
import { Amount } from '../../Amount';
import React from 'react';
export const AmountAndGasPrice: React.SFC<{}> = () => (
<li className="ConfModal-details-detail">
<p>
You are sending{' '}
<strong>
<Amount />
</strong>{' '}
with a transaction fee of{' '}
<strong>
<TransactionFee />
</strong>
</p>
</li>
);

View File

@ -1,18 +0,0 @@
import React from 'react';
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { UnitDisplay } from 'components/ui';
import { Wei } from 'libs/units';
export const GasPrice: React.SFC<{}> = () => (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { gasPrice } = getTransactionFields(transactionInstance);
return (
<UnitDisplay unit={'gwei'} value={Wei(gasPrice)} symbol={'gwei'} checkOffline={false} />
);
}}
/>
);

View File

@ -1,57 +0,0 @@
import React from 'react';
import { getTransactionFee, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { UnitDisplay } from 'components/ui';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { getNetworkConfig } from 'selectors/config';
import BN from 'bn.js';
interface Props {
rates: AppState['rates']['rates'];
network: AppState['config']['network'];
isOffline: AppState['config']['offline'];
}
class TransactionFeeClass extends React.Component<Props> {
public render() {
const { rates, network, isOffline } = this.props;
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const fee = getTransactionFee(transactionInstance);
const usdFee = network.isTestnet ? new BN(0) : fee.muln(rates[network.unit].USD);
return (
<React.Fragment>
<UnitDisplay unit={'ether'} value={fee} symbol={network.unit} checkOffline={false} />{' '}
{!isOffline &&
rates[network.unit] && (
<span>
($
<UnitDisplay
value={usdFee}
unit="ether"
displayShortBalance={2}
displayTrailingZeroes={true}
checkOffline={true}
/>)
</span>
)}
</React.Fragment>
);
}}
/>
);
}
}
function mapStateToProps(state: AppState) {
return {
rates: state.rates.rates,
network: getNetworkConfig(state),
isOffline: state.config.offline
};
}
export const TransactionFee = connect(mapStateToProps)(TransactionFeeClass);

View File

@ -1,2 +0,0 @@
export * from './GasPrice';
export * from './TransactionFee';

View File

@ -1 +0,0 @@
export * from './AmountAndGasPrice';

View File

@ -1,44 +0,0 @@
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { isEtherTransaction } from 'selectors/transaction';
interface StateProps {
showData: boolean;
}
class ShowDataWhenNoTokenClass extends Component<StateProps> {
public render() {
return this.props.showData ? <Data /> : null;
}
}
const ShowDataWhenNoToken = connect((state: AppState) => ({ showData: isEtherTransaction(state) }))(
ShowDataWhenNoTokenClass
);
const Data: React.SFC<{}> = () => (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { data } = getTransactionFields(transactionInstance);
const dataBox = (
<span>
You are sending the following data:{' '}
<textarea className="form-control" value={data} rows={3} disabled={true} />
</span>
);
return (
<li className="ConfModal-details-detail">
{!emptyData(data) ? dataBox : 'There is no data attached to this transaction'}
</li>
);
}}
/>
);
const emptyData = (data: string) => data === '0x';
export { ShowDataWhenNoToken as Data };

View File

@ -1,16 +0,0 @@
import { Addresses } from './Addresses';
import { Data } from './Data';
import { Node } from './Node';
import { AmountAndGasPrice } from './AmountAndGasPrice';
import { Nonce } from './Nonce';
import React from 'react';
export const Details: React.SFC<{}> = () => (
<ul className="ConfModal-details">
<Addresses />
<Nonce />
<AmountAndGasPrice />
<Node />
<Data />
</ul>
);

View File

@ -1,19 +0,0 @@
import React from 'react';
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { SerializedTransaction } from 'components/renderCbs';
import { Nonce as makeNonce } from 'libs/units';
export const Nonce: React.SFC<{}> = () => (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { nonce } = getTransactionFields(transactionInstance);
return (
<li className="ConfModal-details-detail">
You are sending with a nonce of <code>{makeNonce(nonce).toString()}</code>
</li>
);
}}
/>
);

View File

@ -1 +0,0 @@
export * from './Details';

View File

@ -1,21 +0,0 @@
import { AppState } from 'reducers';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getFrom } from 'selectors/transaction';
type From = AppState['transaction']['meta']['from'];
interface StateProps {
from: From;
}
interface OwnProps {
withFrom(from: string): React.ReactElement<any> | null;
}
class FromClass extends Component<StateProps & OwnProps, {}> {
public render() {
const { from, withFrom } = this.props;
return from ? withFrom(from) : null;
}
}
export const From = connect((state: AppState) => ({ from: getFrom(state) }))(FromClass);

View File

@ -1,10 +0,0 @@
import { SummaryAmount, SummaryFrom, SummaryTo } from './components';
import React from 'react';
export const Summary: React.SFC<{}> = () => (
<div className="ConfModal-summary">
<SummaryFrom />
<SummaryAmount />
<SummaryTo />
</div>
);

View File

@ -1,11 +0,0 @@
import React from 'react';
import { Amount } from '../../Amount';
export const SummaryAmount: React.SFC<{}> = () => (
<div className="ConfModal-summary-amount">
<div className="ConfModal-summary-amount-arrow" />
<div className="ConfModal-summary-amount-currency">
<Amount />
</div>
</div>
);

View File

@ -1,9 +0,0 @@
import React from 'react';
import { From } from '../../From';
import { Identicon } from 'components/ui';
export const SummaryFrom: React.SFC<{}> = () => (
<div className="ConfModal-summary-icon ConfModal-summary-icon--from">
<From withFrom={from => <Identicon size="100%" address={from} />} />
</div>
);

View File

@ -1,37 +0,0 @@
import { Identicon } from 'components/ui';
import { SerializedTransaction } from 'components/renderCbs';
import { makeTransaction, getTransactionFields } from 'libs/transaction';
import ERC20 from 'libs/erc20';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getUnit } from 'selectors/transaction';
interface StateProps {
unit: string;
}
//got duplication here
class SummaryToClass extends Component<StateProps> {
public render() {
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => {
const transactionInstance = makeTransaction(serializedTransaction);
const { to, data } = getTransactionFields(transactionInstance);
return (
<div className="ConfModal-summary-icon ConfModal-summary-icon--to">
<Identicon
size="100%"
address={this.props.unit === 'ether' ? to : ERC20.transfer.decodeInput(data)._to}
/>
</div>
);
}}
/>
);
}
}
export const SummaryTo = connect((state: AppState) => ({ unit: getUnit(state) }))(SummaryToClass);

View File

@ -1,3 +0,0 @@
export * from './SummaryTo';
export * from './SummaryFrom';
export * from './SummaryAmount';

View File

@ -1 +0,0 @@
export * from './Summary';

View File

@ -1,2 +1 @@
export * from './Details';
export * from './Summary';
export * from './Body';

View File

@ -0,0 +1 @@
export * from './ConfirmationModal';

View File

@ -1,14 +0,0 @@
import {
ConfirmationModalTemplate,
OwnProps as ConfirmationModalTemplateProps
} from '../ConfirmationModalTemplate';
import { Details, Summary } from './components';
import React, { SFC } from 'react';
interface OwnProps {
onClose: ConfirmationModalTemplateProps['onClose'];
}
export const ConfirmationModal: SFC<OwnProps> = ({ onClose }) => (
<ConfirmationModalTemplate summary={<Summary />} details={<Details />} onClose={onClose} />
);

View File

@ -1,6 +1,6 @@
import React from 'react';
import Modal, { IButton } from 'components/ui/Modal';
import Spinner from 'components/ui/Spinner';
import React from 'react';
import { connect } from 'react-redux';
import { getWalletType, IWalletType } from 'selectors/wallet';
import { getLanguageSelection } from 'selectors/config';
@ -10,12 +10,8 @@ import {
broadcastWeb3TransactionRequested,
TBroadcastWeb3TransactionRequested
} from 'actions/transaction';
import {
currentTransactionBroadcasting,
currentTransactionBroadcasted,
currentTransactionFailed
} from 'selectors/transaction';
import translate, { translateRaw } from 'translations';
import { currentTransactionBroadcasting } from 'selectors/transaction';
import { translateRaw } from 'translations';
import './ConfirmationModalTemplate.scss';
import { AppState } from 'reducers';
@ -28,8 +24,6 @@ interface StateProps {
lang: string;
walletTypes: IWalletType;
transactionBroadcasting: boolean;
transactionBroadcasted: boolean;
transactionFailed: boolean;
}
export interface ConfirmButtonCBProps {
@ -42,13 +36,13 @@ export interface ConfirmButtonCBProps {
}
export interface OwnProps {
summary: React.ReactElement<any> | null;
details: React.ReactElement<any> | null;
isOpen?: boolean;
Body: React.ReactElement<any>;
withConfirmButton?(props: ConfirmButtonCBProps): IButton;
onClose(): void;
}
interface State {
retryingFailedBroadcast: boolean;
timeToRead: number;
}
@ -58,19 +52,11 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
private readTimer = 0;
public constructor(props: Props) {
super(props);
const { transactionFailed } = props;
this.state = {
timeToRead: 5,
retryingFailedBroadcast: transactionFailed
timeToRead: 5
};
}
public componentDidUpdate() {
if (this.props.transactionBroadcasted && !this.state.retryingFailedBroadcast) {
this.props.onClose();
}
}
// Count down 5 seconds before allowing them to confirm
public componentDidMount() {
this.readTimer = window.setInterval(() => {
@ -83,7 +69,7 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
}
public render() {
const { onClose, transactionBroadcasting } = this.props;
const { onClose, transactionBroadcasting, isOpen } = this.props;
const { timeToRead } = this.state;
const buttonPrefix = timeToRead > 0 ? `(${timeToRead}) ` : '';
const defaultConfirmButton = {
@ -114,29 +100,21 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
];
return (
<div className="ConfModalWrap">
<Modal
title="Confirm Your Transaction"
buttons={buttons}
handleClose={onClose}
disableButtons={transactionBroadcasting}
isOpen={true}
>
<div className="ConfModal">
{transactionBroadcasting ? (
<div className="ConfModal-loading">
<Spinner size="x5" />
</div>
) : (
<div>
{this.props.summary}
{this.props.details}
<div className="ConfModal-confirm">{translate('SENDModal_Content_3')}</div>
</div>
)}
</div>
</Modal>
</div>
<Modal
title="Confirm Transaction"
buttons={buttons}
handleClose={onClose}
disableButtons={transactionBroadcasting}
isOpen={isOpen}
>
{transactionBroadcasting ? (
<React.Fragment>
<Spinner size="x5" />
</React.Fragment>
) : (
<React.Fragment>{this.props.Body}</React.Fragment>
)}
</Modal>
);
}
@ -149,7 +127,6 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
this.props.walletTypes.isWeb3Wallet
? this.props.broadcastWeb3TransactionRequested()
: this.props.broadcastLocalTransactionRequested();
this.setState({ retryingFailedBroadcast: false });
}
};
}
@ -157,8 +134,6 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
export const ConfirmationModalTemplate = connect(
(state: AppState) => ({
transactionBroadcasting: currentTransactionBroadcasting(state),
transactionBroadcasted: currentTransactionBroadcasted(state),
transactionFailed: currentTransactionFailed(state),
lang: getLanguageSelection(state),
walletTypes: getWalletType(state)
}),

View File

@ -18,7 +18,7 @@ export const GaslimitLoading: React.SFC<{
}> = ({ gasEstimationPending, onlyIncludeLoader }) => (
<CSSTransition in={gasEstimationPending} timeout={300} classNames="fade">
<div className={`Calculating-limit small ${gasEstimationPending ? 'active' : ''}`}>
{!!onlyIncludeLoader ? 'Calculating gas limit' : 'Calculating'}
{onlyIncludeLoader ? 'Calculating gas limit' : 'Calculating'}
<Spinner />
</div>
</CSSTransition>
@ -45,7 +45,7 @@ export const GasLimitField: React.SFC<Props> = ({
<div className="flex-spacer" />
<GaslimitLoading
gasEstimationPending={gasEstimationPending}
onlyIncludeLoader={false}
onlyIncludeLoader={onlyIncludeLoader}
/>
</div>
{onlyIncludeLoader ? null : (

View File

@ -64,7 +64,7 @@ const styles: any = {
margin: '0 0 8px',
textAlign: 'left',
fontSize: '14px',
fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace',
fontFamily: '"Roboto Mono", Menlo, Monaco, Consolas, "Courier New", monospace',
fontWeight: 300
},
infoLabel: {

View File

@ -1,17 +1,18 @@
import React, { Component } from 'react';
import { ConfirmationModal } from 'components/ConfirmationModal';
import { getOffline } from 'selectors/config';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { CallbackProps } from '../SendButtonFactory';
import { getCurrentTransactionStatus } from 'selectors/transaction';
import { getCurrentTransactionStatus, currentTransactionBroadcasted } from 'selectors/transaction';
import { showNotification, TShowNotification } from 'actions/notifications';
import { ITransactionStatus } from 'reducers/transaction/broadcast';
import { reset, TReset } from 'actions/transaction';
import { ConfirmationModal } from 'components/ConfirmationModal';
interface StateProps {
offline: boolean;
currentTransaction: false | ITransactionStatus | null;
transactionBroadcasted: boolean;
}
interface State {
@ -38,17 +39,19 @@ class OnlineSendClass extends Component<Props, State> {
public state: State = INITIAL_STATE;
public render() {
const displayModal = this.state.showModal ? (
<this.props.Modal onClose={this.toggleModal} />
) : null;
return !this.props.offline ? (
<React.Fragment>
{this.props.withProps({ onClick: this.openModal })}
{displayModal}
<this.props.Modal isOpen={this.state.showModal} onClose={this.closeModal} />
</React.Fragment>
) : null;
}
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.transactionBroadcasted && this.state.showModal) {
this.closeModal();
}
}
private openModal = () => {
const { currentTransaction } = this.props;
@ -61,16 +64,17 @@ class OnlineSendClass extends Component<Props, State> {
'The current transaction is already broadcasting or has been successfully broadcasted'
);
}
this.toggleModal();
this.setState({ showModal: true });
};
private toggleModal = () =>
this.setState((prevState: State) => ({ showModal: !prevState.showModal }));
private closeModal = () => this.setState({ showModal: false });
}
export const OnlineSend = connect(
(state: AppState) => ({
offline: getOffline(state),
currentTransaction: getCurrentTransactionStatus(state)
currentTransaction: getCurrentTransactionStatus(state),
transactionBroadcasted: currentTransactionBroadcasted(state)
}),
{ showNotification, reset }
)(OnlineSendClass);

View File

@ -17,6 +17,7 @@ export interface CallbackProps {
interface StateProps {
walletType: IWalletType;
}
interface OwnProps {
onlyTransactionParameters?: boolean;
Modal: typeof ConfirmationModal;

View File

@ -2,22 +2,35 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { getSerializedTransaction } from 'selectors/transaction';
import { makeTransaction, IHexStrTransaction } from 'libs/transaction';
import { getTransactionFields } from 'libs/transaction/utils/ether';
interface StateProps {
serializedTransaction: Buffer | null;
}
interface Props {
withSerializedTransaction(serializedTransaction: string): React.ReactElement<any> | null;
withSerializedTransaction(
serializedTransaction: string,
transactionFields: IHexStrTransaction
): React.ReactElement<any> | null;
}
class SerializedTransactionClass extends Component<StateProps & Props, {}> {
public render() {
const { serializedTransaction, withSerializedTransaction } = this.props;
return serializedTransaction
? withSerializedTransaction(serializedTransaction.toString('hex'))
? withSerializedTransaction(
serializedTransaction.toString('hex'),
getRawTxFields(serializedTransaction.toString('hex'))
)
: null;
}
}
const getRawTxFields = (serializedTransaction: string) =>
getTransactionFields(makeTransaction(serializedTransaction));
export const SerializedTransaction = connect((state: AppState) => ({
serializedTransaction: getSerializedTransaction(state)
}))(SerializedTransactionClass);

View File

@ -2,13 +2,13 @@ pre {
color: #333;
background-color: #fafafa;
border: 1px solid #ececec;
border-radius: 0px;
padding: 8px;
padding: 0.5rem 1rem;
margin: 0;
font-size: 1rem;
border-radius: 5px;
code {
font-size: 14px;
line-height: 20px;
word-break: break-all;
word-wrap: break-word;
white-space: pre;
}
}

View File

@ -4,39 +4,45 @@ import React from 'react';
interface Props {
address: string;
className?: string;
size?: string;
}
export default function Identicon(props: Props) {
const size = props.size || '4rem';
const { address, className } = props;
// FIXME breaks on failed checksums
const identiconDataUrl = isValidETHAddress(props.address) ? toDataUrl(props.address) : '';
const identiconDataUrl = isValidETHAddress(address) ? toDataUrl(address) : '';
return (
<div style={{ position: 'relative', width: size, height: size }} title="Address Identicon">
<div
style={{
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
borderRadius: '50%',
boxShadow: `
inset rgba(255, 255, 255, 0.5) 0 2px 2px,
inset rgba(0, 0, 0, 0.6) 0 -1px 8px
`
}}
/>
// Use inline styles for printable wallets
<div
className={`Identicon ${className}`}
title="Address Identicon"
style={{ width: size, height: size, position: 'relative' }}
>
{identiconDataUrl && (
<img
src={identiconDataUrl}
style={{
borderRadius: '50%',
width: '100%',
height: '100%'
}}
/>
<React.Fragment>
<img
src={identiconDataUrl}
style={{
height: '100%',
width: '100%',
padding: '0px',
borderRadius: '50%'
}}
/>
<div
className="border"
style={{
position: 'absolute',
height: 'inherit',
width: 'inherit',
top: 0,
boxShadow: '0 3px 8px 0 rgba(0, 0, 0, 0.1), inset 0 0 3px 0 rgba(0, 0, 0, 0.1)',
borderRadius: '50%'
}}
/>
</React.Fragment>
)}
</div>
);

View File

@ -4,102 +4,80 @@
$m-background: #fff;
$m-window-padding-w: 20px;
$m-window-padding-h: 30px;
$m-header-padding: 15px;
$m-header-height: 62px;
$m-content-padding: 20px;
$m-header-padding: 1rem 2rem 0.5rem 2rem;
$m-content-padding: 1.5rem 2rem;
$m-footer-padding: 0.5rem 2rem 1rem 2rem;
$m-close-size: 26px;
$m-anim-speed: 400ms;
@keyframes modalshade-open {
0% {
opacity: 0;
}
70%,
100% {
opacity: 1;
}
}
@keyframes modal-open {
0%,
30% {
opacity: 0;
transform: translateX(-50%) scale(0.88);
}
100% {
opacity: 1;
transform: translateX(-50%) scale(1);
}
}
.Modalshade {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(#000, 0.82);
background-color: rgba(#000, 0.54);
z-index: $zindex-modal-background;
display: none;
animation: modalshade-open $m-anim-speed ease 1;
&.is-open {
display: block;
}
display: block;
}
.Modal {
position: fixed;
top: $m-window-padding-h;
top: 50%;
left: 50%;
width: initial;
max-width: 95%;
max-width: calc(100% - #{$m-window-padding-w * 2});
max-height: 95%;
max-height: calc(100% - #{$m-window-padding-h * 2});
background: $m-background;
border-radius: 4px;
transform: translateX(-50%);
border-radius: 2px;
transform: translate(-50%, -50%);
z-index: $zindex-modal;
overflow: hidden;
display: none;
display: flex;
flex-direction: column;
animation: modal-open $m-anim-speed ease 1;
text-align: left;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.2), 0px 16px 24px 2px rgba(0, 0, 0, 0.14),
0px 6px 30px 5px rgba(0, 0, 0, 0.12);
&.is-open {
display: flex;
&-fade {
background: linear-gradient(to bottom, #fff0, #fff);
position: fixed;
height: 25px;
width: calc(100% - 3rem);
bottom: 4.5rem;
left: 50%;
transform: translateX(-50%);
}
&-header {
position: relative;
padding: 0 $m-header-padding;
height: $m-header-height;
border-bottom: 1px solid $gray-lighter;
background: $m-background;
display: flex;
flex-wrap: nowrap;
padding: $m-header-padding;
align-items: center;
&-title {
font-size: 1.625rem;
font-weight: 400;
margin: 0;
padding-right: $m-close-size;
font-size: $font-size-large;
line-height: $m-header-height;
height: $m-header-height;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: 0;
}
&-close {
@include reset-button;
position: absolute;
top: 50%;
right: $m-header-padding;
height: $m-close-size;
width: $m-close-size;
opacity: 0.8;
transform: translateY(-50%) translateZ(0);
opacity: 0.3;
transition: opacity 120ms;
&:hover {
opacity: 1;
opacity: 0.87;
}
&-icon {
@ -114,11 +92,13 @@ $m-anim-speed: 400ms;
flex-direction: column;
padding: $m-content-padding;
overflow: auto;
> .Spinner {
margin: 2.5rem auto;
}
}
&-footer {
padding: 7px 10px;
border-top: 1px solid $gray-lighter;
padding: $m-footer-padding;
background: $m-background;
// Selector needs a little extra oomph to override bootstrap
@ -128,8 +108,29 @@ $m-anim-speed: 400ms;
min-width: 100px;
}
}
}
@media(max-width: 820px) {
width: calc(100% - 40px);
.animate-modal {
&-enter,
&-exit {
position: relative;
z-index: 3;
transition: opacity 300ms;
}
&-enter {
opacity: 0;
&-active {
opacity: 1;
}
}
&-exit {
opacity: 1;
&-active {
opacity: 0;
}
}
}

View File

@ -1,5 +1,6 @@
import closeIcon from 'assets/images/icon-x.svg';
import closeIcon from 'assets/images/close.svg';
import React, { PureComponent } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './Modal.scss';
export interface IButton {
@ -17,6 +18,12 @@ interface Props {
handleClose?(): void;
}
const Fade = ({ children, ...props }) => (
<CSSTransition {...props} timeout={300} classNames="animate-modal">
{children}
</CSSTransition>
);
export default class Modal extends PureComponent<Props, {}> {
private modalContent: HTMLElement | null = null;
@ -43,24 +50,32 @@ export default class Modal extends PureComponent<Props, {}> {
const hasButtons = buttons && buttons.length;
return (
<div>
<div className={`Modalshade ${isOpen ? 'is-open' : ''}`} />
<div className={`Modal ${isOpen ? 'is-open' : ''}`}>
{title && (
<div className="Modal-header">
<h2 className="Modal-header-title">{title}</h2>
<button className="Modal-header-close" onClick={handleClose}>
<img className="Modal-header-close-icon" src={closeIcon} />
</button>
</div>
)}
<TransitionGroup>
{isOpen && (
<Fade>
<div>
<div className={`Modalshade`} />
<div className={`Modal`}>
{title && (
<div className="Modal-header flex-wrapper">
<h2 className="Modal-header-title">{title}</h2>
<div className="flex-spacer" />
<button className="Modal-header-close" onClick={handleClose}>
<img className="Modal-header-close-icon" src={closeIcon} />
</button>
</div>
)}
<div className="Modal-content" ref={el => (this.modalContent = el)}>
{isOpen && children}
</div>
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
</div>
</div>
<div className="Modal-content" ref={el => (this.modalContent = el)}>
{isOpen && children}
<div className="Modal-fade" />
</div>
{hasButtons && <div className="Modal-footer">{this.renderButtons()}</div>}
</div>
</div>
</Fade>
)}
</TransitionGroup>
);
}

View File

@ -70,7 +70,7 @@ const UnitDisplay: React.SFC<EthProps | TokenProps> = params => {
element = (
<span>
{formattedValue}
{symbol ? ` ${symbol}` : ''}
<span>{symbol ? ` ${symbol}` : ''}</span>
</span>
);
}

View File

@ -1,5 +1,5 @@
@import "common/sass/variables";
@import "common/sass/mixins";
@import 'common/sass/variables';
@import 'common/sass/mixins';
.UnlockHeader {
position: relative;
@ -21,13 +21,20 @@
position: absolute;
top: 56px;
right: 6px;
padding: 8px;
margin: 0.5rem 1rem;
z-index: 1;
line-height: 24px;
font-size: 24px;
transition: opacity 120ms;
opacity: 0.3;
> img {
height: 26px;
width: 26px;
}
&:hover {
opacity: 0.7;
opacity: 0.87;
}
}
}

View File

@ -4,6 +4,7 @@ import { AppState } from 'reducers';
import translate, { TranslateType } from 'translations';
import WalletDecrypt, { DisabledWallets } from 'components/WalletDecrypt';
import { IWallet } from 'libs/wallet/IWallet';
import closeIcon from 'assets/images/close.svg';
import './UnlockHeader.scss';
interface Props {
@ -52,7 +53,7 @@ export class UnlockHeader extends React.PureComponent<Props, State> {
{wallet &&
isExpanded && (
<button className="UnlockHeader-close" onClick={this.toggleisExpanded}>
<i className="fa fa-times" />
<img src={closeIcon} alt="close" />
</button>
)}
<WalletDecrypt

View File

@ -30,8 +30,7 @@ const getTransactionFields = (t: Tx): IHexStrTransaction => {
};
};
const getTransactionFee = (t: Tx) => {
const { gasPrice, gasLimit } = getTransactionFields(t);
const getTransactionFee = (gasPrice: string, gasLimit: string) => {
return Wei(gasPrice).mul(Wei(gasLimit));
};

View File

@ -1 +1,89 @@
@import './fonts/social-media';
@font-face {
font-family: 'Lato';
src: url('../assets/fonts/Lato-Light.woff2') format('woff2'),
url('../assets/fonts/Lato-Light.woff') format('woff');
font-style: normal;
font-weight: 300;
}
@font-face {
font-family: 'Lato';
src: url('../assets/fonts/Lato-Regular.woff2') format('woff2'),
url('../assets/fonts/Lato-Regular.woff') format('woff');
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: 'Lato';
src: url('../assets/fonts/Lato-Bold.woff2') format('woff2'),
url('../assets/fonts/Lato-Bold.woff') format('woff');
font-style: normal;
font-weight: 700;
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 300;
src: url('../assets/fonts/Roboto-Mono-Light.woff2') format('woff2'),
url('../assets/fonts/Roboto-Mono-Light.woff') format('woff');
}
@font-face {
font-family: 'Roboto Mono';
font-style: normal;
font-weight: 400;
src: url('../assets/fonts/Roboto-Mono-Regular.woff2') format('woff2'),
url('../assets/fonts/Roboto-Mono-Regular.woff') format('woff');
}
@font-face {
font-family: 'social-media';
src: url('../assets/fonts/social-media.woff2') format('woff2'),
url('../assets/fonts/social-media.woff') format('woff');
font-weight: normal;
font-style: normal;
}
.sm-icon {
display: inline-block;
font: normal normal normal 32px/1 'social-media';
text-transform: none;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.sm-16px {
font-size: 16px;
}
&.sm-24px {
font-size: 24px;
}
&.sm-32px {
font-size: 32px;
}
&.sm-48px {
font-size: 48px;
}
// Refer to docs for updating icon-fonts
&.sm-logo-facebook:before {
content: '\ea02';
}
&.sm-logo-reddit:before {
content: '\ea03';
}
&.sm-logo-github:before {
content: '\ea04';
}
&.sm-logo-twitter:before {
content: '\ea05';
}
&.sm-logo-linkedin:before {
content: '\ea06';
}
&.sm-logo-slack:before {
content: '\ea07';
}
&.sm-logo-medium:before {
content: '\ea08';
}
}

View File

@ -1,50 +0,0 @@
@font-face {
font-family: 'social-media';
src: url('../assets/fonts/social-media.woff2') format('woff2'),
url('../assets/fonts/social-media.woff') format('woff');
font-weight: normal;
font-style: normal;
}
.sm-icon {
display: inline-block;
font: normal normal normal 32px/1 'social-media';
text-transform: none;
/* Better Font Rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.sm-16px {
font-size: 16px;
}
&.sm-24px {
font-size: 24px;
}
&.sm-32px {
font-size: 32px;
}
&.sm-48px {
font-size: 48px;
}
// Refer to docs for updating icon-fonts
&.sm-logo-facebook:before {
content: '\ea02';
}
&.sm-logo-reddit:before {
content: '\ea03';
}
&.sm-logo-github:before {
content: '\ea04';
}
&.sm-logo-twitter:before {
content: '\ea05';
}
&.sm-logo-linkedin:before {
content: '\ea06';
}
&.sm-logo-slack:before {
content: '\ea07';
}
&.sm-logo-medium:before {
content: '\ea08';
}
}

View File

@ -8,6 +8,7 @@
@import './overrides/grid';
@import './overrides/input-groups';
@import './overrides/type';
@import './overrides/tables';
// Other overrides
@import './overrides/react-select';

View File

@ -1,6 +1,6 @@
// Button overrides
@import "common/sass/variables";
@import "common/sass/mixins";
@import 'common/sass/variables';
@import 'common/sass/mixins';
.btn {
@include button-size(
@ -60,12 +60,12 @@
$line-height-small,
$btn-border-radius-small
);
padding: .1rem .6rem .2rem;
padding: 0.1rem 0.6rem 0.2rem;
}
// This is a "smaller" small, to accomodate overrides done in v3.
.btn-smr {
@include button-size(
.4rem,
0.4rem,
1rem,
14px,
$line-height-base,
@ -75,7 +75,7 @@
// Custom color
.btn-white {
@include button-variant($brand-info, rgba(255,255,255,.8), rgba(255,255,255,.8));
@include button-variant($brand-info, rgba(255,255,255,0.8), rgba(255,255,255,0.8));
color: $brand-info;
}
@ -84,7 +84,7 @@
position: relative;
overflow: hidden;
input[type=file] {
input[type='file'] {
position: absolute;
top: 0;
right: 0;
@ -114,12 +114,13 @@
background-color: white;
text-decoration: none;
color: $brand-info;
opacity: .6;
opacity: 0.6;
}
}
.btn-group .btn-default {
border-bottom-width: 1px;
border-color: transparent;
&.active {
border: 1px solid $brand-primary;
color: $brand-primary;

View File

@ -0,0 +1,3 @@
tr {
transition: background-color 120ms;
}

View File

@ -1,4 +1,5 @@
@import "common/sass/mixins";
@import 'common/sass/mixins';
@import 'common/sass/variables';
.Tab {
&-content {
@ -9,8 +10,7 @@
min-height: 1.5rem;
padding: 1.5rem 2rem;
margin: 0 auto 1rem;
box-shadow: 0 1px rgba(0, 0, 0, 0.1),
0 1px 4px rgba(0, 0, 0, 0.12);
box-shadow: $tab-box-shadow;
border-radius: 2px;
&.is-full-width {

View File

@ -1,14 +1,14 @@
@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables";
@import "./variables/colors";
@import "./variables/spacing";
@import "./variables/grid";
@import "./variables/typography";
@import "./variables/transitions";
@import "./variables/links";
@import "./variables/tables";
@import "./variables/buttons";
@import "./variables/forms";
@import "./variables/zindex";
@import '~bootstrap-sass/assets/stylesheets/bootstrap/variables';
@import './variables/colors';
@import './variables/spacing';
@import './variables/grid';
@import './variables/typography';
@import './variables/transitions';
@import './variables/links';
@import './variables/tables';
@import './variables/buttons';
@import './variables/forms';
@import './variables/zindex';
// Misc.
$border-radius-small: 0px;
@ -133,23 +133,20 @@ $state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%);
$tooltip-max-width: 14rem;
$tooltip-color: #fff;
$tooltip-bg: #000;
$tooltip-opacity: .9;
$tooltip-opacity: 0.9;
$tooltip-arrow-width: $space-sm;
$tooltip-arrow-color: $tooltip-bg;
$popover-bg: #fff;
$popover-max-width: 18.4rem;
$popover-border-color: rgba(0, 0, 0, .2);
$popover-border-color: rgba(0, 0, 0, 0.2);
$popover-fallback-border-color: #ccc;
$popover-title-bg: darken($popover-bg, 3%);
$popover-arrow-width: $font-size-base;
$popover-arrow-color: $popover-bg;
$popover-arrow-outer-width: ($popover-arrow-width + 1);
$popover-arrow-outer-color: fadein($popover-border-color, 5%);
$popover-arrow-outer-fallback-color: darken(
$popover-fallback-border-color,
20%
);
$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%);
$label-default-bg: $gray-light;
$label-primary-bg: $brand-primary;
@ -165,10 +162,10 @@ $modal-inner-padding: $space*1.5;
$modal-title-padding: $space;
$modal-title-line-height: $line-height-base;
$modal-content-bg: #fff;
$modal-content-border-color: rgba(0, 0, 0, .2);
$modal-content-border-color: rgba(0, 0, 0, 0.2);
$modal-content-fallback-border-color: #999;
$modal-backdrop-bg: #000;
$modal-backdrop-opacity: .5;
$modal-backdrop-opacity: 0.5;
$modal-header-border-color: #e5e5e5;
$modal-footer-border-color: $modal-header-border-color;
$modal-lg: 70rem;
@ -270,12 +267,12 @@ $breadcrumb-padding-horizontal: $font-size-base;
$breadcrumb-bg: #f5f5f5;
$breadcrumb-color: #ccc;
$breadcrumb-active-color: $gray-light;
$breadcrumb-separator: "/";
$breadcrumb-separator: '/';
$carousel-text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
$carousel-text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
$carousel-control-color: #fff;
$carousel-control-width: 15%;
$carousel-control-opacity: .5;
$carousel-control-opacity: 0.5;
$carousel-control-font-size: $font-size-bump;
$carousel-indicator-active-bg: #fff;
$carousel-indicator-border-color: #fff;
@ -285,6 +282,8 @@ $close-font-weight: bold;
$close-color: #000;
$close-text-shadow: 0 1px 0 #fff;
$tab-box-shadow: 0 1px rgba(0, 0, 0, 0.1), 0 1px 4px rgba(0, 0, 0, 0.12);
$code-color: #c7254e;
$code-bg: #f9f2f4;
$kbd-color: #fff;

View File

@ -1,8 +1,8 @@
$table-cell-padding: $space-sm;
$table-condensed-cell-padding: $space-xs;
$table-bg: transparent;
$table-bg-accent: #f5f5f5;
$table-bg-hover: $gray-lightest;
$table-bg-accent: rgba(0, 0, 0, 0.03);
$table-bg-hover: rgba(0, 0, 0, 0.06);
$table-bg-active: $table-bg-hover;
$table-border-color: transparent;
$table-cell-padding: 0.75rem 1rem;

View File

@ -1,6 +1,6 @@
$font-family-sans-serif: 'Lato', sans-serif;
$font-family-serif: Georgia, "Times New Roman", Times, serif;
$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace;
$font-family-serif: Georgia, 'Times New Roman', Times, serif;
$font-family-monospace: 'Roboto Mono', Menlo, Monaco, Consolas, 'Courier New', monospace;
$font-family-base: $font-family-sans-serif;
$base: 15;
@ -9,13 +9,13 @@ $font-size-pixels-xl: $base + 1px; // for xl screens
$font-size-pixels-sm: $base + px; // for small screens
$font-size-large-bump: 2.25rem; // 33.75
$font-size-large: 1.90rem; // 28.5
$font-size-medium-bump: 1.50rem; // 22.5
$font-size-medium: 1.30rem; // 19.5
$font-size-large: 1.9rem; // 28.5
$font-size-medium-bump: 1.5rem; // 22.5
$font-size-medium: 1.25rem; // 20
$font-size-bump-more: 1.15rem; // 17.25
$font-size-bump: 1.07rem; // 16.05
$font-size-base: 1.00rem; // 15
$font-size-small: 0.90rem; // 13.8
$font-size-base: 1rem; // 15
$font-size-small: 0.9rem; // 13.8
$font-size-xs: 0.75rem; // 12
$font-size-h1: $font-size-large-bump;

71
common/selectors/rates.ts Normal file
View File

@ -0,0 +1,71 @@
import { AppState } from 'reducers';
import { getNetworkConfig } from 'selectors/config';
import { getUnit, isEtherTransaction, getParamsFromSerializedTx } from 'selectors/transaction';
import BN from 'bn.js';
import { Wei, TokenValue } from 'libs/units';
export const getRates = (state: AppState) => state.rates;
const getUSDConversionRate = (state: AppState, unit: string) => {
const { isTestnet } = getNetworkConfig(state);
const { rates } = getRates(state);
const isEther = isEtherTransaction(state);
const conversionUnit = isEther ? 'ETH' : unit;
if (isTestnet) {
return null;
}
const conversionRate = rates[conversionUnit];
if (!conversionRate) {
return null;
}
return conversionRate.USD;
};
export const getValueInUSD = (state: AppState, value: TokenValue | Wei) => {
const unit = getUnit(state);
const conversionRate = getUSDConversionRate(state, unit);
if (!conversionRate) {
return null;
}
const sendValueUSD = value.muln(conversionRate);
return sendValueUSD;
};
export const getTransactionFeeInUSD = (state: AppState, fee: Wei) => {
const { unit } = getNetworkConfig(state);
const conversionRate = getUSDConversionRate(state, unit);
if (!conversionRate) {
return null;
}
const feeValueUSD = fee.muln(conversionRate);
return feeValueUSD;
};
export interface AllUSDValues {
valueUSD: BN | null;
feeUSD: BN | null;
totalUSD: BN | null;
}
export const getAllUSDValuesFromSerializedTx = (state: AppState): AllUSDValues => {
const fields = getParamsFromSerializedTx(state);
if (!fields) {
return {
feeUSD: null,
valueUSD: null,
totalUSD: null
};
}
const { currentValue, fee } = fields;
const valueUSD = getValueInUSD(state, currentValue);
const feeUSD = getTransactionFeeInUSD(state, fee);
return {
feeUSD,
valueUSD,
totalUSD: feeUSD && valueUSD ? valueUSD.add(feeUSD) : null
};
};

View File

@ -10,7 +10,6 @@ const getTokenTo = (state: AppState) => getMetaState(state).tokenTo;
const getTokenValue = (state: AppState) => getMetaState(state).tokenValue;
const getUnit = (state: AppState) => getMetaState(state).unit;
const getPreviousUnit = (state: AppState) => getMetaState(state).previousUnit;
const getDecimalFromUnit = (state: AppState, unit: string) => {
if (isEtherUnit(unit)) {
return getDecimalFromEtherUnit('ether');

View File

@ -1,6 +1,10 @@
import { getWalletType } from 'selectors/wallet';
import { AppState } from 'reducers';
import { getTransactionState } from './transaction';
import { getTransactionFields, makeTransaction, IHexStrTransaction } from 'libs/transaction';
import { isEtherTransaction, getUnit, getDecimal } from 'selectors/transaction';
import { Wei, TokenValue, Address } from 'libs/units';
import erc20 from 'libs/erc20';
const getSignState = (state: AppState) => getTransactionState(state).sign;
@ -17,4 +21,32 @@ const getWeb3Tx = (state: AppState) => getSignState(state).web3.transaction;
const getSerializedTransaction = (state: AppState) =>
getWalletType(state).isWeb3Wallet ? getWeb3Tx(state) : getSignedTx(state);
export interface SerializedTxParams extends IHexStrTransaction {
unit: string;
currentTo: Buffer;
currentValue: Wei | TokenValue;
fee: Wei;
total: Wei;
isToken: boolean;
decimal: number;
}
export const getParamsFromSerializedTx = (state: AppState): SerializedTxParams => {
const tx = getSerializedTransaction(state);
const isEther = isEtherTransaction(state);
const decimal = getDecimal(state);
if (!tx) {
throw Error('Serialized transaction not found');
}
const fields = getTransactionFields(makeTransaction(tx));
const { value, data, gasLimit, gasPrice, to } = fields;
const currentValue = isEther ? Wei(value) : TokenValue(erc20.transfer.decodeInput(data)._value);
const currentTo = isEther ? Address(to) : Address(erc20.transfer.decodeInput(data)._to);
const unit = getUnit(state);
const fee = Wei(gasLimit).mul(Wei(gasPrice));
const total = fee.add(Wei(value));
return { ...fields, currentValue, currentTo, fee, total, unit, decimal, isToken: !isEther };
};
export { signaturePending, getSignedTx, getWeb3Tx, getSignState, getSerializedTransaction };

View File

@ -207,8 +207,8 @@
"SENDModal_Content_2": "to address ",
"SENDModal_Content_3": "Are you sure you want to do this? ",
"SENDModal_Content_4": "NOTE: If you encounter an error, you most likely need to add ether to your account to cover the gas cost of sending tokens. Gas is paid in ether. ",
"SENDModal_No": "No, get me out of here! ",
"SENDModal_Yes": "Yes, I am sure! Make transaction. ",
"SENDModal_No": "Cancel",
"SENDModal_Yes": "Send",
"TOKEN_Addr": "Address ",
"TOKEN_Symbol": "Token Symbol ",
"TOKEN_Dec": "Decimals ",