diff --git a/src/views/Wallet/views/AccountSend/components/Advanced/index.js b/src/views/Wallet/views/AccountSend/components/Advanced/index.js new file mode 100644 index 00000000..dd9d44e6 --- /dev/null +++ b/src/views/Wallet/views/AccountSend/components/Advanced/index.js @@ -0,0 +1,221 @@ +/* @flow */ + +import React from 'react'; +import styled from 'styled-components'; +import colors from 'config/colors'; + +import Input from 'components/inputs/Input'; +import Textarea from 'components/Textarea'; +import Tooltip from 'components/Tooltip'; +import Icon from 'components/Icon'; +import Link from 'components/Link'; +import ICONS from 'config/icons'; + +import type { Props } from '../../Container'; + +// duplicates from ../../Container +const InputRow = styled.div` + margin-bottom: 20px; +`; +// duplicates end + +const InputLabelWrapper = styled.div` + display: flex; + align-items: center; +`; + +const GreenSpan = styled.span` + color: ${colors.GREEN_PRIMARY}; +`; + +const AdvancedSettingsWrapper = styled.div` + padding: 20px 0; + display: flex; + flex-direction: column; + justify-content: space-between; + + border-top: 1px solid ${colors.DIVIDER}; +`; + +const GasInputRow = styled(InputRow)` + width: 100%; + display: flex; +`; + +const GasInput = styled(Input)` + &:first-child { + padding-right: 20px; + } +`; + +const StyledTextarea = styled(Textarea)` + margin-bottom: 20px; + height: 80px; +`; + +const AdvancedSettingsSendButtonWrapper = styled.div` + width: 100%; + display: flex; + justify-content: flex-end; +`; + +const getGasLimitInputState = (gasLimitErrors: string, gasLimitWarnings: string): string => { + let state = ''; + if (gasLimitWarnings && !gasLimitErrors) { + state = 'warning'; + } + if (gasLimitErrors) { + state = 'error'; + } + return state; +}; + +const getGasPriceInputState = (gasPriceErrors: string, gasPriceWarnings: string): string => { + let state = ''; + if (gasPriceWarnings && !gasPriceErrors) { + state = 'warning'; + } + if (gasPriceErrors) { + state = 'error'; + } + return state; +}; + +// stateless component +const AdvancedForm = (props: Props) => { + const { + network, + } = props.selectedAccount; + if (!network) return null; + const { + networkSymbol, + currency, + recommendedGasPrice, + errors, + warnings, + infos, + data, + gasLimit, + gasPrice, + } = props.sendForm; + const { + onGasLimitChange, + onGasPriceChange, + onDataChange, + } = props.sendFormActions; + + let gasLimitTooltipCurrency: string; + let gasLimitTooltipValue: string; + if (networkSymbol !== currency) { + gasLimitTooltipCurrency = 'tokens'; + gasLimitTooltipValue = network.defaultGasLimitTokens.toString(10); + } else { + gasLimitTooltipCurrency = networkSymbol; + gasLimitTooltipValue = network.defaultGasLimit.toString(10); + } + + return ( + + + + Gas limit + + Gas limit is the amount of gas to send with your transaction.
+ TX fee = gas price * gas limit & is paid to miners for including your TX in a block.
+ Increasing this number will not get your TX mined faster.
+ Default value for sending {gasLimitTooltipCurrency} is {gasLimitTooltipValue} + + )} + placement="top" + > + +
+ + )} + bottomText={errors.gasLimit || warnings.gasLimit || infos.gasLimit} + value={gasLimit} + isDisabled={networkSymbol === currency && data.length > 0} + onChange={event => onGasLimitChange(event.target.value)} + /> + + + Gas price + + Gas Price is the amount you pay per unit of gas.
+ TX fee = gas price * gas limit & is paid to miners for including your TX in a block.
+ Higher the gas price = faster transaction, but more expensive. Recommended is {recommendedGasPrice} GWEI.
+ Read more + + )} + placement="top" + > + +
+ + )} + bottomText={errors.gasPrice || warnings.gasPrice || infos.gasPrice} + value={gasPrice} + onChange={event => onGasPriceChange(event.target.value)} + /> +
+ + + Data + + Data is usually used when you send transactions to contracts. + + )} + placement="top" + > + + + + )} + bottomText={errors.data || warnings.data || infos.data} + disabled={networkSymbol !== currency} + value={networkSymbol !== currency ? '' : data} + onChange={event => onDataChange(event.target.value)} + /> + + + { props.children } + +
+ ); +}; + +export default AdvancedForm; \ No newline at end of file diff --git a/src/views/Wallet/views/AccountSend/index.js b/src/views/Wallet/views/AccountSend/index.js index c2318e72..64863ab3 100644 --- a/src/views/Wallet/views/AccountSend/index.js +++ b/src/views/Wallet/views/AccountSend/index.js @@ -1,6 +1,6 @@ /* @flow */ -import React, { Component } from 'react'; +import React from 'react'; import styled, { css } from 'styled-components'; import { Select } from 'components/Select'; import Button from 'components/Button'; @@ -12,11 +12,9 @@ import { FONT_SIZE, FONT_WEIGHT, TRANSITION } from 'config/variables'; import colors from 'config/colors'; import P from 'components/Paragraph'; import { H2 } from 'components/Heading'; -import Textarea from 'components/Textarea'; -import Tooltip from 'components/Tooltip'; -import { calculate, validation } from 'actions/SendFormActions'; import SelectedAccount from 'views/Wallet/components/SelectedAccount'; import type { Token } from 'flowtype'; +import AdvancedForm from './components/Advanced'; import PendingTransactions from './components/PendingTransactions'; import type { Props } from './Container'; @@ -25,11 +23,6 @@ import type { Props } from './Container'; // and put it inside config/variables.js const SmallScreenWidth = '850px'; -type State = { - isAdvancedSettingsHidden: boolean, - shouldAnimateAdvancedSettingsToggle: boolean, -}; - const Wrapper = styled.section` padding: 0 48px; `; @@ -143,453 +136,255 @@ const SendButton = styled(Button)` } `; -const AdvancedSettingsWrapper = styled.div` - padding: 20px 0; - display: flex; - flex-direction: column; - justify-content: space-between; - - border-top: 1px solid ${colors.DIVIDER}; -`; - -const GasInputRow = styled(InputRow)` - width: 100%; - display: flex; -`; - -const GasInput = styled(Input)` - &:first-child { - padding-right: 20px; - } -`; - -const AdvancedSettingsSendButtonWrapper = styled.div` - width: 100%; - display: flex; - justify-content: flex-end; -`; - -const StyledTextarea = styled(Textarea)` - margin-bottom: 20px; - height: 80px; -`; - const AdvancedSettingsIcon = styled(Icon)` margin-left: 10px; `; -const GreenSpan = styled.span` - color: ${colors.GREEN_PRIMARY}; -`; +// render helpers +const getAddressInputState = (address: string, addressErrors: string, addressWarnings: string): string => { + let state = ''; + if (address && !addressErrors) { + state = 'success'; + } + if (addressWarnings && !addressErrors) { + state = 'warning'; + } + if (addressErrors) { + state = 'error'; + } + return state; +}; -const InputLabelWrapper = styled.div` - display: flex; - align-items: center; -`; +const getAmountInputState = (amountErrors: string, amountWarnings: string): string => { + let state = ''; + if (amountWarnings && !amountErrors) { + state = 'warning'; + } + if (amountErrors) { + state = 'error'; + } + return state; +}; -class AccountSend extends Component { - constructor(props: Props) { - super(props); - this.state = { - isAdvancedSettingsHidden: true, - shouldAnimateAdvancedSettingsToggle: false, - }; +const getTokensSelectData = (tokens: Array, accountNetwork: any): Array<{ value: string, label: string }> => { + const tokensSelectData: Array<{ value: string, label: string }> = tokens.map(t => ({ value: t.symbol, label: t.symbol })); + tokensSelectData.unshift({ value: accountNetwork.symbol, label: accountNetwork.symbol }); + + return tokensSelectData; +}; + +// stateless component +const AccountSend = (props: Props) => { + const device = props.wallet.selectedDevice; + const { + account, + network, + discovery, + tokens, + } = props.selectedAccount; + const { + address, + amount, + setMax, + networkSymbol, + currency, + feeLevels, + selectedFeeLevel, + gasPriceNeedsUpdate, + total, + errors, + warnings, + infos, + data, + sending, + advanced, + } = props.sendForm; + + const { + toggleAdvanced, + onAddressChange, + onAmountChange, + onSetMax, + onCurrencyChange, + onFeeLevelChange, + updateFeeLevels, + onSend, + } = props.sendFormActions; + + if (!device || !account || !discovery || !network) return null; + + let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending; + let sendButtonText: string = 'Send'; + if (networkSymbol !== currency && amount.length > 0 && !errors.amount) { + sendButtonText += ` ${amount} ${currency.toUpperCase()}`; + } else if (networkSymbol === currency && total !== '0') { + sendButtonText += ` ${total} ${network.symbol}`; } - componentWillReceiveProps(newProps: Props) { - calculate(this.props, newProps); - validation(newProps); + if (!device.connected) { + sendButtonText = 'Device is not connected'; + isSendButtonDisabled = true; + } else if (!device.available) { + sendButtonText = 'Device is unavailable'; + isSendButtonDisabled = true; + } else if (!discovery.completed) { + sendButtonText = 'Loading accounts'; + isSendButtonDisabled = true; } - getAddressInputState(address: string, addressErrors: string, addressWarnings: string) { - let state = ''; - if (address && !addressErrors) { - state = 'success'; - } - if (addressWarnings && !addressErrors) { - state = 'warning'; - } - if (addressErrors) { - state = 'error'; - } - return state; - } + const tokensSelectData = getTokensSelectData(tokens, network); + const isAdvancedSettingsHidden = !advanced; + // eslint workaround (is this some bug?) + // if i put {true} directly to "AdvancedSettingsIcon" component + // i get eslint error + const advancedButtonCanAnimate = true; - getAmountInputState(amountErrors: string, amountWarnings: string) { - let state = ''; - if (amountWarnings && !amountErrors) { - state = 'warning'; - } - if (amountErrors) { - state = 'error'; - } - return state; - } + return ( + + + Send Ethereum or tokens + + onAddressChange(event.target.value)} + /> + - getGasLimitInputState(gasLimitErrors: string, gasLimitWarnings: string) { - let state = ''; - if (gasLimitWarnings && !gasLimitErrors) { - state = 'warning'; - } - if (gasLimitErrors) { - state = 'error'; - } - return state; - } - - getGasPriceInputState(gasPriceErrors: string, gasPriceWarnings: string) { - let state = ''; - if (gasPriceWarnings && !gasPriceErrors) { - state = 'warning'; - } - if (gasPriceErrors) { - state = 'error'; - } - return state; - } - - getTokensSelectData(tokens: Array, accountNetwork: any) { - const tokensSelectData: Array<{ value: string, label: string }> = tokens.map(t => ({ value: t.symbol, label: t.symbol })); - tokensSelectData.unshift({ value: accountNetwork.symbol, label: accountNetwork.symbol }); - - return tokensSelectData; - } - - handleToggleAdvancedSettingsButton() { - this.toggleAdvancedSettings(); - } - - toggleAdvancedSettings() { - this.setState(previousState => ({ - isAdvancedSettingsHidden: !previousState.isAdvancedSettingsHidden, - shouldAnimateAdvancedSettingsToggle: true, - })); - } - - render() { - const device = this.props.wallet.selectedDevice; - const { - account, - network, - discovery, - tokens, - } = this.props.selectedAccount; - const { - address, - amount, - setMax, - networkSymbol, - currency, - feeLevels, - selectedFeeLevel, - recommendedGasPrice, - gasPriceNeedsUpdate, - total, - errors, - warnings, - infos, - data, - sending, - gasLimit, - gasPrice, - } = this.props.sendForm; - - const { - onAddressChange, - onAmountChange, - onSetMax, - onCurrencyChange, - onFeeLevelChange, - updateFeeLevels, - onSend, - onGasLimitChange, - onGasPriceChange, - onDataChange, - } = this.props.sendFormActions; - - if (!device || !account || !discovery || !network) return null; - - let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending; - let sendButtonText: string = 'Send'; - if (networkSymbol !== currency && amount.length > 0 && !errors.amount) { - sendButtonText += ` ${amount} ${currency.toUpperCase()}`; - } else if (networkSymbol === currency && total !== '0') { - sendButtonText += ` ${total} ${network.symbol}`; - } - - if (!device.connected) { - sendButtonText = 'Device is not connected'; - isSendButtonDisabled = true; - } else if (!device.available) { - sendButtonText = 'Device is unavailable'; - isSendButtonDisabled = true; - } else if (!discovery.completed) { - sendButtonText = 'Loading accounts'; - isSendButtonDisabled = true; - } - - const tokensSelectData = this.getTokensSelectData(tokens, network); - - let gasLimitTooltipCurrency: string; - let gasLimitTooltipValue: string; - if (networkSymbol !== currency) { - gasLimitTooltipCurrency = 'tokens'; - gasLimitTooltipValue = network.defaultGasLimitTokens.toString(10); - } else { - gasLimitTooltipCurrency = networkSymbol; - gasLimitTooltipValue = network.defaultGasLimit.toString(10); - } - - - return ( - - - Send Ethereum or tokens - - onAddressChange(event.target.value)} - /> - - - - onAmountChange(event.target.value)} - bottomText={errors.amount || warnings.amount || infos.amount} - sideAddons={[ - ( - onSetMax()} - isActive={setMax} - > - {!setMax && ( - - )} - {setMax && ( - - )} - Set max - - ), - ( - - ), - ]} - /> - - - - - Fee - {gasPriceNeedsUpdate && ( - - - Recommended fees updated. Click here to use them - - )} - - onAmountChange(event.target.value)} + bottomText={errors.amount || warnings.amount || infos.amount} + sideAddons={[ + ( + onSetMax()} + isActive={setMax} > - {sendButtonText} - - - - )} + {!setMax && ( + + )} + {setMax && ( + + )} + Set max + + ), + ( + + ), + ]} + /> + - {this.props.selectedAccount.pending.length > 0 && ( - + + Fee + {gasPriceNeedsUpdate && ( + + + Recommended fees updated. Click here to use them + + )} + +