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:
parent
e7633c6d2f
commit
0da409cd12
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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 |
|
@ -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 |
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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} />
|
||||
);
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -0,0 +1 @@
|
|||
export * from './Body';
|
|
@ -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
|
|
@ -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>
|
||||
);
|
|
@ -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} />
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
|
@ -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);
|
|
@ -1,2 +0,0 @@
|
|||
export * from './GasPrice';
|
||||
export * from './TransactionFee';
|
|
@ -1 +0,0 @@
|
|||
export * from './AmountAndGasPrice';
|
|
@ -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 };
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
|
@ -1 +0,0 @@
|
|||
export * from './Details';
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -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);
|
|
@ -1,3 +0,0 @@
|
|||
export * from './SummaryTo';
|
||||
export * from './SummaryFrom';
|
||||
export * from './SummaryAmount';
|
|
@ -1 +0,0 @@
|
|||
export * from './Summary';
|
|
@ -1,2 +1 @@
|
|||
export * from './Details';
|
||||
export * from './Summary';
|
||||
export * from './Body';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './ConfirmationModal';
|
|
@ -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} />
|
||||
);
|
|
@ -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)
|
||||
}),
|
||||
|
|
|
@ -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 : (
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -17,6 +17,7 @@ export interface CallbackProps {
|
|||
interface StateProps {
|
||||
walletType: IWalletType;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
onlyTransactionParameters?: boolean;
|
||||
Modal: typeof ConfirmationModal;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ const UnitDisplay: React.SFC<EthProps | TokenProps> = params => {
|
|||
element = (
|
||||
<span>
|
||||
{formattedValue}
|
||||
{symbol ? ` ${symbol}` : ''}
|
||||
<span>{symbol ? ` ${symbol}` : ''}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
@import './overrides/grid';
|
||||
@import './overrides/input-groups';
|
||||
@import './overrides/type';
|
||||
@import './overrides/tables';
|
||||
|
||||
// Other overrides
|
||||
@import './overrides/react-select';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
tr {
|
||||
transition: background-color 120ms;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 ",
|
||||
|
|
Loading…
Reference in New Issue