diff --git a/src/views/Wallet/views/AccountSend/index.js b/src/views/Wallet/views/AccountSend/index.js index 7d8b47d8..cf93ccba 100644 --- a/src/views/Wallet/views/AccountSend/index.js +++ b/src/views/Wallet/views/AccountSend/index.js @@ -1,19 +1,178 @@ /* @flow */ import React, { Component } from 'react'; -import styled from 'styled-components'; -import Select from 'react-select'; +import styled, { css } from 'styled-components'; +import { Select } from 'components/Select'; +import Button from 'components/Button'; +import Input from 'components/inputs/Input'; +import Icon from 'components/Icon'; +import Link from 'components/Link'; +import ICONS from 'config/icons'; +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 'rc-tooltip'; +import TooltipContent from 'components/TooltipContent'; import { calculate, validation } from 'actions/SendFormActions'; import SelectedAccount from 'views/Wallet/components/SelectedAccount'; import type { Token } from 'flowtype'; -import AdvancedForm from './components/AdvancedForm'; import PendingTransactions from './components/PendingTransactions'; -import { FeeSelectValue, FeeSelectOption } from './components/FeeSelect'; import type { Props } from './Container'; -export default class AccountSendContainer extends Component { +type State = { + isAdvancedSettingsHidden: boolean, + shouldAnimateAdvancedSettingsToggle: boolean, +}; + +const Wrapper = styled.section` + padding: 0 48px; +`; + +const StyledH2 = styled(H2)` + padding: 20px 0; +`; + +const InputRow = styled.div` + margin-bottom: 20px; +`; + +const SetMaxAmountButton = styled(Button)` + height: 34px; + padding: 0 10px; + display: flex; + align-items: center; + justify-content: center; + + font-size: ${FONT_SIZE.SMALLER}; + font-weight: ${FONT_WEIGHT.SMALLEST}; + color: ${colors.TEXT_SECONDARY}; + + border-radius: 0; + border: 1px solid ${colors.DIVIDER}; + border-right: 0; + border-left: 0; + background: transparent; + transition: ${TRANSITION.HOVER}; + + &:hover { + background: ${colors.GRAY_LIGHT}; + } + + ${props => props.isActive && css` + color: ${colors.WHITE}; + background: ${colors.GREEN_PRIMARY}; + border-color: ${colors.GREEN_PRIMARY}; + + &:hover { + background: ${colors.GREEN_SECONDARY}; + } + + &:active { + background: ${colors.GREEN_TERTIARY}; + } + `} +`; + +const CurrencySelect = styled(Select)` + height: 34px; + flex: 0.2; +`; + +const FeeOptionWrapper = styled.div` + display: flex; + justify-content: space-between; +`; + +const FeeLabelWrapper = styled.div` + display: flex; + align-items: center; + margin-bottom: 4px; +`; + +const FeeLabel = styled.span` + color: ${colors.TEXT_SECONDARY}; +`; + +const UpdateFeeWrapper = styled.span` + margin-left: 8px; + display: flex; + align-items: center; + font-size: ${FONT_SIZE.SMALLER}; + color: ${colors.WARNING_PRIMARY}; +`; + +const StyledLink = styled(Link)` + margin-left: 4px; +`; + +const ToggleAdvancedSettingsWrapper = styled.div` + margin-bottom: 20px; + display: flex; + justify-content: space-between; +`; + +const ToggleAdvancedSettingsButton = styled(Button)` + padding: 0; + display: flex; + align-items: center; + font-weight: ${FONT_WEIGHT.BIGGER}; +`; + +const SendButton = styled(Button)` + min-width: 50%; +`; + +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}; +`; + +class AccountSend extends Component { + constructor(props: Props) { + super(props); + this.state = { + isAdvancedSettingsHidden: true, + shouldAnimateAdvancedSettingsToggle: false, + }; + } + componentWillReceiveProps(newProps: Props) { calculate(this.props, newProps); validation(newProps); @@ -21,184 +180,393 @@ export default class AccountSendContainer extends Component { this.props.saveSessionStorage(); } + getAddressInputState(address: string, addressErrors: string, addressWarnings: string) { + let state = ''; + if (address && !addressErrors) { + state = 'success'; + } else if (addressWarnings && !addressErrors) { + state = 'warning'; + } else if (addressErrors) { + state = 'error'; + } + return state; + } + + getAmountInputState(amountErrors: string, amountWarnings: string) { + let state = ''; + if (amountWarnings && !amountErrors) { + state = 'warning'; + } else if (amountErrors) { + state = 'error'; + } + return state; + } + + getAmountInputBottomText(amountErrors: string, amountWarnings: string) { + let text = ''; + if (amountWarnings && !amountErrors) { + text = amountWarnings; + } else if (amountErrors) { + text = amountErrors; + } + return text; + } + + getGasLimitInputState(gasLimitErrors: string, gasLimitWarnings: string) { + let state = ''; + if (gasLimitWarnings && !gasLimitErrors) { + state = 'warning'; + } else if (gasLimitErrors) { + state = 'error'; + } + return state; + } + + getGasPriceInputState(gasPriceErrors: string, gasPriceWarnings: string) { + let state = ''; + if (gasPriceWarnings && !gasPriceErrors) { + state = 'warning'; + } else if (gasPriceErrors) { + state = 'error'; + } + return state; + } + + getTokensSelectData(tokens: Array, accountNetwork: any) { + const tokensSelectData = 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, + 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={this.getAmountInputBottomText(errors.amount, warnings.amount)} + sideAddons={[ + ( + onSetMax()} + isActive={setMax} + > + {!setMax && ( + + )} + {setMax && ( + + )} + Set max + + ), + ( + + ), + ]} + /> + + + + + Fee + {gasPriceNeedsUpdate && ( + + + Recommended fees updated. Click here to use them + + )} + + onAddressChange(event.target.value)} - /> - - { errors.address ? ({ errors.address }) : null } - { warnings.address ? ({ warnings.address }) : null } - { infos.address ? ({ infos.address }) : null } - - -
- -
- onAmountChange(event.target.value)} - /> - - Set max - - 0} - optionClassName="fee-option" - options={feeLevels} - /> -
- - - - - - - - - ); -}; +export default AccountSend;