From edda9f71ea5ccdce667eca34dd003c9160fa05b4 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Sun, 7 Jan 2018 11:43:06 -0500 Subject: [PATCH] Improved Gas UX (Pt. 1 - Gas Slider on Send) (#728) * Initial crack at simple only gas slider component. * Work on advanced component. Refactor redux and components to specify gas limit vs price. * Convert fee summary to a render cbesque thing. * Rework responsive columns. * Remove force offline button. * Tweak styles. * Fix tscheck issues, remove unneeded prop. * Fix references to GasField * Gas slider in lite send. * Make gas slider network-aware for symbol and price calculation. --- .../transaction/actionCreators/fields.ts | 9 ++ .../actions/transaction/actionTypes/fields.ts | 5 + common/actions/transaction/constants.ts | 1 + .../BalanceSidebar/OfflineToggle.tsx | 52 ---------- common/components/BalanceSidebar/index.tsx | 5 - common/components/GasFieldFactory/index.ts | 1 - .../{GasField.tsx => GasLimitField.tsx} | 6 +- .../GasLimitFieldFactory.tsx} | 10 +- .../GasLimitInputFactory.tsx} | 8 +- .../components/GasLimitFieldFactory/index.ts | 1 + common/components/GasSlider/GasSlider.scss | 10 ++ common/components/GasSlider/GasSlider.tsx | 99 +++++++++++++++++++ .../GasSlider/components/AdvancedGas.scss | 4 + .../GasSlider/components/AdvancedGas.tsx | 70 +++++++++++++ .../GasSlider/components/FeeSummary.scss | 11 +++ .../GasSlider/components/FeeSummary.tsx | 78 +++++++++++++++ .../GasSlider/components/SimpleGas.scss | 36 +++++++ .../GasSlider/components/SimpleGas.tsx | 54 ++++++++++ common/components/GasSlider/index.tsx | 2 + common/components/NonceField/NonceInput.tsx | 2 +- common/components/index.ts | 3 +- common/components/ui/UnitDisplay.tsx | 8 +- common/config/data.ts | 10 +- .../Tabs/Contracts/components/Deploy.tsx | 4 +- .../InteractExplorer/components/Fields.tsx | 4 +- .../{GasField.tsx => GasLimitField.tsx} | 6 +- .../components/Fields/Fields.tsx | 19 ++-- .../components/RequestPayment.tsx | 4 +- .../Tabs/Swap/components/LiteSend/Fields.tsx | 11 +-- common/reducers/transaction/fields/fields.ts | 3 +- common/sagas/transaction/fields/fields.ts | 23 ++++- common/sass/styles.scss | 3 + common/sass/styles/overrides.scss | 3 + common/sass/styles/overrides/rc-slider.scss | 25 +++++ package.json | 1 + 35 files changed, 481 insertions(+), 110 deletions(-) delete mode 100644 common/components/BalanceSidebar/OfflineToggle.tsx delete mode 100644 common/components/GasFieldFactory/index.ts rename common/components/{GasField.tsx => GasLimitField.tsx} (76%) rename common/components/{GasFieldFactory/GasFieldFactory.tsx => GasLimitFieldFactory/GasLimitFieldFactory.tsx} (80%) rename common/components/{GasFieldFactory/GasInputFactory.tsx => GasLimitFieldFactory/GasLimitInputFactory.tsx} (75%) create mode 100644 common/components/GasLimitFieldFactory/index.ts create mode 100644 common/components/GasSlider/GasSlider.scss create mode 100644 common/components/GasSlider/GasSlider.tsx create mode 100644 common/components/GasSlider/components/AdvancedGas.scss create mode 100644 common/components/GasSlider/components/AdvancedGas.tsx create mode 100644 common/components/GasSlider/components/FeeSummary.scss create mode 100644 common/components/GasSlider/components/FeeSummary.tsx create mode 100644 common/components/GasSlider/components/SimpleGas.scss create mode 100644 common/components/GasSlider/components/SimpleGas.tsx create mode 100644 common/components/GasSlider/index.tsx rename common/containers/Tabs/Contracts/components/Interact/components/InteractExplorer/components/{GasField.tsx => GasLimitField.tsx} (78%) create mode 100644 common/sass/styles/overrides/rc-slider.scss diff --git a/common/actions/transaction/actionCreators/fields.ts b/common/actions/transaction/actionCreators/fields.ts index 1e0827c3..0ac1a4b5 100644 --- a/common/actions/transaction/actionCreators/fields.ts +++ b/common/actions/transaction/actionCreators/fields.ts @@ -5,6 +5,7 @@ import { SetNonceFieldAction, SetValueFieldAction, InputGasLimitAction, + InputGasPriceAction, InputDataAction, InputNonceAction, ResetAction, @@ -18,6 +19,12 @@ const inputGasLimit = (payload: InputGasLimitAction['payload']) => ({ payload }); +type TInputGasPrice = typeof inputGasPrice; +const inputGasPrice = (payload: InputGasPriceAction['payload']) => ({ + type: TypeKeys.GAS_PRICE_INPUT, + payload +}); + type TInputNonce = typeof inputNonce; const inputNonce = (payload: InputNonceAction['payload']) => ({ type: TypeKeys.NONCE_INPUT, @@ -71,6 +78,7 @@ const reset = (): ResetAction => ({ type: TypeKeys.RESET }); export { TInputGasLimit, + TInputGasPrice, TInputNonce, TInputData, TSetGasLimitField, @@ -81,6 +89,7 @@ export { TSetGasPriceField, TReset, inputGasLimit, + inputGasPrice, inputNonce, inputData, setGasLimitField, diff --git a/common/actions/transaction/actionTypes/fields.ts b/common/actions/transaction/actionTypes/fields.ts index 0b151b05..c390844b 100644 --- a/common/actions/transaction/actionTypes/fields.ts +++ b/common/actions/transaction/actionTypes/fields.ts @@ -6,6 +6,10 @@ interface InputGasLimitAction { type: TypeKeys.GAS_LIMIT_INPUT; payload: string; } +interface InputGasPriceAction { + type: TypeKeys.GAS_PRICE_INPUT; + payload: string; +} interface InputDataAction { type: TypeKeys.DATA_FIELD_INPUT; payload: string; @@ -79,6 +83,7 @@ type FieldAction = export { InputGasLimitAction, + InputGasPriceAction, InputDataAction, InputNonceAction, SetGasLimitFieldAction, diff --git a/common/actions/transaction/constants.ts b/common/actions/transaction/constants.ts index 6da04328..4988575c 100644 --- a/common/actions/transaction/constants.ts +++ b/common/actions/transaction/constants.ts @@ -28,6 +28,7 @@ export enum TypeKeys { DATA_FIELD_INPUT = 'DATA_FIELD_INPUT', GAS_LIMIT_INPUT = 'GAS_LIMIT_INPUT', + GAS_PRICE_INPUT = 'GAS_PRICE_INPUT', NONCE_INPUT = 'NONCE_INPUT', DATA_FIELD_SET = 'DATA_FIELD_SET', diff --git a/common/components/BalanceSidebar/OfflineToggle.tsx b/common/components/BalanceSidebar/OfflineToggle.tsx deleted file mode 100644 index 5175765e..00000000 --- a/common/components/BalanceSidebar/OfflineToggle.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { forceOfflineConfig as dForceOfflineConfig, TForceOfflineConfig } from 'actions/config'; -import OfflineSymbol from 'components/ui/OfflineSymbol'; -import { connect } from 'react-redux'; -import { AppState } from 'reducers'; - -type sizeType = 'small' | 'medium' | 'large'; - -interface OfflineToggleProps { - offline: boolean; - forceOffline: boolean; - forceOfflineConfig: TForceOfflineConfig; - size?: sizeType; -} - -class OfflineToggle extends React.Component { - public render() { - const { forceOfflineConfig, offline, forceOffline, size } = this.props; - - return ( -
- {!offline ? ( -
-
- -
-
- -
-
- ) : ( -
-
You are currently offline.
-
- )} -
- ); - } -} - -function mapStateToProps(state: AppState) { - return { - offline: state.config.offline, - forceOffline: state.config.forceOffline - }; -} - -export default connect(mapStateToProps, { - forceOfflineConfig: dForceOfflineConfig -})(OfflineToggle); diff --git a/common/components/BalanceSidebar/index.tsx b/common/components/BalanceSidebar/index.tsx index 3221afb3..acba0611 100644 --- a/common/components/BalanceSidebar/index.tsx +++ b/common/components/BalanceSidebar/index.tsx @@ -10,7 +10,6 @@ import AccountInfo from './AccountInfo'; import EquivalentValues from './EquivalentValues'; import Promos from './Promos'; import TokenBalances from './TokenBalances'; -import OfflineToggle from './OfflineToggle'; interface Props { wallet: IWallet; @@ -37,10 +36,6 @@ export class BalanceSidebar extends React.Component { } const blocks: Block[] = [ - { - name: 'Go Offline', - content: - }, { name: 'Account Info', content: diff --git a/common/components/GasFieldFactory/index.ts b/common/components/GasFieldFactory/index.ts deleted file mode 100644 index 518a5659..00000000 --- a/common/components/GasFieldFactory/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './GasFieldFactory'; diff --git a/common/components/GasField.tsx b/common/components/GasLimitField.tsx similarity index 76% rename from common/components/GasField.tsx rename to common/components/GasLimitField.tsx index df91d6ce..5b905d83 100644 --- a/common/components/GasField.tsx +++ b/common/components/GasLimitField.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { GasFieldFactory } from './GasFieldFactory'; +import { GasLimitFieldFactory } from './GasLimitFieldFactory'; import translate from 'translations'; import { Aux } from 'components/ui'; -export const GasField: React.SFC<{}> = () => ( +export const GasLimitField: React.SFC<{}> = () => ( - ( { } public render() { - return ; + return ; } private setGas = (ev: React.FormEvent) => { @@ -45,13 +45,13 @@ class GasLimitFieldClass extends Component { const GasLimitField = connect(null, { inputGasLimit })(GasLimitFieldClass); -interface DefaultGasFieldProps { +interface DefaultGasLimitFieldProps { withProps(props: CallBackProps): React.ReactElement | null; } -const DefaultGasField: React.SFC = ({ withProps }) => ( +const DefaultGasLimitField: React.SFC = ({ withProps }) => ( } /> ); -export { DefaultGasField as GasFieldFactory }; +export { DefaultGasLimitField as GasLimitFieldFactory }; diff --git a/common/components/GasFieldFactory/GasInputFactory.tsx b/common/components/GasLimitFieldFactory/GasLimitInputFactory.tsx similarity index 75% rename from common/components/GasFieldFactory/GasInputFactory.tsx rename to common/components/GasLimitFieldFactory/GasLimitInputFactory.tsx index a9422b93..727ed644 100644 --- a/common/components/GasFieldFactory/GasInputFactory.tsx +++ b/common/components/GasLimitFieldFactory/GasLimitInputFactory.tsx @@ -3,7 +3,7 @@ import { Query } from 'components/renderCbs'; import { connect } from 'react-redux'; import { AppState } from 'reducers'; import { getGasLimit } from 'selectors/transaction'; -import { CallBackProps } from 'components/GasFieldFactory'; +import { CallBackProps } from 'components/GasLimitFieldFactory'; interface StateProps { gasLimit: AppState['transaction']['fields']['gasLimit']; @@ -15,7 +15,7 @@ interface OwnProps { } type Props = StateProps & OwnProps; -class GasInputClass extends Component { +class GasLimitInputClass extends Component { public render() { const { gasLimit, onChange } = this.props; return ( @@ -29,6 +29,6 @@ class GasInputClass extends Component { } } -export const GasInput = connect((state: AppState) => ({ gasLimit: getGasLimit(state) }))( - GasInputClass +export const GasLimitInput = connect((state: AppState) => ({ gasLimit: getGasLimit(state) }))( + GasLimitInputClass ); diff --git a/common/components/GasLimitFieldFactory/index.ts b/common/components/GasLimitFieldFactory/index.ts new file mode 100644 index 00000000..5bc25148 --- /dev/null +++ b/common/components/GasLimitFieldFactory/index.ts @@ -0,0 +1 @@ +export * from './GasLimitFieldFactory'; diff --git a/common/components/GasSlider/GasSlider.scss b/common/components/GasSlider/GasSlider.scss new file mode 100644 index 00000000..07cfb0b1 --- /dev/null +++ b/common/components/GasSlider/GasSlider.scss @@ -0,0 +1,10 @@ +@import 'common/sass/variables'; + +.GasSlider { + &-toggle { + display: inline-block; + position: relative; + margin-top: $space-sm; + left: -8px; + } +} diff --git a/common/components/GasSlider/GasSlider.tsx b/common/components/GasSlider/GasSlider.tsx new file mode 100644 index 00000000..789d85a5 --- /dev/null +++ b/common/components/GasSlider/GasSlider.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { translateRaw } from 'translations'; +import { connect } from 'react-redux'; +import { + inputGasPrice, + TInputGasPrice, + inputGasLimit, + TInputGasLimit, + inputNonce, + TInputNonce +} from 'actions/transaction'; +import { fetchCCRates, TFetchCCRates } from 'actions/rates'; +import { getNetworkConfig } from 'selectors/config'; +import { AppState } from 'reducers'; +import SimpleGas from './components/SimpleGas'; +import AdvancedGas from './components/AdvancedGas'; +import './GasSlider.scss'; + +interface Props { + // Component configuration + disableAdvanced?: boolean; + // Data + gasPrice: AppState['transaction']['fields']['gasPrice']; + gasLimit: AppState['transaction']['fields']['gasLimit']; + offline: AppState['config']['offline']; + network: AppState['config']['network']; + // Actions + inputGasPrice: TInputGasPrice; + inputGasLimit: TInputGasLimit; + inputNonce: TInputNonce; + fetchCCRates: TFetchCCRates; +} + +interface State { + showAdvanced: boolean; +} + +class GasSlider extends React.Component { + public state: State = { + showAdvanced: false + }; + + public componentDidMount() { + this.props.fetchCCRates([this.props.network.unit]); + } + + public render() { + const { gasPrice, gasLimit, offline, disableAdvanced } = this.props; + const showAdvanced = (this.state.showAdvanced || offline) && !disableAdvanced; + + return ( +
+ {showAdvanced ? ( + + ) : ( + + )} + + {!offline && + !disableAdvanced && ( + + )} +
+ ); + } + + private toggleAdvanced = () => { + this.setState({ showAdvanced: !this.state.showAdvanced }); + }; +} + +function mapStateToProps(state: AppState) { + return { + gasPrice: state.transaction.fields.gasPrice, + gasLimit: state.transaction.fields.gasLimit, + offline: state.config.offline, + network: getNetworkConfig(state) + }; +} + +export default connect(mapStateToProps, { + inputGasPrice, + inputGasLimit, + inputNonce, + fetchCCRates +})(GasSlider); diff --git a/common/components/GasSlider/components/AdvancedGas.scss b/common/components/GasSlider/components/AdvancedGas.scss new file mode 100644 index 00000000..8776d0fe --- /dev/null +++ b/common/components/GasSlider/components/AdvancedGas.scss @@ -0,0 +1,4 @@ +.AdvancedGas { + margin-top: 0; + margin-bottom: 0; +} diff --git a/common/components/GasSlider/components/AdvancedGas.tsx b/common/components/GasSlider/components/AdvancedGas.tsx new file mode 100644 index 00000000..0b290bb6 --- /dev/null +++ b/common/components/GasSlider/components/AdvancedGas.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import translate from 'translations'; +import { DataFieldFactory } from 'components/DataFieldFactory'; +import FeeSummary from './FeeSummary'; +import './AdvancedGas.scss'; + +interface Props { + gasPrice: string; + gasLimit: string; + changeGasPrice(gwei: string): void; + changeGasLimit(wei: string): void; +} + +export default class AdvancedGas extends React.Component { + public render() { + return ( +
+
+ + +
+ +
+ + +
+ +
+ + ( + + )} + /> +
+ +
+ ( + + {gasPriceWei} * {gasLimit} = {fee} {usd && ~= ${usd} USD} + + )} + /> +
+
+ ); + } + + private handleGasPriceChange = (ev: React.FormEvent) => { + this.props.changeGasPrice(ev.currentTarget.value); + }; + + private handleGasLimitChange = (ev: React.FormEvent) => { + this.props.changeGasLimit(ev.currentTarget.value); + }; +} diff --git a/common/components/GasSlider/components/FeeSummary.scss b/common/components/GasSlider/components/FeeSummary.scss new file mode 100644 index 00000000..3910465e --- /dev/null +++ b/common/components/GasSlider/components/FeeSummary.scss @@ -0,0 +1,11 @@ +@import 'common/sass/variables'; + +.FeeSummary { + background: $gray-lighter; + height: 42px; + line-height: 42px; + padding: 0 12px; + font-family: $font-family-monospace; + text-align: center; + font-size: 14px; +} diff --git a/common/components/GasSlider/components/FeeSummary.tsx b/common/components/GasSlider/components/FeeSummary.tsx new file mode 100644 index 00000000..7b597dc6 --- /dev/null +++ b/common/components/GasSlider/components/FeeSummary.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import BN from 'bn.js'; +import { connect } from 'react-redux'; +import { AppState } from 'reducers'; +import { getNetworkConfig } from 'selectors/config'; +import { UnitDisplay } from 'components/ui'; +import './FeeSummary.scss'; + +interface RenderData { + gasPriceWei: string; + gasPriceGwei: string; + gasLimit: string; + fee: React.ReactElement; + usd: React.ReactElement | null; +} + +interface Props { + // Redux props + gasPrice: AppState['transaction']['fields']['gasPrice']; + gasLimit: AppState['transaction']['fields']['gasLimit']; + rates: AppState['rates']['rates']; + network: AppState['config']['network']; + // Component props + render(data: RenderData): React.ReactElement | string; +} + +class FeeSummary extends React.Component { + public render() { + const { gasPrice, gasLimit, rates, network } = this.props; + + const feeBig = gasPrice.value && gasLimit.value && gasPrice.value.mul(gasLimit.value); + const fee = ( + + ); + + const usdBig = network.isTestnet + ? new BN(0) + : feeBig && rates[network.unit] && feeBig.muln(rates[network.unit].USD); + const usd = ( + + ); + + return ( +
+ {this.props.render({ + gasPriceWei: gasPrice.value.toString(), + gasPriceGwei: gasPrice.raw, + fee, + usd, + gasLimit: gasLimit.raw + })} +
+ ); + } +} + +function mapStateToProps(state: AppState) { + return { + gasPrice: state.transaction.fields.gasPrice, + gasLimit: state.transaction.fields.gasLimit, + rates: state.rates.rates, + network: getNetworkConfig(state) + }; +} + +export default connect(mapStateToProps)(FeeSummary); diff --git a/common/components/GasSlider/components/SimpleGas.scss b/common/components/GasSlider/components/SimpleGas.scss new file mode 100644 index 00000000..cc48f426 --- /dev/null +++ b/common/components/GasSlider/components/SimpleGas.scss @@ -0,0 +1,36 @@ +@import 'common/sass/variables'; + +.SimpleGas { + margin-top: 0; + margin-bottom: 0; + + &-label { + display: block; + } + + &-slider { + padding-top: 8px; + margin-bottom: $space-xs; + + &-labels { + margin-top: 4px; + display: flex; + + > span { + flex: 1; + padding: 0 $space-xs; + text-align: center; + color: $gray-light; + font-size: $font-size-xs; + + &:first-child { + text-align: left; + } + + &:last-child { + text-align: right; + } + } + } + } +} diff --git a/common/components/GasSlider/components/SimpleGas.tsx b/common/components/GasSlider/components/SimpleGas.tsx new file mode 100644 index 00000000..5fe71ddf --- /dev/null +++ b/common/components/GasSlider/components/SimpleGas.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import Slider from 'rc-slider'; +import translate from 'translations'; +import { gasPriceDefaults } from 'config/data'; +import FeeSummary from './FeeSummary'; +import './SimpleGas.scss'; + +interface Props { + gasPrice: string; + changeGasPrice(gwei: string): void; +} + +export default class SimpleGas extends React.Component { + public render() { + const { gasPrice } = this.props; + + return ( +
+
+ +
+ +
+
+ +
+ {translate('Cheap')} + {translate('Balanced')} + {translate('Fast')} +
+
+
+
+ ( + + {fee} {usd && / ${usd}} + + )} + /> +
+
+ ); + } + + private handleSlider = (gasGwei: number) => { + this.props.changeGasPrice(gasGwei.toString()); + }; +} diff --git a/common/components/GasSlider/index.tsx b/common/components/GasSlider/index.tsx new file mode 100644 index 00000000..0ab3bc93 --- /dev/null +++ b/common/components/GasSlider/index.tsx @@ -0,0 +1,2 @@ +import GasSlider from './GasSlider'; +export default GasSlider; diff --git a/common/components/NonceField/NonceInput.tsx b/common/components/NonceField/NonceInput.tsx index 2cc5d533..bfdd80f4 100644 --- a/common/components/NonceField/NonceInput.tsx +++ b/common/components/NonceField/NonceInput.tsx @@ -27,8 +27,8 @@ class NonceInputClass extends Component { const { nonce: { raw, value }, onChange, shouldDisplay } = this.props; const content = ( - {nonceHelp} + {nonceHelp} !!(param as EthProps).unit; const UnitDisplay: React.SFC = params => { - const { value, symbol, displayShortBalance, checkOffline } = params; + const { value, symbol, displayShortBalance, displayTrailingZeroes, checkOffline } = params; let element; if (!value) { @@ -58,6 +59,9 @@ const UnitDisplay: React.SFC = params => { if (parseFloat(formattedValue) === 0 && parseFloat(convertedValue) !== 0) { const padding = digits !== 0 ? `.${'0'.repeat(digits - 1)}1` : ''; formattedValue = `< 0${padding}`; + } else if (displayTrailingZeroes) { + const [whole, deci] = formattedValue.split('.'); + formattedValue = `${whole}.${(deci || '').padEnd(digits, '0')}`; } } else { formattedValue = convertedValue; diff --git a/common/config/data.ts b/common/config/data.ts index 2c001b48..900cc888 100644 --- a/common/config/data.ts +++ b/common/config/data.ts @@ -83,6 +83,7 @@ export interface NetworkConfig { chainId: number; tokens: Token[]; contracts: NetworkContract[] | null; + isTestnet?: boolean; } export interface CustomNetworkConfig { @@ -150,7 +151,8 @@ export const NETWORKS: { [key: string]: NetworkConfig } = { color: '#adc101', blockExplorer: makeExplorer('https://ropsten.etherscan.io'), tokens: require('./tokens/ropsten.json'), - contracts: require('./contracts/ropsten.json') + contracts: require('./contracts/ropsten.json'), + isTestnet: true }, Kovan: { name: 'Kovan', @@ -159,7 +161,8 @@ export const NETWORKS: { [key: string]: NetworkConfig } = { color: '#adc101', blockExplorer: makeExplorer('https://kovan.etherscan.io'), tokens: require('./tokens/ropsten.json'), - contracts: require('./contracts/ropsten.json') + contracts: require('./contracts/ropsten.json'), + isTestnet: true }, Rinkeby: { name: 'Rinkeby', @@ -168,7 +171,8 @@ export const NETWORKS: { [key: string]: NetworkConfig } = { color: '#adc101', blockExplorer: makeExplorer('https://rinkeby.etherscan.io'), tokens: require('./tokens/rinkeby.json'), - contracts: require('./contracts/rinkeby.json') + contracts: require('./contracts/rinkeby.json'), + isTestnet: true }, RSK: { name: 'RSK', diff --git a/common/containers/Tabs/Contracts/components/Deploy.tsx b/common/containers/Tabs/Contracts/components/Deploy.tsx index 8840a143..2672b2a8 100644 --- a/common/containers/Tabs/Contracts/components/Deploy.tsx +++ b/common/containers/Tabs/Contracts/components/Deploy.tsx @@ -1,7 +1,7 @@ import translate from 'translations'; import classnames from 'classnames'; import { DataFieldFactory } from 'components/DataFieldFactory'; -import { GasFieldFactory } from 'components/GasFieldFactory'; +import { GasLimitFieldFactory } from 'components/GasLimitFieldFactory'; import { SendButtonFactory } from 'components/SendButtonFactory'; import { SigningStatus } from 'components/SigningStatus'; import { NonceField } from 'components/NonceField'; @@ -48,7 +48,7 @@ class DeployClass extends Component {