/* @flow */ import React, { useState } from 'react'; import BigNumber from 'bignumber.js'; import styled, { css } from 'styled-components'; import { Select, Button, Input, Link, Icon, P, colors, icons as ICONS } from 'trezor-ui-components'; import { FONT_SIZE, FONT_WEIGHT, SCREEN_SIZE } from 'config/variables'; import { FIAT_CURRENCIES } from 'config/app'; import Title from 'views/Wallet/components/Title'; import Content from 'views/Wallet/components/Content'; import * as stateUtils from 'reducers/utils'; import type { Token } from 'flowtype'; import { FormattedMessage } from 'react-intl'; import l10nCommonMessages from 'views/common.messages'; import { getBottomText } from 'utils/uiUtils'; import AdvancedForm from './components/AdvancedForm'; import PendingTransactions from '../components/PendingTransactions'; import l10nMessages from './index.messages'; import l10nSendMessages from '../../common.messages'; import type { Props } from './Container'; const AmountInputLabelWrapper = styled.div` display: flex; justify-content: space-between; `; const AmountInputLabel = styled.span` text-align: right; color: ${colors.TEXT_SECONDARY}; `; const InputRow = styled.div` padding-bottom: 28px; `; const LocalAmountWrapper = styled.div` display: flex; align-self: flex-start; margin-top: 26px; @media screen and (max-width: ${SCREEN_SIZE.MD}) { flex: 1 0 100%; justify-content: flex-end; margin-top: 0px; padding-top: 28px; } `; const AmountRow = styled(InputRow)` display: flex; align-items: flex-end; padding-bottom: 28px; @media screen and (max-width: ${SCREEN_SIZE.MD}) { flex-wrap: wrap; } `; const LocalAmountInput = styled(Input)` @media screen and (max-width: ${SCREEN_SIZE.MD}) { flex: 1 1 100%; } `; const LocalCurrencySelect = styled(Select)` min-width: 77px; height: 40px; @media screen and (max-width: ${SCREEN_SIZE.MD}) { flex: 1 1 0; } `; const SetMaxAmountButton = styled(Button)` padding: 0 10px; font-size: ${FONT_SIZE.SMALL}; transition: all 0s; border-radius: 0; border-right: 0; border-left: 0; `; const CurrencySelect = styled(Select)` min-width: 77px; height: 40px; flex: 0.2; `; const FeeOptionWrapper = styled.div` display: flex; justify-content: space-between; `; const OptionValue = styled(P)` flex: 1 0 auto; min-width: 70px; margin-right: 5px; `; const OptionLabel = styled(P)` flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; text-align: right; word-break: break-all; `; const FeeLabelWrapper = styled.div` display: flex; align-items: center; padding-bottom: 10px; `; const FeeLabel = styled.span` color: ${colors.TEXT_SECONDARY}; `; const UpdateFeeWrapper = styled.span` margin-left: 8px; display: flex; align-items: center; font-size: ${FONT_SIZE.SMALL}; color: ${colors.WARNING_PRIMARY}; `; const StyledLink = styled(Link)` margin-left: 4px; white-space: nowrap; `; const ToggleAdvancedSettingsWrapper = styled.div` min-height: 40px; margin-bottom: 20px; display: flex; flex-direction: row; justify-content: space-between; @media screen and (max-width: ${SCREEN_SIZE.MD}) { ${props => props.isAdvancedSettingsHidden && css` flex-direction: column; `} } `; const ToggleAdvancedSettingsButton = styled(Button)` min-height: 40px; padding: 0; display: flex; flex: 1 1 0; align-items: center; font-weight: ${FONT_WEIGHT.MEDIUM}; justify-content: flex-start; `; const FormButtons = styled.div` display: flex; flex: 1 1; @media screen and (max-width: ${SCREEN_SIZE.MD}) { margin-top: ${props => (props.isAdvancedSettingsHidden ? '10px' : 0)}; } button + button { margin-left: 5px; } `; const SendButton = styled(Button)` word-break: break-all; flex: 1; `; const ClearButton = styled(Button)``; const AdvancedSettingsIcon = styled(Icon)` margin-left: 10px; margin-right: 6px; `; const QrButton = styled(Button)` border-top-left-radius: 0px; border-bottom-left-radius: 0px; border-left: 0px; height: 40px; padding: 0 10px; `; const EqualsSign = styled.div` align-self: center; padding: 0 10px; font-size: ${FONT_SIZE.BIGGER}; @media screen and (max-width: ${SCREEN_SIZE.MD}) { display: none; } `; const StyledIcon = styled(Icon)` margin-right: 6px; `; // render helpers const getAddressInputState = ( address: string, addressErrors: boolean, addressWarnings: boolean, domainResolving: boolean ): ?string => { let state = null; if (address && !addressErrors) { state = 'success'; } if ((addressWarnings && !addressErrors) || domainResolving) { state = 'warning'; } if (addressErrors) { state = 'error'; } return state; }; const getAmountInputState = (amountErrors: boolean, amountWarnings: boolean): ?string => { let state = null; if (amountWarnings && !amountErrors) { state = 'warning'; } if (amountErrors) { state = 'error'; } return state; }; 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; }; const buildCurrencyOption = currency => { return { value: currency, label: currency.toUpperCase() }; }; // stateless component const AccountSend = (props: Props) => { const device = props.wallet.selectedDevice; const { account, network, discovery, tokens, shouldRender } = props.selectedAccount; const { address, amount, localAmount, setMax, networkSymbol, currency, localCurrency, feeLevels, selectedFeeLevel, gasPriceNeedsUpdate, total, errors, warnings, infos, sending, advanced, domainResolving, } = props.sendForm; const { toggleAdvanced, onAddressChange, onAmountChange, onLocalAmountChange, onSetMax, onCurrencyChange, onLocalCurrencyChange, onFeeLevelChange, updateFeeLevels, onSend, onClear, } = props.sendFormActions; const [touched, setTouched] = useState(false); if (!device || !account || !discovery || !network || !shouldRender) { const { loader, exceptionPage } = props.selectedAccount; return ; } const isCurrentCurrencyToken = networkSymbol !== currency; let selectedTokenBalance = '0'; const selectedToken = tokens.find(t => t.symbol === currency); if (selectedToken) { const pendingAmount: BigNumber = stateUtils.getPendingAmount( props.selectedAccount.pending, selectedToken.symbol, true ); selectedTokenBalance = new BigNumber(selectedToken.balance) .minus(pendingAmount) .toString(10); } let isSendButtonDisabled: boolean = Object.keys(errors).length > 0 || total === '0' || amount.length === 0 || address.length === 0 || sending || domainResolving || account.imported; let amountText = ''; if (networkSymbol !== currency && amount.length > 0 && !errors.amount) { amountText = `${amount} ${currency.toUpperCase()}`; } else if (networkSymbol === currency && total !== '0') { amountText = `${total} ${network.symbol}`; } let sendButtonText = ( ); if (!device.connected) { sendButtonText = ; isSendButtonDisabled = true; } else if (!device.available) { sendButtonText = ; isSendButtonDisabled = true; } else if (!discovery.completed) { sendButtonText = ; isSendButtonDisabled = true; } const tokensSelectData = getTokensSelectData(tokens, network); const tokensSelectValue = tokensSelectData.find(t => t.value === currency); const isAdvancedSettingsHidden = !advanced; return ( <FormattedMessage {...l10nMessages.TR_SEND_ETHEREUM_OR_TOKENS} /> onAddressChange(event.target.value)} sideAddons={[ , ]} /> {isCurrentCurrencyToken && selectedToken && !props.wallet.hideBalance && ( )} } value={amount} onChange={event => onAmountChange(event.target.value)} bottomText={getBottomText(errors.amount, warnings.amount, infos.amount)} sideAddons={[ onSetMax()} isWhite={!setMax}> {!setMax && ( )} {setMax && ( )} , , ]} /> = onLocalAmountChange(event.target.value)} sideAddons={[ onLocalCurrencyChange(option)} value={buildCurrencyOption(localCurrency)} options={FIAT_CURRENCIES.map(c => buildCurrencyOption(c))} />, ]} /> {gasPriceNeedsUpdate && ( {' '} )}