Improve Gas Price UX (Part 2) (#850)

* Remove gas dropdown & Add gas sliders

* Update styles

* Revert changes made to requestpayment.tsx

* Update style & add custom labels to GasLimitField

* Update styles

* Update confirm transaction modal

* Revert "Update confirm transaction modal"

This reverts commit 743c9a505fe070feb55f7af550ad918a3d8899d1.

* Add transaction fee to tx confirmation modal

* Update styles

* Remove old gasPriceDropdown files & use network units in tx fee

* Add option to lock gaslimit data

* fix tslint errors

* Rename lockData to readOnly

* Add option to check if validAmount before generating transaction

* Add nonce field if gas slider is readonly

* Automatically set nonce in  <Send/>

* Update snapshot

* Move getNonceRequested to GasSlider component

* Add optional to check value for isValidAmount selector

* Add selector for transaction fee

* Update GasSlider component & Rename to Gas

* update snapshots

* Fix subtabs className

* Update styles

* Remove dataField on contract interact

* rename <Gas/> to <TXMetaDataPanel/>
This commit is contained in:
James Prado 2018-01-24 22:43:27 -05:00 committed by Daniel Ternyak
parent 22c107fe4c
commit c631f45ab7
38 changed files with 459 additions and 320 deletions

View File

@ -1,4 +1,4 @@
import { GasPrice } from './components';
import { TransactionFee } from './components';
import { Amount } from '../../Amount';
import React from 'react';
@ -9,9 +9,9 @@ export const AmountAndGasPrice: React.SFC<{}> = () => (
<strong>
<Amount />
</strong>{' '}
with a gas price of{' '}
with a transaction fee of{' '}
<strong>
<GasPrice />
<TransactionFee />
</strong>
</p>
</li>

View File

@ -0,0 +1,57 @@
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 +1,2 @@
export * from './GasPrice';
export * from './TransactionFee';

View File

@ -8,27 +8,46 @@ import { gasLimitValidator } from 'libs/validators';
interface Props {
includeLabel: boolean;
onlyIncludeLoader: boolean;
customLabel?: string;
disabled?: boolean;
}
export const GaslimitLoading: React.SFC<{ gasEstimationPending: boolean }> = ({
gasEstimationPending
}) => (
export const GaslimitLoading: React.SFC<{
gasEstimationPending: boolean;
onlyIncludeLoader?: boolean;
}> = ({ gasEstimationPending, onlyIncludeLoader }) => (
<CSSTransition in={gasEstimationPending} timeout={300} classNames="fade">
<div className={`SimpleGas-estimating small ${gasEstimationPending ? 'active' : ''}`}>
Calculating gas limit
<div className={`Calculating-limit small ${gasEstimationPending ? 'active' : ''}`}>
{!!onlyIncludeLoader ? 'Calculating gas limit' : 'Calculating'}
<Spinner />
</div>
</CSSTransition>
);
export const GasLimitField: React.SFC<Props> = ({ includeLabel, onlyIncludeLoader }) => (
export const GasLimitField: React.SFC<Props> = ({
includeLabel,
onlyIncludeLoader,
customLabel,
disabled
}) => (
<React.Fragment>
{includeLabel ? <label>{translate('TRANS_gas')} </label> : null}
<GasLimitFieldFactory
withProps={({ gasLimit: { raw }, onChange, readOnly, gasEstimationPending }) => (
<>
<GaslimitLoading gasEstimationPending={gasEstimationPending} />
<React.Fragment>
<div className="flex-wrapper">
{includeLabel ? (
customLabel ? (
<label>{customLabel} </label>
) : (
<label>{translate('TRANS_gas')} </label>
)
) : null}
<div className="flex-spacer" />
<GaslimitLoading
gasEstimationPending={gasEstimationPending}
onlyIncludeLoader={false}
/>
</div>
{onlyIncludeLoader ? null : (
<input
className={`form-control ${gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}`}
@ -37,9 +56,10 @@ export const GasLimitField: React.SFC<Props> = ({ includeLabel, onlyIncludeLoade
readOnly={!!readOnly}
value={raw}
onChange={onChange}
disabled={disabled}
/>
)}
</>
</React.Fragment>
)}
/>
</React.Fragment>

View File

@ -1,10 +0,0 @@
@import 'common/sass/variables';
.GasSlider {
&-toggle {
display: inline-block;
position: relative;
margin-top: $space-sm;
left: -8px;
}
}

View File

@ -1,37 +0,0 @@
.AdvancedGas {
margin-top: 0;
margin-bottom: 0;
.checkbox {
display: flex;
align-items: center;
width: fit-content;
input[type='checkbox'] {
position: initial;
margin: 0;
margin-right: 8px;
}
span {
font-size: 1rem;
font-weight: 400;
}
}
&-gasLimit {
display: flex;
flex-wrap: wrap;
align-items: baseline;
.flex-spacer {
flex-grow: 2;
}
input {
width: 100%;
margin-top: 0;
}
}
&-nonce {
input {
margin-top: 0;
}
}
}

View File

@ -1,101 +0,0 @@
import React from 'react';
import classnames from 'classnames';
import translate from 'translations';
import FeeSummary from './FeeSummary';
import './AdvancedGas.scss';
import { TToggleAutoGasLimit, toggleAutoGasLimit } from 'actions/config';
import { AppState } from 'reducers';
import { TInputGasPrice } from 'actions/transaction';
import { NonceField, GasLimitField, DataField } from 'components';
import { connect } from 'react-redux';
import { getAutoGasLimitEnabled } from 'selectors/config';
import { isValidGasPrice } from 'selectors/transaction';
import { sanitizeNumericalInput } from 'libs/values';
interface OwnProps {
inputGasPrice: TInputGasPrice;
gasPrice: AppState['transaction']['fields']['gasPrice'];
}
interface StateProps {
autoGasLimitEnabled: AppState['config']['autoGasLimit'];
validGasPrice: boolean;
}
interface DispatchProps {
toggleAutoGasLimit: TToggleAutoGasLimit;
}
type Props = OwnProps & StateProps & DispatchProps;
class AdvancedGas extends React.Component<Props> {
public render() {
const { autoGasLimitEnabled, gasPrice, validGasPrice } = this.props;
return (
<div className="AdvancedGas row form-group">
<div className="col-md-12">
<label className="checkbox">
<input
type="checkbox"
defaultChecked={autoGasLimitEnabled}
onChange={this.handleToggleAutoGasLimit}
/>
<span>Automatically Calculate Gas Limit</span>
</label>
</div>
<div className="col-md-4 col-sm-6 col-xs-12">
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
<input
className={classnames('form-control', { 'is-invalid': !validGasPrice })}
type="number"
placeholder="e.g. 40"
value={gasPrice.raw}
onChange={this.handleGasPriceChange}
/>
</div>
<div className="col-md-4 col-sm-6 col-xs-12 AdvancedGas-gasLimit">
<label>{translate('OFFLINE_Step2_Label_4')}</label>
<div className="SimpleGas-flex-spacer" />
<GasLimitField includeLabel={false} onlyIncludeLoader={false} />
</div>
<div className="col-md-4 col-sm-12 col-xs-12 AdvancedGas-nonce">
<NonceField alwaysDisplay={true} />
</div>
<div className="col-md-12 col-xs-12">
<DataField />
</div>
<div className="col-sm-12 col-xs-12">
<FeeSummary
render={({ gasPriceWei, gasLimit, fee, usd }) => (
<span>
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
</span>
)}
/>
</div>
</div>
);
}
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.inputGasPrice(sanitizeNumericalInput(value));
};
private handleToggleAutoGasLimit = (_: React.FormEvent<HTMLInputElement>) => {
this.props.toggleAutoGasLimit();
};
}
export default connect(
(state: AppState) => ({
autoGasLimitEnabled: getAutoGasLimitEnabled(state),
validGasPrice: isValidGasPrice(state)
}),
{ toggleAutoGasLimit }
)(AdvancedGas);

View File

@ -1,2 +0,0 @@
import GasSlider from './GasSlider';
export default GasSlider;

View File

@ -1,38 +0,0 @@
@import "common/sass/variables";
.GasPrice {
&-dropdown-menu {
padding: 0.5rem !important;
min-width: 300px !important;
@media screen and (max-width: $screen-xs) {
left: 0;
right: auto;
}
}
&-header {
max-width: 26rem;
color: $text-color;
p {
font-weight: 400;
margin: $space-sm 0 0;
}
a, a:hover, a:focus, a:visited {
color: $brand-primary !important;
}
}
&-padding-reset {
padding-left: 0 !important;
padding-right: 0 !important;
}
&-description {
white-space: normal;
font-weight: 300 !important;
margin: 2rem 0 0;
}
}

View File

@ -20,7 +20,6 @@ import {
CustomNodeConfig,
CustomNetworkConfig
} from 'config';
import GasPriceDropdown from './components/GasPriceDropdown';
import Navigation from './components/Navigation';
import CustomNodeModal from './components/CustomNodeModal';
import OnlineStatus from './components/OnlineStatus';
@ -134,10 +133,6 @@ export default class Header extends Component<Props, State> {
<OnlineStatus isOffline={isOffline} />
</div>
<div className="Header-branding-right-dropdown">
<GasPriceDropdown onChange={this.props.setGasPriceField} />
</div>
<div className="Header-branding-right-dropdown">
<LanguageDropDown
ariaLabel={`change language. current language ${languages[selectedLanguage]}`}

View File

@ -17,7 +17,7 @@ export const NonceField: React.SFC<Props> = ({ alwaysDisplay }) => (
<NonceFieldFactory
withProps={({ nonce: { raw, value }, onChange, readOnly, shouldDisplay }) => {
const content = (
<>
<div>
<label>Nonce</label>
{nonceHelp}
@ -29,7 +29,7 @@ export const NonceField: React.SFC<Props> = ({ alwaysDisplay }) => (
readOnly={readOnly}
onChange={onChange}
/>
</>
</div>
);
return alwaysDisplay || shouldDisplay ? content : null;

View File

@ -21,7 +21,7 @@ export default class SubTabs extends React.Component<Props> {
return (
<div className="SubTabs row">
<div className="SubTabs-tabs col-sm-8">
<div className="SubTabs-tabs col-sm-12">
{tabs.map((t, i) => (
// Same as normal Link, but knows when it's active, and applies activeClassName
<NavLink

View File

@ -0,0 +1,26 @@
@import 'common/sass/variables';
.Gas {
&-toggle {
display: inline-block;
position: relative;
margin-top: $space-sm;
left: -8px;
}
}
.Calculating-limit {
color: rgba(51, 51, 51, 0.7);
display: flex;
align-items: baseline;
font-weight: 400;
opacity: 0;
pointer-events: none;
&.active {
opacity: 1;
}
.Spinner {
margin-left: 8px;
}
}

View File

@ -1,15 +1,24 @@
import React from 'react';
import { translateRaw } from 'translations';
import { connect } from 'react-redux';
import { inputGasPrice, TInputGasPrice } from 'actions/transaction';
import {
inputGasPrice,
TInputGasPrice,
getNonceRequested,
TGetNonceRequested,
reset,
TReset
} from 'actions/transaction';
import { fetchCCRates, TFetchCCRates } from 'actions/rates';
import { getNetworkConfig, getOffline } from 'selectors/config';
import { AppState } from 'reducers';
import SimpleGas from './components/SimpleGas';
import AdvancedGas from './components/AdvancedGas';
import './GasSlider.scss';
import AdvancedGas, { AdvancedOptions } from './components/AdvancedGas';
import './TXMetaDataPanel.scss';
import { getGasPrice } from 'selectors/transaction';
type SliderStates = 'simple' | 'advanced';
interface StateProps {
gasPrice: AppState['transaction']['fields']['gasPrice'];
offline: AppState['config']['offline'];
@ -19,26 +28,42 @@ interface StateProps {
interface DispatchProps {
inputGasPrice: TInputGasPrice;
fetchCCRates: TFetchCCRates;
getNonceRequested: TGetNonceRequested;
reset: TReset;
}
// Set default props for props that can't be truthy or falsy
interface DefaultProps {
initialState: SliderStates;
}
interface OwnProps {
disableAdvanced?: boolean;
initialState?: SliderStates;
disableToggle?: boolean;
advancedGasOptions?: AdvancedOptions;
className?: string;
}
type Props = DispatchProps & OwnProps & StateProps;
interface State {
showAdvanced: boolean;
sliderState: SliderStates;
}
class GasSlider extends React.Component<Props, State> {
class TXMetaDataPanel extends React.Component<Props, State> {
public static defaultProps: DefaultProps = {
initialState: 'simple'
};
public state: State = {
showAdvanced: false
sliderState: (this.props as DefaultProps).initialState
};
public componentDidMount() {
if (!this.props.offline) {
this.props.reset();
this.props.fetchCCRates([this.props.network.unit]);
this.props.getNonceRequested();
}
}
@ -49,21 +74,24 @@ class GasSlider extends React.Component<Props, State> {
}
public render() {
const { offline, disableAdvanced, gasPrice } = this.props;
const showAdvanced = (this.state.showAdvanced || offline) && !disableAdvanced;
const { offline, disableToggle, gasPrice, advancedGasOptions, className = '' } = this.props;
const showAdvanced = this.state.sliderState === 'advanced' || offline;
return (
<div className="GasSlider">
<div className={`Gas col-md-12 ${className}`}>
{showAdvanced ? (
<AdvancedGas gasPrice={gasPrice} inputGasPrice={this.props.inputGasPrice} />
<AdvancedGas
gasPrice={gasPrice}
inputGasPrice={this.props.inputGasPrice}
options={advancedGasOptions}
/>
) : (
<SimpleGas gasPrice={gasPrice} inputGasPrice={this.props.inputGasPrice} />
)}
{!offline &&
!disableAdvanced && (
!disableToggle && (
<div className="help-block">
<a className="GasSlider-toggle" onClick={this.toggleAdvanced}>
<a className="Gas-toggle" onClick={this.toggleAdvanced}>
<strong>
{showAdvanced
? `- ${translateRaw('Back to simple')}`
@ -77,7 +105,7 @@ class GasSlider extends React.Component<Props, State> {
}
private toggleAdvanced = () => {
this.setState({ showAdvanced: !this.state.showAdvanced });
this.setState({ sliderState: this.state.sliderState === 'advanced' ? 'simple' : 'advanced' });
};
}
@ -91,5 +119,7 @@ function mapStateToProps(state: AppState): StateProps {
export default connect(mapStateToProps, {
inputGasPrice,
fetchCCRates
})(GasSlider);
fetchCCRates,
getNonceRequested,
reset
})(TXMetaDataPanel);

View File

@ -0,0 +1,49 @@
@import 'common/sass/variables';
.AdvancedGas {
margin-top: 0;
margin-bottom: 0;
&-calculate-limit {
.checkbox {
display: flex;
align-items: center;
width: fit-content;
input[type='checkbox'] {
position: initial;
margin: 0;
margin-right: 8px;
}
span {
font-size: 1rem;
font-weight: 400;
}
}
}
&-flex-wrapper {
margin: 0px -8px;
}
&-gas-price,
&-gas-limit,
&-nonce {
width: initial;
flex-grow: 1;
margin: 0px 8px;
}
@media screen and (max-width: $screen-lg) {
&-flex-wrapper {
flex-wrap: wrap;
}
&-nonce {
width: 100%;
}
}
&-data {
}
&-fee-summary {
}
}

View File

@ -0,0 +1,139 @@
import React from 'react';
import classnames from 'classnames';
import translate, { translateRaw } from 'translations';
import FeeSummary from './FeeSummary';
import './AdvancedGas.scss';
import { TToggleAutoGasLimit, toggleAutoGasLimit } from 'actions/config';
import { AppState } from 'reducers';
import { TInputGasPrice } from 'actions/transaction';
import { NonceField, GasLimitField, DataField } from 'components';
import { connect } from 'react-redux';
import { getAutoGasLimitEnabled } from 'selectors/config';
import { isValidGasPrice } from 'selectors/transaction';
import { sanitizeNumericalInput } from 'libs/values';
export interface AdvancedOptions {
gasPriceField?: boolean;
gasLimitField?: boolean;
nonceField?: boolean;
dataField?: boolean;
feeSummary?: boolean;
}
interface OwnProps {
inputGasPrice: TInputGasPrice;
gasPrice: AppState['transaction']['fields']['gasPrice'];
options?: AdvancedOptions;
}
interface StateProps {
autoGasLimitEnabled: AppState['config']['autoGasLimit'];
validGasPrice: boolean;
}
interface DispatchProps {
toggleAutoGasLimit: TToggleAutoGasLimit;
}
interface State {
options: AdvancedOptions;
}
type Props = OwnProps & StateProps & DispatchProps;
class AdvancedGas extends React.Component<Props, State> {
public state = {
options: {
gasPriceField: true,
gasLimitField: true,
nonceField: true,
dataField: true,
feeSummary: true,
...this.props.options
}
};
public render() {
const { autoGasLimitEnabled, gasPrice, validGasPrice } = this.props;
const { gasPriceField, gasLimitField, nonceField, dataField, feeSummary } = this.state.options;
return (
<div className="AdvancedGas row form-group">
<div className="AdvancedGas-calculate-limit">
<label className="checkbox">
<input
type="checkbox"
defaultChecked={autoGasLimitEnabled}
onChange={this.handleToggleAutoGasLimit}
/>
<span>Automatically Calculate Gas Limit</span>
</label>
</div>
<div className="AdvancedGas-flex-wrapper flex-wrapper">
{gasPriceField && (
<div className="AdvancedGas-gas-price">
<label>{translate('OFFLINE_Step2_Label_3')} (gwei)</label>
<input
className={classnames('form-control', { 'is-invalid': !validGasPrice })}
type="number"
placeholder="40"
value={gasPrice.raw}
onChange={this.handleGasPriceChange}
/>
</div>
)}
{gasLimitField && (
<div className="AdvancedGas-gas-limit">
<GasLimitField
includeLabel={true}
customLabel={translateRaw('OFFLINE_Step2_Label_4')}
onlyIncludeLoader={false}
/>
</div>
)}
{nonceField && (
<div className="AdvancedGas-nonce">
<NonceField alwaysDisplay={true} />
</div>
)}
</div>
{dataField && (
<div className="AdvancedGas-data">
<DataField />
</div>
)}
{feeSummary && (
<div className="AdvancedGas-fee-summary">
<FeeSummary
render={({ gasPriceWei, gasLimit, fee, usd }) => (
<span>
{gasPriceWei} * {gasLimit} = {fee} {usd && <span>~= ${usd} USD</span>}
</span>
)}
/>
</div>
)}
</div>
);
}
private handleGasPriceChange = (ev: React.FormEvent<HTMLInputElement>) => {
const { value } = ev.currentTarget;
this.props.inputGasPrice(sanitizeNumericalInput(value));
};
private handleToggleAutoGasLimit = (_: React.FormEvent<HTMLInputElement>) => {
this.props.toggleAutoGasLimit();
};
}
export default connect(
(state: AppState) => ({
autoGasLimitEnabled: getAutoGasLimitEnabled(state),
validGasPrice: isValidGasPrice(state)
}),
{ toggleAutoGasLimit }
)(AdvancedGas);

View File

@ -4,23 +4,26 @@
margin-top: 0;
margin-bottom: 0;
&-flex-spacer {
flex-grow: 2;
}
&-title {
&-input-group {
display: flex;
}
&-estimating {
color: rgba(51, 51, 51, 0.7);
display: flex;
align-items: baseline;
font-weight: 400;
opacity: 0;
&.active {
opacity: 1;
> .SimpleGas-slider {
flex-grow: 1;
margin-right: $input-padding-x;
}
.Spinner {
margin-left: 8px;
> .FeeSummary {
margin-left: $input-padding-x;
min-width: 224px;
}
@media screen and (max-width: $screen-md) {
flex-wrap: wrap;
> .SimpleGas-slider {
width: 100%;
margin-right: 0;
}
> .FeeSummary {
width: 100%;
margin-left: 0;
}
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import Slider from 'rc-slider';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import { gasPriceDefaults } from 'config';
import FeeSummary from './FeeSummary';
import { TInputGasPrice } from 'actions/transaction';
@ -29,14 +29,16 @@ class SimpleGas extends React.Component<Props> {
return (
<div className="SimpleGas row form-group">
<div className="col-md-12 SimpleGas-title">
<label className="SimpleGas-label">{translate('Transaction Fee')}</label>
<div className="SimpleGas-flex-spacer" />
<GasLimitField includeLabel={false} onlyIncludeLoader={true} />
<div className="SimpleGas-title">
<GasLimitField
includeLabel={true}
customLabel={translateRaw('Transaction Fee')}
onlyIncludeLoader={true}
/>
</div>
{gasLimitEstimationTimedOut && (
<div className="col-md-12 prompt-toggle-gas-limit">
<div className="prompt-toggle-gas-limit">
<p className="small">
{isWeb3Node
? "Couldn't calculate gas limit, if you know what your doing, try setting manually in Advanced settings"
@ -45,7 +47,7 @@ class SimpleGas extends React.Component<Props> {
</div>
)}
<div className="col-md-8 col-sm-12">
<div className="SimpleGas-input-group">
<div className="SimpleGas-slider">
<Slider
onChange={this.handleSlider}
@ -59,8 +61,6 @@ class SimpleGas extends React.Component<Props> {
<span>{translate('Fast')}</span>
</div>
</div>
</div>
<div className="col-md-4 col-sm-12">
<FeeSummary
render={({ fee, usd }) => (
<span>

View File

@ -0,0 +1,2 @@
import TXMetaDataPanel from './TXMetaDataPanel';
export default TXMetaDataPanel;

View File

@ -3,12 +3,12 @@
.DWModal {
&-path {
display: flex;
margin-bottom: 20px;
&-label {
font-size: $font-size-medium;
margin-right: 16px;
line-height: $input-height-base;
}
.form-control {
@ -25,7 +25,7 @@
&-addresses {
overflow-y: scroll;
&-table {
width: 695px;
width: 732px;
text-align: center;
margin: auto;
margin-bottom: 10px;

View File

@ -122,8 +122,10 @@ class DeterministicWalletsModalClass extends React.Component<Props, State> {
handleClose={onCancel}
>
<div className="DWModal">
{/* TODO: replace styles for flexbox with flexbox classes in https://github.com/MyEtherWallet/MyEtherWallet/pull/850/files#diff-2150778b9391533fec7b8afd060c7672 */}
<form className="DWModal-path form-group-sm" onSubmit={this.handleSubmitCustomPath}>
<form
className="DWModal-path form-group-sm flex-wrapper"
onSubmit={this.handleSubmitCustomPath}
>
<span className="DWModal-path-label">Addresses </span>
<Select
name="fieldDPath"

View File

@ -14,5 +14,5 @@ export { default as Footer } from './Footer';
export { default as BalanceSidebar } from './BalanceSidebar';
export { default as PaperWallet } from './PaperWallet';
export { default as AlphaAgreement } from './AlphaAgreement';
export { default as GasSlider } from './GasSlider';
export { default as TXMetaDataPanel } from './TXMetaDataPanel';
export { default as WalletDecrypt } from './WalletDecrypt';

View File

@ -7,10 +7,12 @@
border: 1px solid #ccc;
padding: 0.4rem 1rem;
border-radius: 2px;
height: 2.5rem;
&:focus {
outline: none;
}
&:active, &:hover {
&:active,
&:hover {
opacity: 0.8;
}
> li {
@ -46,10 +48,10 @@
text-align: left;
z-index: 500;
background: white;
box-shadow: 2px 1px 60px rgba(0,0,0,.175);
box-shadow: 2px 1px 60px rgba(0, 0, 0, 0.175);
&::before {
content: "";
content: '';
position: absolute;
top: -20px;
left: 50%;
@ -59,7 +61,7 @@
border-top: 10px solid transparent;
border-bottom: 10px solid #fff;
}
&.open {
display: block;
}
@ -74,7 +76,7 @@
padding: 5px 20px;
color: #163151;
&:hover {
opacity: .8;
opacity: 0.8;
background-color: #163151;
color: #fff;
}
@ -84,7 +86,7 @@
color: grey;
&:hover {
background-color: #fff;
color:#163151;
color: #163151;
cursor: not-allowed;
}
}
@ -109,4 +111,4 @@
img {
padding-right: 1px;
}
}
}

View File

@ -1,10 +1,8 @@
import translate from 'translations';
import classnames from 'classnames';
import { DataFieldFactory } from 'components/DataFieldFactory';
import { GasLimitFieldFactory } from 'components/GasLimitFieldFactory';
import { SendButtonFactory } from 'components/SendButtonFactory';
import { SigningStatus } from 'components/SigningStatus';
import { NonceField } from 'components/NonceField';
import WalletDecrypt, { DISABLE_WALLETS } from 'components/WalletDecrypt';
import { GenerateTransaction } from 'components/GenerateTransaction';
import React, { Component } from 'react';
@ -12,6 +10,7 @@ import { setToField, TSetToField } from 'actions/transaction';
import { resetWallet, TResetWallet } from 'actions/wallet';
import { connect } from 'react-redux';
import { FullWalletOnly } from 'components/renderCbs';
import { NonceField, TXMetaDataPanel } from 'components';
import './Deploy.scss';
interface DispatchProps {
@ -46,27 +45,22 @@ class DeployClass extends Component<DispatchProps> {
/>
</div>
<label className="Deploy-field form-group">
<h4 className="Deploy-field-label">Gas Limit</h4>
<GasLimitFieldFactory
withProps={({ gasLimit: { raw, value }, onChange, readOnly }) => (
<input
name="gasLimit"
value={raw}
disabled={readOnly}
onChange={onChange}
className={classnames('Deploy-field-input', 'form-control', {
'is-invalid': !value
})}
/>
)}
/>
</label>
<div className="row form-group">
<div className="col-xs-11">
<div className="col-xs-12 clearfix">
<NonceField alwaysDisplay={false} />
</div>
</div>
<div className="row form-group">
<div className="col-xs-12 clearfix">
<TXMetaDataPanel
initialState="advanced"
disableToggle={true}
advancedGasOptions={{ dataField: false }}
/>
</div>
</div>
<div className="row form-group">
<div className="col-xs-12 clearfix">
<GenerateTransaction />

View File

@ -1,7 +1,6 @@
import { GasLimitField } from './GasLimitField';
import { AmountField } from './AmountField';
import React, { Component } from 'react';
import { NonceField, SendButton, SigningStatus } from 'components';
import { SendButton, SigningStatus, TXMetaDataPanel } from 'components';
import WalletDecrypt, { DISABLE_WALLETS } from 'components/WalletDecrypt';
import { FullWalletOnly } from 'components/renderCbs';
@ -12,9 +11,13 @@ export class Fields extends Component<OwnProps> {
public render() {
const makeContent = () => (
<React.Fragment>
<GasLimitField />
<AmountField />
<NonceField alwaysDisplay={false} />
<TXMetaDataPanel
className="form-group"
initialState="advanced"
disableToggle={true}
advancedGasOptions={{ dataField: false }}
/>
{this.props.button}
<SigningStatus />
<SendButton />

View File

@ -1,22 +0,0 @@
import React from 'react';
import { GasLimitFieldFactory } from 'components/GasLimitFieldFactory';
import classnames from 'classnames';
export const GasLimitField: React.SFC<{}> = () => (
<label className="InteractExplorer-field form-group">
<h4 className="InteractExplorer-field-label">Gas Limit</h4>
<GasLimitFieldFactory
withProps={({ gasLimit: { raw, value }, onChange, readOnly }) => (
<input
name="gasLimit"
value={raw}
disabled={readOnly}
onChange={onChange}
className={classnames('InteractExplorer-field-input', 'form-control', {
'is-invalid': !value
})}
/>
)}
/>
</label>
);

View File

@ -4,7 +4,7 @@ import { isAnyOfflineWithWeb3 } from 'selectors/derived';
import {
AddressField,
AmountField,
GasSlider,
TXMetaDataPanel,
SendEverything,
CurrentCustomMessage,
GenerateTransaction,
@ -29,7 +29,7 @@ const content = (
<div className="row form-group">
<div className="col-xs-12">
<GasSlider />
<TXMetaDataPanel />
</div>
</div>

View File

@ -15,7 +15,7 @@ import BN from 'bn.js';
import { NetworkConfig } from 'config';
import { validNumber, validDecimal } from 'libs/validators';
import { getGasLimit } from 'selectors/transaction';
import { AddressField, AmountField, GasLimitField } from 'components';
import { AddressField, AmountField, TXMetaDataPanel } from 'components';
import { SetGasLimitFieldAction } from 'actions/transaction/actionTypes/fields';
import { buildEIP681EtherRequest, buildEIP681TokenRequest } from 'libs/values';
import { getNetworkConfig, getSelectedTokenContractAddress } from 'selectors/config';
@ -106,7 +106,16 @@ class RequestPayment extends React.Component<Props, {}> {
<div className="row form-group">
<div className="col-xs-11">
<GasLimitField includeLabel={true} onlyIncludeLoader={false} />
<TXMetaDataPanel
initialState="advanced"
disableToggle={true}
advancedGasOptions={{
gasPriceField: false,
nonceField: false,
dataField: false,
feeSummary: false
}}
/>
</div>
</div>

View File

@ -34,13 +34,14 @@
}
&-dropdown {
display: inline-block;
margin: 0.5rem 0;
margin: 0 0;
}
&-input {
width: 100%;
max-width: 10rem;
margin-right: $space-sm;
margin-bottom: 0;
}
&-divider {
@ -54,6 +55,3 @@
margin-top: $space * 2.5;
}
}

View File

@ -329,7 +329,7 @@ export default class CurrencySwap extends Component<Props, State> {
<article className="CurrencySwap">
<h1 className="CurrencySwap-title">{translate('SWAP_init_1')}</h1>
{loaded || timeoutLoaded ? (
<div className="form-inline CurrencySwap-inner-wrap">
<div className="CurrencySwap-inner-wrap">
<div className="CurrencySwap-input-group">
{originErr && <span className="CurrencySwap-error-message">{originErr}</span>}
<input

View File

@ -3,7 +3,7 @@ import { AmountFieldFactory } from 'components/AmountFieldFactory';
import { AddressFieldFactory } from 'components/AddressFieldFactory';
import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { GenerateTransaction, SendButton, SigningStatus, GasSlider } from 'components';
import { GenerateTransaction, SendButton, SigningStatus, TXMetaDataPanel } from 'components';
import { resetWallet, TResetWallet } from 'actions/wallet';
import translate from 'translations';
import { getUnit } from 'selectors/transaction';
@ -76,7 +76,7 @@ class FieldsClass extends Component<Props> {
</div>
<div className="row form-group">
<div className="col-xs-12">
<GasSlider disableAdvanced={true} />
<TXMetaDataPanel initialState={'simple'} disableToggle={true} />
</div>
</div>
<SigningStatus />

View File

@ -30,6 +30,11 @@ const getTransactionFields = (t: Tx): IHexStrTransaction => {
};
};
const getTransactionFee = (t: Tx) => {
const { gasPrice, gasLimit } = getTransactionFields(t);
return Wei(gasPrice).mul(Wei(gasLimit));
};
/**
* @description Return the minimum amount of ether needed
* @param t
@ -101,5 +106,6 @@ export {
validateTx,
makeTransaction,
getTransactionFields,
getTransactionFee,
computeIndexingHash
};

View File

@ -20,6 +20,7 @@ export {
validGasLimit,
makeTransaction,
getTransactionFields,
getTransactionFee,
computeIndexingHash
} from './ether';
export * from './token';

View File

@ -33,4 +33,5 @@
@import './styles/overrides';
@import './styles/scaffolding';
@import './styles/tab';
@import './styles/flexbox';
@import './fonts';

View File

@ -0,0 +1,12 @@
.flex-wrapper {
display: flex;
&-wrap {
flex-wrap: wrap;
}
&-nowrap {
flex-wrap: nowrap;
}
.flex-spacer {
flex-grow: 2;
}
}

View File

@ -30,7 +30,6 @@ input[readonly] {
}
.form-control {
margin-top: $space-sm;
margin-bottom: $space-sm;
transition: $transition;
padding: $input-padding;