// @flow import React, { Fragment, PureComponent } from 'react'; import styled, { keyframes } from 'styled-components'; import { BigNumber } from 'bignumber.js'; import { Transition, animated } from 'react-spring'; import { FEES } from '../constants/fees'; import { InputLabelComponent } from '../components/input-label'; import { InputComponent } from '../components/input'; import { TextComponent } from '../components/text'; import { SelectComponent } from '../components/select'; import { RowComponent } from '../components/row'; import { ColumnComponent } from '../components/column'; import { Divider } from '../components/divider'; import { Button } from '../components/button'; import { ConfirmDialogComponent } from '../components/confirm-dialog'; import { formatNumber } from '../utils/format-number'; import type { SendTransactionInput } from '../containers/send'; import type { State as SendState } from '../redux/modules/send'; import SentIcon from '../assets/images/transaction_sent_icon.svg'; import MenuIcon from '../assets/images/menu_icon.svg'; import ValidIcon from '../assets/images/green_check.png'; import InvalidIcon from '../assets/images/error_icon.png'; import LoadingIcon from '../assets/images/sync_icon.png'; import theme from '../theme'; const rotate = keyframes` from { transform: rotate(0deg); } to { transform: rotate(360deg); } `; const Loader = styled.img` width: 25px; height: 25px; animation: 2s linear infinite; animation-name: ${rotate}; margin-bottom: 10px; `; const FormWrapper = styled.div` margin-top: ${props => props.theme.layoutContentPaddingTop}; width: 71%; `; const SendWrapper = styled(ColumnComponent)` width: 25%; margin-top: 60px; `; type AmountProps = | { isEmpty: boolean, } | Object; const AmountWrapper = styled.div` width: 100%; position: relative; &:before { content: 'ZEC'; font-family: ${props => props.theme.fontFamily}; position: absolute; margin-top: 15px; margin-left: 15px; display: block; transition: all 0.05s ease-in-out; opacity: ${(props: AmountProps) => (props.isEmpty ? '0' : '1')}; color: #fff; z-index: 10; } `; const AmountInput = styled(InputComponent)` padding-left: ${(props: AmountProps) => (props.isEmpty ? '15' : '50')}px; `; const ShowFeeButton = styled.button` background: none; border: none; cursor: pointer; width: 100%; color: ${props => props.theme.colors.text}; outline: none; display: flex; align-items: center; margin: 30px 0; opacity: 0.7; &:hover { opacity: 1; } `; const SeeMoreIcon = styled.img` width: 25px; height: 25px; border: 1px solid ${props => props.theme.colors.text}; border-radius: 100%; margin-right: 11.5px; `; const FeeWrapper = styled.div` background-color: #000; border-radius: 4px; padding: 13px 19px; margin-bottom: 20px; `; const InfoCard = styled.div` width: 100%; background-color: ${props => props.theme.colors.cardBackgroundColor}; border-radius: ${props => props.theme.boxBorderRadius}; `; const InfoContent = styled.div` padding: 15px; `; const InfoCardLabel = styled(TextComponent)` opacity: 0.5; margin-bottom: 10px; `; const InfoCardUSD = styled(TextComponent)` opacity: 0.5; margin-top: 2.5px; `; const FormButton = styled(Button)` width: 100%; margin: 10px 0; `; const ModalContent = styled(ColumnComponent)` min-height: 400px; align-items: center; justify-content: center; p { word-break: break-all; } `; const ConfirmItemWrapper = styled(RowComponent)` padding: 22.5px 33.5px; width: 100%; `; type ItemLabelProps = | { color: string, } | Object; /* eslint-disable max-len */ const ItemLabel = styled(TextComponent)` font-weight: ${(props: PropsWithTheme) => String(props.theme.fontWeight.bold)}; font-size: ${(props: PropsWithTheme) => String(props.theme.fontSize.small)}; color: ${(props: PropsWithTheme) => props.color || props.theme.colors.modalItemLabel}; margin-bottom: 3.5px; `; const SendZECValue = styled(TextComponent)` color: ${props => props.theme.colors.transactionSent}; font-size: ${props => `${props.theme.fontSize.large}em`}; font-weight: ${props => String(props.theme.fontWeight.bold)}; `; const SendUSDValue = styled(TextComponent)` opacity: 0.5; font-weight: ${props => String(props.theme.fontWeight.light)}; font-size: ${props => `${props.theme.fontSize.medium}em`}; `; const Icon = styled.img` width: 35px; height: 35px; margin-left: 15px; `; const ValidateStatusIcon = styled.img` width: 13px; height: 13px; margin-right: 7px; `; const RevealsMain = styled.div` width: 100%; height: 100%; position: relative; display: flex; align-items: flex-start; justify-content: flex-start; & > div { top: 0; right: 0; left: 0; } `; type Props = { ...SendState, balance: number, zecPrice: number, addresses: string[], sendTransaction: SendTransactionInput => void, loadAddresses: () => void, resetSendView: () => void, validateAddress: ({ address: string }) => void, loadAddresses: () => void, loadZECPrice: () => void, }; type State = { showFee: boolean, from: string, amount: string, to: string, feeType: string | number, fee: number | null, memo: string, }; const initialState = { showFee: false, from: '', amount: '', to: '', feeType: FEES.LOW, fee: FEES.LOW, memo: '', }; export class SendView extends PureComponent { state = initialState; componentDidMount() { const { resetSendView, loadAddresses, loadZECPrice } = this.props; resetSendView(); loadAddresses(); loadZECPrice(); } handleChange = (field: string) => (value: string) => { const { validateAddress } = this.props; if (field === 'to') { // eslint-disable-next-line max-len this.setState(() => ({ [field]: value }), () => validateAddress({ address: value })); } else { this.setState(() => ({ [field]: value })); } }; handleChangeFeeType = (value: string) => { if (value === FEES.CUSTOM) { this.setState(() => ({ feeType: FEES.CUSTOM, fee: null, })); } else { const fee = new BigNumber(value); this.setState(() => ({ feeType: fee.toString(), fee: fee.toNumber(), })); } }; handleSubmit = () => { const { from, amount, to, memo, fee, } = this.state; const { sendTransaction } = this.props; if (!from || !amount || !to || !fee) return; sendTransaction({ from, to, amount, fee, memo, }); }; showModal = (toggle: void => void) => { const { from, amount, to, fee, } = this.state; // eslint-disable-next-line react/prop-types const { isToAddressValid } = this.props; if (!from || !amount || !to || !fee || !isToAddressValid) return; toggle(); }; reset = () => { const { resetSendView } = this.props; this.setState(initialState, () => resetSendView()); }; getFeeText = () => { const { fee } = this.state; if (!fee) return '0.0'; const feeValue = new BigNumber(fee); if (feeValue.isEqualTo(FEES.LOW)) return `Low ZEC ${feeValue.toString()}`; // eslint-disable-next-line max-len if (feeValue.isEqualTo(FEES.MEDIUM)) return `Medium ZEC ${feeValue.toString()}`; if (feeValue.isEqualTo(FEES.HIGH)) return `High ZEC ${feeValue.toString()}`; return `Custom ZEC ${feeValue.toString()}`; }; renderValidationStatus = () => { const { isToAddressValid } = this.props; return isToAddressValid ? ( ) : ( ); }; renderModalContent = ({ valueSent, valueSentInUsd, toggle, }: { /* eslint-disable react/no-unused-prop-types */ valueSent: string, valueSentInUsd: string, toggle: () => void, /* eslint-enable react/no-unused-prop-types */ }) => { // eslint-disable-next-line react/prop-types const { operationId, isSending, error } = this.props; const { from, to } = this.state; if (isSending) { return ( ); } if (operationId) { return ( ); } if (error) return ; return ( <> ); }; render() { const { addresses, balance, zecPrice, isSending, error, operationId, } = this.props; const { showFee, from, amount, to, memo, fee, feeType, } = this.state; const isEmpty = amount === ''; const fixedAmount = isEmpty ? 0.0 : amount; const zecBalance = formatNumber({ value: balance, append: 'ZEC ' }); const zecBalanceInUsd = formatNumber({ value: new BigNumber(balance).times(zecPrice).toNumber(), append: 'USD $', }); const valueSent = formatNumber({ value: new BigNumber(fixedAmount).toFormat(2), append: 'ZEC ', }); const valueSentInUsd = formatNumber({ value: new BigNumber(amount).times(zecPrice).toNumber(), append: 'USD $', }); return ( ({ value: addr, label: addr }))} /> null} name='to' /> this.setState(state => ({ showFee: !state.showFee, })) } > {(show: boolean) => show && ((props: Object) => ( ({ label: cur.toLowerCase(), value: String(FEES[cur]), }))} /> )) } {feeType === FEES.CUSTOM && ( )} ( this.showModal(toggle)} id='send-submit-button' label='Send' variant='secondary' focused isFluid disabled={!from || !amount || !to || !fee} /> )} > {toggle => ( {this.renderModalContent({ valueSent, valueSentInUsd, toggle, })} )} ); } }