2018-11-23 15:23:41 -08:00
|
|
|
// @flow
|
2019-02-04 20:41:45 -08:00
|
|
|
|
2019-01-23 21:08:20 -08:00
|
|
|
import React, { Fragment, PureComponent } from 'react';
|
2019-02-18 09:25:16 -08:00
|
|
|
import styled, { withTheme, keyframes } from 'styled-components';
|
2019-01-09 05:14:02 -08:00
|
|
|
import { BigNumber } from 'bignumber.js';
|
2019-01-23 21:08:20 -08:00
|
|
|
import { Transition, animated } from 'react-spring';
|
2019-02-14 14:24:38 -08:00
|
|
|
import { type Match } from 'react-router-dom';
|
2018-11-23 15:23:41 -08:00
|
|
|
|
2019-01-29 07:36:13 -08:00
|
|
|
import { FEES } from '../constants/fees';
|
2019-02-18 09:25:16 -08:00
|
|
|
import { DARK } from '../constants/themes';
|
2018-12-20 09:18:45 -08:00
|
|
|
|
2018-12-19 13:41:43 -08:00
|
|
|
import { InputLabelComponent } from '../components/input-label';
|
|
|
|
import { InputComponent } from '../components/input';
|
|
|
|
import { TextComponent } from '../components/text';
|
|
|
|
import { SelectComponent } from '../components/select';
|
2018-12-20 06:10:44 -08:00
|
|
|
import { RowComponent } from '../components/row';
|
|
|
|
import { ColumnComponent } from '../components/column';
|
|
|
|
import { Divider } from '../components/divider';
|
|
|
|
import { Button } from '../components/button';
|
2019-01-21 08:29:06 -08:00
|
|
|
import { ConfirmDialogComponent } from '../components/confirm-dialog';
|
2018-11-23 15:23:41 -08:00
|
|
|
|
2019-01-29 07:36:13 -08:00
|
|
|
import { formatNumber } from '../utils/format-number';
|
2019-02-08 19:51:34 -08:00
|
|
|
import { ascii2hex } from '../utils/ascii-to-hexadecimal';
|
2018-12-20 11:00:35 -08:00
|
|
|
|
2019-02-16 19:34:12 -08:00
|
|
|
import SentIcon from '../assets/images/transaction_sent_icon_dark.svg';
|
2019-02-18 09:25:16 -08:00
|
|
|
import MenuIconDark from '../assets/images/menu_icon_dark.svg';
|
|
|
|
import MenuIconLight from '../assets/images/menu_icon_light.svg';
|
2019-02-16 19:34:12 -08:00
|
|
|
import ValidIcon from '../assets/images/green_check_dark.png';
|
|
|
|
import InvalidIcon from '../assets/images/error_icon_dark.png';
|
|
|
|
import LoadingIcon from '../assets/images/sync_icon_dark.png';
|
2019-02-18 18:22:38 -08:00
|
|
|
import ArrowUpIconDark from '../assets/images/arrow_up_dark.png';
|
|
|
|
import ArrowUpIconLight from '../assets/images/arrow_up_light.png';
|
2018-12-21 07:44:29 -08:00
|
|
|
|
2019-02-19 08:03:27 -08:00
|
|
|
import type { MapDispatchToProps, MapStateToProps } from '../containers/send';
|
2019-01-21 08:29:06 -08:00
|
|
|
|
2019-01-21 13:42:50 -08:00
|
|
|
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;
|
|
|
|
`;
|
|
|
|
|
2018-12-20 06:10:44 -08:00
|
|
|
const FormWrapper = styled.div`
|
2019-01-10 19:30:46 -08:00
|
|
|
width: 71%;
|
2018-12-20 06:10:44 -08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const SendWrapper = styled(ColumnComponent)`
|
2019-01-10 19:30:46 -08:00
|
|
|
width: 25%;
|
2019-02-18 18:22:38 -08:00
|
|
|
margin-top: 42px;
|
2018-12-19 13:41:43 -08:00
|
|
|
`;
|
|
|
|
|
2019-02-18 15:40:15 -08:00
|
|
|
const Label = styled(InputLabelComponent)`
|
|
|
|
text-transform: uppercase;
|
|
|
|
color: ${props => props.theme.colors.transactionsDate};
|
|
|
|
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
|
|
|
|
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
2018-12-19 13:41:43 -08:00
|
|
|
`;
|
|
|
|
|
2019-02-06 19:06:48 -08:00
|
|
|
type AmountProps =
|
|
|
|
| {
|
|
|
|
isEmpty: boolean,
|
|
|
|
}
|
|
|
|
| Object;
|
2019-01-22 12:08:41 -08:00
|
|
|
const AmountWrapper = styled.div`
|
|
|
|
width: 100%;
|
2019-01-24 06:56:49 -08:00
|
|
|
position: relative;
|
2019-01-24 19:37:53 -08:00
|
|
|
|
2019-01-22 12:08:41 -08:00
|
|
|
&:before {
|
|
|
|
content: 'ZEC';
|
2019-02-07 08:31:59 -08:00
|
|
|
font-family: ${props => props.theme.fontFamily};
|
2019-01-22 12:08:41 -08:00
|
|
|
position: absolute;
|
2019-02-20 07:18:55 -08:00
|
|
|
margin-top: 16px;
|
2019-01-22 12:08:41 -08:00
|
|
|
margin-left: 15px;
|
|
|
|
display: block;
|
|
|
|
transition: all 0.05s ease-in-out;
|
2019-02-06 19:06:48 -08:00
|
|
|
opacity: ${(props: AmountProps) => (props.isEmpty ? '0' : '1')};
|
2019-02-19 08:03:27 -08:00
|
|
|
color: ${props => props.theme.colors.text};
|
2019-01-24 06:56:49 -08:00
|
|
|
z-index: 10;
|
2019-01-22 12:08:41 -08:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const AmountInput = styled(InputComponent)`
|
2019-02-06 19:06:48 -08:00
|
|
|
padding-left: ${(props: AmountProps) => (props.isEmpty ? '15' : '50')}px;
|
2019-01-22 12:08:41 -08:00
|
|
|
`;
|
|
|
|
|
2018-12-19 13:41:43 -08:00
|
|
|
const ShowFeeButton = styled.button`
|
|
|
|
background: none;
|
|
|
|
border: none;
|
|
|
|
cursor: pointer;
|
|
|
|
width: 100%;
|
2019-02-07 08:31:59 -08:00
|
|
|
color: ${props => props.theme.colors.text};
|
2018-12-19 13:41:43 -08:00
|
|
|
outline: none;
|
2019-01-21 10:50:37 -08:00
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
2019-01-23 19:10:08 -08:00
|
|
|
opacity: 0.7;
|
2018-12-19 13:41:43 -08:00
|
|
|
|
|
|
|
&:hover {
|
2019-01-23 19:10:08 -08:00
|
|
|
opacity: 1;
|
2018-12-19 13:41:43 -08:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2019-01-21 10:50:37 -08:00
|
|
|
const SeeMoreIcon = styled.img`
|
|
|
|
width: 25px;
|
|
|
|
height: 25px;
|
2019-02-07 08:31:59 -08:00
|
|
|
border: 1px solid ${props => props.theme.colors.text};
|
2019-01-23 19:10:08 -08:00
|
|
|
border-radius: 100%;
|
|
|
|
margin-right: 11.5px;
|
2019-01-21 10:50:37 -08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const FeeWrapper = styled.div`
|
2019-02-18 18:22:38 -08:00
|
|
|
background-color: ${props => props.theme.colors.sendAdditionalOptionsBg};
|
|
|
|
border: 1px solid ${props => props.theme.colors.sendAdditionalOptionsBorder};
|
|
|
|
border-radius: 3px;
|
|
|
|
padding: 0 20px 15px;
|
2019-01-21 10:50:37 -08:00
|
|
|
margin-bottom: 20px;
|
|
|
|
`;
|
|
|
|
|
2018-12-20 06:10:44 -08:00
|
|
|
const InfoCard = styled.div`
|
|
|
|
width: 100%;
|
2019-02-17 19:56:50 -08:00
|
|
|
background-color: ${props => props.theme.colors.sendCardBg};
|
|
|
|
border: 1px solid ${props => props.theme.colors.sendCardBorder}
|
2019-02-07 08:31:59 -08:00
|
|
|
border-radius: ${props => props.theme.boxBorderRadius};
|
2019-02-18 08:20:47 -08:00
|
|
|
margin-bottom: 10px;
|
2018-12-20 06:10:44 -08:00
|
|
|
`;
|
|
|
|
|
|
|
|
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)`
|
2019-01-23 21:08:20 -08:00
|
|
|
width: 100%;
|
2019-02-18 08:20:47 -08:00
|
|
|
margin: 5px 0;
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
margin-top: 0;
|
|
|
|
}
|
2018-12-20 06:10:44 -08:00
|
|
|
`;
|
|
|
|
|
2019-01-21 13:42:50 -08:00
|
|
|
const ModalContent = styled(ColumnComponent)`
|
|
|
|
min-height: 400px;
|
2019-01-10 06:51:32 -08:00
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
2019-01-21 13:42:50 -08:00
|
|
|
|
|
|
|
p {
|
|
|
|
word-break: break-all;
|
|
|
|
}
|
2019-01-10 06:51:32 -08:00
|
|
|
`;
|
|
|
|
|
2019-01-21 08:29:06 -08:00
|
|
|
const ConfirmItemWrapper = styled(RowComponent)`
|
|
|
|
padding: 22.5px 33.5px;
|
|
|
|
width: 100%;
|
|
|
|
`;
|
|
|
|
|
2019-02-11 05:54:03 -08:00
|
|
|
type ItemLabelProps = {
|
|
|
|
color: string,
|
|
|
|
};
|
2019-02-06 19:06:48 -08:00
|
|
|
/* eslint-disable max-len */
|
2019-01-21 10:16:16 -08:00
|
|
|
const ItemLabel = styled(TextComponent)`
|
2019-02-06 19:06:48 -08:00
|
|
|
font-weight: ${(props: PropsWithTheme<ItemLabelProps>) => String(props.theme.fontWeight.bold)};
|
|
|
|
font-size: ${(props: PropsWithTheme<ItemLabelProps>) => String(props.theme.fontSize.small)};
|
|
|
|
color: ${(props: PropsWithTheme<ItemLabelProps>) => props.color || props.theme.colors.modalItemLabel};
|
2019-01-21 08:29:06 -08:00
|
|
|
margin-bottom: 3.5px;
|
|
|
|
`;
|
|
|
|
|
2019-02-16 19:34:12 -08:00
|
|
|
const ValidateItemLabel = styled(ItemLabel)`
|
|
|
|
margin-bottom: -1px;
|
|
|
|
`;
|
|
|
|
|
2019-01-21 08:29:06 -08:00
|
|
|
const SendZECValue = styled(TextComponent)`
|
2019-02-07 08:31:59 -08:00
|
|
|
color: ${props => props.theme.colors.transactionSent};
|
|
|
|
font-size: ${props => `${props.theme.fontSize.large}em`};
|
|
|
|
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
2019-01-21 08:29:06 -08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const SendUSDValue = styled(TextComponent)`
|
|
|
|
opacity: 0.5;
|
2019-02-07 08:31:59 -08:00
|
|
|
font-weight: ${props => String(props.theme.fontWeight.light)};
|
|
|
|
font-size: ${props => `${props.theme.fontSize.medium}em`};
|
2019-01-21 08:29:06 -08:00
|
|
|
`;
|
|
|
|
|
|
|
|
const Icon = styled.img`
|
|
|
|
width: 35px;
|
|
|
|
height: 35px;
|
|
|
|
margin-left: 15px;
|
|
|
|
`;
|
|
|
|
|
2019-01-21 10:16:16 -08:00
|
|
|
const ValidateStatusIcon = styled.img`
|
|
|
|
width: 13px;
|
|
|
|
height: 13px;
|
|
|
|
margin-right: 7px;
|
|
|
|
`;
|
|
|
|
|
2019-01-23 21:08:20 -08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2019-02-08 19:51:34 -08:00
|
|
|
// $FlowFixMe
|
|
|
|
const Checkbox = styled.input.attrs({
|
|
|
|
type: 'checkbox',
|
|
|
|
})`
|
|
|
|
margin-right: 10px;
|
|
|
|
`;
|
|
|
|
|
2019-02-08 20:57:34 -08:00
|
|
|
const MaxAvailableAmount = styled.button`
|
|
|
|
margin-top: -15px;
|
|
|
|
margin-right: -15px;
|
|
|
|
width: 45px;
|
|
|
|
height: 48px;
|
|
|
|
border: none;
|
|
|
|
background: none;
|
|
|
|
color: white;
|
|
|
|
cursor: pointer;
|
2019-02-18 18:22:38 -08:00
|
|
|
border-left: 1px solid ${props => props.theme.colors.inputBorder};
|
2019-02-08 20:57:34 -08:00
|
|
|
opacity: 0.8;
|
2019-02-18 18:22:38 -08:00
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
2019-02-08 20:57:34 -08:00
|
|
|
|
|
|
|
&:hover {
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const MaxAvailableAmountImg = styled.img`
|
|
|
|
width: 20px;
|
|
|
|
height: 20px;
|
|
|
|
`;
|
|
|
|
|
2019-02-16 19:34:12 -08:00
|
|
|
const ValidateWrapper = styled(RowComponent)`
|
|
|
|
margin-top: 3px;
|
|
|
|
`;
|
|
|
|
|
2019-02-18 15:40:15 -08:00
|
|
|
const ActionsWrapper = styled(RowComponent)`
|
|
|
|
padding: 30px 0;
|
|
|
|
align-items: center;
|
|
|
|
flex-direction: row;
|
|
|
|
justify-content: space-between;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const HexadecimalWrapper = styled.div`
|
|
|
|
display: flex;
|
|
|
|
opacity: 0.7;
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
&:hover {
|
2019-02-19 08:03:27 -08:00
|
|
|
opacity: 1;s
|
2019-02-18 15:40:15 -08:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const HexadecimalText = styled(TextComponent)`
|
|
|
|
white-space: nowrap;
|
|
|
|
`;
|
|
|
|
|
2019-02-19 08:03:27 -08:00
|
|
|
const SimpleTooltip = styled.div`
|
|
|
|
background: ${props => props.theme.colors.walletAddressTooltipBg};
|
|
|
|
position: absolute;
|
|
|
|
top: -30px;
|
|
|
|
right: 0px;
|
|
|
|
padding: 6px 10px;
|
|
|
|
border-radius: 3px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const TooltipText = styled(TextComponent)`
|
|
|
|
color: ${props => props.theme.colors.walletAddressTooltip};
|
|
|
|
font-size: 10px;
|
|
|
|
font-weight: 700;
|
|
|
|
text-align: center;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const SendButtonWrapper = styled.div`
|
|
|
|
position: relative;
|
|
|
|
width: 100%;
|
|
|
|
display: flex;
|
|
|
|
`;
|
|
|
|
|
2019-01-24 05:56:51 -08:00
|
|
|
type Props = {
|
2019-02-18 18:30:45 -08:00
|
|
|
match: Match,
|
2019-02-18 09:25:16 -08:00
|
|
|
theme: AppTheme,
|
2019-02-19 08:03:27 -08:00
|
|
|
} & MapStateToProps &
|
|
|
|
MapDispatchToProps;
|
2019-01-10 06:51:32 -08:00
|
|
|
|
2018-12-19 13:41:43 -08:00
|
|
|
type State = {
|
|
|
|
showFee: boolean,
|
|
|
|
from: string,
|
2019-01-07 10:59:27 -08:00
|
|
|
amount: string,
|
2018-12-19 13:41:43 -08:00
|
|
|
to: string,
|
2018-12-20 09:18:45 -08:00
|
|
|
feeType: string | number,
|
|
|
|
fee: number | null,
|
2018-12-19 13:41:43 -08:00
|
|
|
memo: string,
|
2019-02-08 19:51:34 -08:00
|
|
|
isHexMemo: boolean,
|
2019-02-18 09:00:41 -08:00
|
|
|
showBalanceTooltip: boolean,
|
2018-12-19 13:41:43 -08:00
|
|
|
};
|
|
|
|
|
2019-02-18 09:00:41 -08:00
|
|
|
const initialState: State = {
|
2019-01-10 06:51:32 -08:00
|
|
|
showFee: false,
|
|
|
|
from: '',
|
2019-01-21 11:23:55 -08:00
|
|
|
amount: '',
|
2019-01-10 06:51:32 -08:00
|
|
|
to: '',
|
|
|
|
feeType: FEES.LOW,
|
|
|
|
fee: FEES.LOW,
|
|
|
|
memo: '',
|
2019-02-08 19:51:34 -08:00
|
|
|
isHexMemo: false,
|
2019-02-18 09:00:41 -08:00
|
|
|
showBalanceTooltip: false,
|
2019-01-10 06:51:32 -08:00
|
|
|
};
|
|
|
|
|
2019-02-18 09:25:16 -08:00
|
|
|
class Component extends PureComponent<Props, State> {
|
2019-01-10 06:51:32 -08:00
|
|
|
state = initialState;
|
2018-12-19 13:41:43 -08:00
|
|
|
|
2019-01-10 10:16:23 -08:00
|
|
|
componentDidMount() {
|
2019-02-14 14:24:38 -08:00
|
|
|
const {
|
|
|
|
resetSendView, loadAddresses, loadZECPrice, match,
|
|
|
|
} = this.props;
|
2019-01-21 13:42:50 -08:00
|
|
|
|
2019-01-10 10:16:23 -08:00
|
|
|
resetSendView();
|
2019-01-24 06:15:09 -08:00
|
|
|
loadAddresses();
|
|
|
|
loadZECPrice();
|
2019-02-14 14:24:38 -08:00
|
|
|
|
|
|
|
if (match.params.to) {
|
|
|
|
this.handleChange('to')(match.params.to);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps: Props) {
|
|
|
|
const previousToAddress = prevProps.match.params.to;
|
|
|
|
const toAddress = this.props.match.params.to; // eslint-disable-line
|
|
|
|
|
|
|
|
if (toAddress && previousToAddress !== toAddress) this.handleChange('to')(toAddress);
|
2019-01-10 10:16:23 -08:00
|
|
|
}
|
2018-12-19 13:41:43 -08:00
|
|
|
|
2019-02-18 09:00:41 -08:00
|
|
|
updateTooltipVisibility = ({ balance, amount }: { balance: number, amount: number }) => {
|
2019-02-20 07:08:53 -08:00
|
|
|
const { from, to } = this.state;
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
showBalanceTooltip: !from || !to ? false : new BigNumber(amount).gt(balance),
|
|
|
|
});
|
2019-02-18 09:00:41 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
getAmountWithFee = () => {
|
|
|
|
const { amount, fee } = this.state;
|
|
|
|
|
|
|
|
const feeValue = fee || 0;
|
|
|
|
|
|
|
|
if (!amount) return feeValue;
|
|
|
|
|
|
|
|
return new BigNumber(amount).plus(feeValue).toNumber();
|
|
|
|
};
|
|
|
|
|
2019-02-08 20:57:34 -08:00
|
|
|
handleChange = (field: string) => (value: string | number) => {
|
2019-02-07 20:20:39 -08:00
|
|
|
const { validateAddress, getAddressBalance, balance } = this.props;
|
2019-02-18 09:00:41 -08:00
|
|
|
const { amount } = this.state;
|
2019-01-21 10:16:16 -08:00
|
|
|
|
2019-01-21 13:42:50 -08:00
|
|
|
if (field === 'to') {
|
2019-02-18 09:00:41 -08:00
|
|
|
this.setState(
|
|
|
|
() => ({ [field]: value }),
|
2019-02-20 07:08:53 -08:00
|
|
|
() => {
|
|
|
|
validateAddress({ address: String(value) });
|
|
|
|
this.updateTooltipVisibility({
|
|
|
|
balance,
|
|
|
|
amount: new BigNumber(amount).toNumber(),
|
|
|
|
});
|
|
|
|
},
|
2019-02-18 09:00:41 -08:00
|
|
|
);
|
2019-01-10 19:38:57 -08:00
|
|
|
} else {
|
2019-02-08 20:57:34 -08:00
|
|
|
if (field === 'from') getAddressBalance({ address: String(value) });
|
2019-02-07 20:20:39 -08:00
|
|
|
|
2019-02-08 19:51:34 -08:00
|
|
|
this.setState(
|
|
|
|
() => ({ [field]: value }),
|
|
|
|
() => {
|
|
|
|
if (field === 'fee') this.handleChange('amount')(amount);
|
2019-02-20 07:08:53 -08:00
|
|
|
this.updateTooltipVisibility({
|
|
|
|
balance,
|
|
|
|
amount: new BigNumber(field === 'amount' ? value : amount).toNumber(),
|
|
|
|
});
|
2019-02-08 19:51:34 -08:00
|
|
|
},
|
|
|
|
);
|
2019-01-10 19:38:57 -08:00
|
|
|
}
|
2018-12-19 13:41:43 -08:00
|
|
|
};
|
|
|
|
|
2018-12-20 09:18:45 -08:00
|
|
|
handleChangeFeeType = (value: string) => {
|
2019-02-07 20:20:39 -08:00
|
|
|
const { amount } = this.state;
|
|
|
|
|
2019-01-21 08:29:06 -08:00
|
|
|
if (value === FEES.CUSTOM) {
|
2019-01-24 19:44:14 -08:00
|
|
|
this.setState(() => ({
|
2019-01-21 08:29:06 -08:00
|
|
|
feeType: FEES.CUSTOM,
|
2018-12-20 09:18:45 -08:00
|
|
|
fee: null,
|
2019-01-24 19:44:14 -08:00
|
|
|
}));
|
2019-01-21 08:29:06 -08:00
|
|
|
} else {
|
|
|
|
const fee = new BigNumber(value);
|
|
|
|
|
2019-02-07 20:20:39 -08:00
|
|
|
this.setState(
|
|
|
|
() => ({
|
|
|
|
feeType: fee.toString(),
|
|
|
|
fee: fee.toNumber(),
|
|
|
|
}),
|
|
|
|
() => this.handleChange('amount')(amount),
|
|
|
|
);
|
2019-01-21 08:29:06 -08:00
|
|
|
}
|
2018-12-20 09:18:45 -08:00
|
|
|
};
|
|
|
|
|
2018-12-21 07:44:29 -08:00
|
|
|
handleSubmit = () => {
|
|
|
|
const {
|
2019-02-08 19:51:34 -08:00
|
|
|
from, amount, to, memo, fee, isHexMemo,
|
2018-12-21 07:44:29 -08:00
|
|
|
} = this.state;
|
|
|
|
const { sendTransaction } = this.props;
|
|
|
|
|
2019-01-07 10:59:27 -08:00
|
|
|
if (!from || !amount || !to || !fee) return;
|
2018-12-21 07:44:29 -08:00
|
|
|
|
|
|
|
sendTransaction({
|
|
|
|
from,
|
|
|
|
to,
|
|
|
|
amount,
|
|
|
|
fee,
|
2019-02-08 19:51:34 -08:00
|
|
|
memo: isHexMemo ? memo : ascii2hex(memo),
|
2018-12-21 07:44:29 -08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2019-01-24 05:56:51 -08:00
|
|
|
showModal = (toggle: void => void) => {
|
2019-01-21 08:29:06 -08:00
|
|
|
const {
|
|
|
|
from, amount, to, fee,
|
|
|
|
} = this.state;
|
2019-01-24 05:56:51 -08:00
|
|
|
// eslint-disable-next-line react/prop-types
|
2019-01-21 13:42:50 -08:00
|
|
|
const { isToAddressValid } = this.props;
|
2019-01-21 08:29:06 -08:00
|
|
|
|
2019-01-21 13:42:50 -08:00
|
|
|
if (!from || !amount || !to || !fee || !isToAddressValid) return;
|
2019-01-21 08:29:06 -08:00
|
|
|
|
|
|
|
toggle();
|
|
|
|
};
|
|
|
|
|
2019-01-10 06:51:32 -08:00
|
|
|
reset = () => {
|
|
|
|
const { resetSendView } = this.props;
|
|
|
|
|
|
|
|
this.setState(initialState, () => resetSendView());
|
|
|
|
};
|
|
|
|
|
2019-01-21 08:29:06 -08:00
|
|
|
getFeeText = () => {
|
|
|
|
const { fee } = this.state;
|
|
|
|
|
2019-01-29 14:18:06 -08:00
|
|
|
if (!fee) return '0.0';
|
|
|
|
|
2019-01-21 08:29:06 -08:00
|
|
|
const feeValue = new BigNumber(fee);
|
|
|
|
|
|
|
|
if (feeValue.isEqualTo(FEES.LOW)) return `Low ZEC ${feeValue.toString()}`;
|
|
|
|
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()}`;
|
|
|
|
};
|
|
|
|
|
2019-01-21 10:16:16 -08:00
|
|
|
renderValidationStatus = () => {
|
2019-02-18 09:25:16 -08:00
|
|
|
const { isToAddressValid, theme } = this.props;
|
2019-01-21 10:16:16 -08:00
|
|
|
|
|
|
|
return isToAddressValid ? (
|
2019-02-16 19:34:12 -08:00
|
|
|
<ValidateWrapper alignItems='center'>
|
2019-01-21 10:16:16 -08:00
|
|
|
<ValidateStatusIcon src={ValidIcon} />
|
2019-02-20 07:08:53 -08:00
|
|
|
<ValidateItemLabel value='VALID' color={theme.colors.transactionReceived(this.props)} />
|
2019-02-16 19:34:12 -08:00
|
|
|
</ValidateWrapper>
|
2019-01-21 10:16:16 -08:00
|
|
|
) : (
|
2019-02-16 19:34:12 -08:00
|
|
|
<ValidateWrapper alignItems='center'>
|
2019-01-21 10:16:16 -08:00
|
|
|
<ValidateStatusIcon src={InvalidIcon} />
|
2019-02-20 07:08:53 -08:00
|
|
|
<ValidateItemLabel value='INVALID' color={theme.colors.transactionSent(this.props)} />
|
2019-02-16 19:34:12 -08:00
|
|
|
</ValidateWrapper>
|
2019-01-21 10:16:16 -08:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-01-21 13:42:50 -08:00
|
|
|
renderModalContent = ({
|
|
|
|
valueSent,
|
|
|
|
valueSentInUsd,
|
|
|
|
toggle,
|
|
|
|
}: {
|
2019-01-24 05:56:51 -08:00
|
|
|
/* eslint-disable react/no-unused-prop-types */
|
2019-01-21 13:42:50 -08:00
|
|
|
valueSent: string,
|
|
|
|
valueSentInUsd: string,
|
|
|
|
toggle: () => void,
|
2019-01-24 05:56:51 -08:00
|
|
|
/* eslint-enable react/no-unused-prop-types */
|
2019-01-21 13:42:50 -08:00
|
|
|
}) => {
|
2019-01-24 05:56:51 -08:00
|
|
|
// eslint-disable-next-line react/prop-types
|
2019-01-21 13:42:50 -08:00
|
|
|
const { operationId, isSending, error } = this.props;
|
|
|
|
const { from, to } = this.state;
|
|
|
|
|
|
|
|
if (isSending) {
|
|
|
|
return (
|
2019-01-23 21:08:20 -08:00
|
|
|
<Fragment>
|
2019-01-21 13:42:50 -08:00
|
|
|
<Loader src={LoadingIcon} />
|
|
|
|
<TextComponent value='Processing transaction...' />
|
2019-01-23 21:08:20 -08:00
|
|
|
</Fragment>
|
2019-01-21 13:42:50 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (operationId) {
|
|
|
|
return (
|
2019-01-29 07:36:13 -08:00
|
|
|
<ColumnComponent width='100%' id='send-success-wrapper'>
|
|
|
|
<TextComponent value={`Transaction ID: ${operationId}`} align='center' />
|
2019-01-21 13:42:50 -08:00
|
|
|
<button
|
|
|
|
type='button'
|
|
|
|
onClick={() => {
|
|
|
|
this.reset();
|
|
|
|
toggle();
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
Send again!
|
|
|
|
</button>
|
2019-01-24 11:26:03 -08:00
|
|
|
</ColumnComponent>
|
2019-01-21 13:42:50 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-01-24 11:26:03 -08:00
|
|
|
if (error) return <TextComponent id='send-error-message' value={error} />;
|
2019-01-21 13:42:50 -08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<ConfirmItemWrapper alignItems='center'>
|
|
|
|
<ColumnComponent>
|
|
|
|
<ItemLabel value='AMOUNT' />
|
|
|
|
<SendZECValue value={`-${valueSent}`} />
|
|
|
|
<SendUSDValue value={`-${valueSentInUsd}`} />
|
|
|
|
</ColumnComponent>
|
|
|
|
<ColumnComponent>
|
|
|
|
<Icon src={SentIcon} alt='Transaction Type Icon' />
|
|
|
|
</ColumnComponent>
|
|
|
|
</ConfirmItemWrapper>
|
|
|
|
<Divider opacity={0.3} />
|
|
|
|
<ConfirmItemWrapper alignItems='center'>
|
|
|
|
<ColumnComponent>
|
|
|
|
<ItemLabel value='FEE' />
|
|
|
|
<TextComponent value={this.getFeeText()} />
|
|
|
|
</ColumnComponent>
|
|
|
|
</ConfirmItemWrapper>
|
|
|
|
<Divider opacity={0.3} />
|
|
|
|
<ConfirmItemWrapper alignItems='center'>
|
|
|
|
<ColumnComponent>
|
|
|
|
<ItemLabel value='FROM' />
|
|
|
|
<TextComponent value={from} />
|
|
|
|
</ColumnComponent>
|
|
|
|
</ConfirmItemWrapper>
|
|
|
|
<Divider opacity={0.3} />
|
|
|
|
<ConfirmItemWrapper alignItems='center'>
|
|
|
|
<ColumnComponent>
|
|
|
|
<ItemLabel value='TO' />
|
|
|
|
<TextComponent value={to} />
|
|
|
|
</ColumnComponent>
|
|
|
|
</ConfirmItemWrapper>
|
|
|
|
<Divider opacity={0.3} marginBottom='27.5px' />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2019-02-18 09:00:41 -08:00
|
|
|
shouldDisableSendButton = () => {
|
|
|
|
const { balance } = this.props;
|
|
|
|
const {
|
|
|
|
from, amount, to, fee,
|
|
|
|
} = this.state;
|
|
|
|
|
|
|
|
return !from || !amount || !to || !fee || new BigNumber(amount).gt(balance);
|
|
|
|
};
|
|
|
|
|
2018-12-19 13:41:43 -08:00
|
|
|
render() {
|
2018-12-21 07:44:29 -08:00
|
|
|
const {
|
2019-02-18 09:25:16 -08:00
|
|
|
addresses, balance, zecPrice, isSending, error, operationId, theme,
|
2018-12-21 07:44:29 -08:00
|
|
|
} = this.props;
|
2018-12-19 13:41:43 -08:00
|
|
|
const {
|
2019-02-19 08:03:27 -08:00
|
|
|
showFee,
|
|
|
|
from,
|
|
|
|
amount,
|
|
|
|
to,
|
|
|
|
memo,
|
|
|
|
fee,
|
|
|
|
feeType,
|
|
|
|
isHexMemo,
|
|
|
|
showBalanceTooltip,
|
2018-12-19 13:41:43 -08:00
|
|
|
} = this.state;
|
|
|
|
|
2019-01-22 12:08:41 -08:00
|
|
|
const isEmpty = amount === '';
|
|
|
|
|
2019-02-19 08:03:27 -08:00
|
|
|
const fixedAmount = isEmpty || new BigNumber(amount).eq(0) ? 0 : this.getAmountWithFee();
|
2019-01-22 11:07:59 -08:00
|
|
|
|
2018-12-20 11:00:35 -08:00
|
|
|
const zecBalance = formatNumber({ value: balance, append: 'ZEC ' });
|
|
|
|
const zecBalanceInUsd = formatNumber({
|
2019-01-10 19:30:46 -08:00
|
|
|
value: new BigNumber(balance).times(zecPrice).toNumber(),
|
2018-12-20 11:00:35 -08:00
|
|
|
append: 'USD $',
|
|
|
|
});
|
2019-01-07 10:59:27 -08:00
|
|
|
const valueSent = formatNumber({
|
2019-02-08 20:57:34 -08:00
|
|
|
value: new BigNumber(fixedAmount).toFormat(4),
|
2019-01-07 10:59:27 -08:00
|
|
|
append: 'ZEC ',
|
|
|
|
});
|
2018-12-20 11:00:35 -08:00
|
|
|
const valueSentInUsd = formatNumber({
|
2019-02-18 09:00:41 -08:00
|
|
|
value: new BigNumber(fixedAmount).times(zecPrice).toNumber(),
|
2018-12-20 11:00:35 -08:00
|
|
|
append: 'USD $',
|
|
|
|
});
|
|
|
|
|
2019-02-19 08:03:27 -08:00
|
|
|
const seeMoreIcon = theme.mode === DARK ? MenuIconDark : MenuIconLight;
|
2019-02-18 09:25:16 -08:00
|
|
|
|
2019-02-19 08:03:27 -08:00
|
|
|
const arrowUpIcon = theme.mode === DARK ? ArrowUpIconDark : ArrowUpIconLight;
|
2019-02-18 18:22:38 -08:00
|
|
|
|
2019-01-21 13:42:50 -08:00
|
|
|
return (
|
2019-01-24 11:26:03 -08:00
|
|
|
<RowComponent id='send-wrapper' justifyContent='space-between'>
|
2018-12-20 06:10:44 -08:00
|
|
|
<FormWrapper>
|
2019-02-18 15:40:15 -08:00
|
|
|
<Label value='From an address' />
|
2018-12-20 06:10:44 -08:00
|
|
|
<SelectComponent
|
|
|
|
onChange={this.handleChange('from')}
|
|
|
|
value={from}
|
|
|
|
placeholder='Select a address'
|
2018-12-20 11:00:35 -08:00
|
|
|
options={addresses.map(addr => ({ value: addr, label: addr }))}
|
2019-02-07 19:05:26 -08:00
|
|
|
capitalize={false}
|
2018-12-20 06:10:44 -08:00
|
|
|
/>
|
2019-02-18 15:40:15 -08:00
|
|
|
<Label value='Amount' />
|
2019-01-22 12:08:41 -08:00
|
|
|
<AmountWrapper isEmpty={isEmpty}>
|
|
|
|
<AmountInput
|
2019-02-08 20:57:34 -08:00
|
|
|
renderRight={() => (
|
|
|
|
<MaxAvailableAmount
|
|
|
|
onClick={() => this.handleChange('amount')(balance)}
|
|
|
|
disabled={!from}
|
|
|
|
>
|
2019-02-18 18:22:38 -08:00
|
|
|
<MaxAvailableAmountImg src={arrowUpIcon} />
|
2019-02-08 20:57:34 -08:00
|
|
|
</MaxAvailableAmount>
|
|
|
|
)}
|
2019-01-22 12:08:41 -08:00
|
|
|
isEmpty={isEmpty}
|
|
|
|
type='number'
|
|
|
|
onChange={this.handleChange('amount')}
|
|
|
|
value={String(amount)}
|
2019-01-24 06:56:49 -08:00
|
|
|
placeholder='ZEC 0.0'
|
2019-01-22 12:08:41 -08:00
|
|
|
min={0.01}
|
2019-01-24 11:26:03 -08:00
|
|
|
name='amount'
|
2019-01-22 12:08:41 -08:00
|
|
|
/>
|
|
|
|
</AmountWrapper>
|
2019-02-18 15:40:15 -08:00
|
|
|
<Label value='To' />
|
2018-12-20 06:10:44 -08:00
|
|
|
<InputComponent
|
|
|
|
onChange={this.handleChange('to')}
|
|
|
|
value={to}
|
2019-01-10 09:51:51 -08:00
|
|
|
placeholder='Enter Address'
|
2019-01-21 10:16:16 -08:00
|
|
|
renderRight={to ? this.renderValidationStatus : () => null}
|
2019-01-24 11:26:03 -08:00
|
|
|
name='to'
|
2018-12-19 13:41:43 -08:00
|
|
|
/>
|
2019-02-18 15:40:15 -08:00
|
|
|
<Label value='Memo' />
|
2018-12-20 06:10:44 -08:00
|
|
|
<InputComponent
|
|
|
|
onChange={this.handleChange('memo')}
|
|
|
|
value={memo}
|
|
|
|
inputType='textarea'
|
|
|
|
placeholder='Enter a text here'
|
2019-01-23 09:04:11 -08:00
|
|
|
name='memo'
|
2018-12-20 06:10:44 -08:00
|
|
|
/>
|
2019-02-18 15:40:15 -08:00
|
|
|
<ActionsWrapper>
|
|
|
|
<ShowFeeButton
|
|
|
|
id='send-show-additional-options-button'
|
|
|
|
onClick={() => this.setState(state => ({
|
|
|
|
showFee: !state.showFee,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<SeeMoreIcon src={seeMoreIcon} alt='Show more icon' />
|
|
|
|
<TextComponent value={`${showFee ? 'Hide' : 'Show'} Additional Options`} />
|
|
|
|
</ShowFeeButton>
|
|
|
|
<HexadecimalWrapper>
|
|
|
|
<Checkbox
|
|
|
|
onChange={event => this.setState({ isHexMemo: event.target.checked })}
|
|
|
|
checked={isHexMemo}
|
|
|
|
/>
|
|
|
|
<HexadecimalText
|
|
|
|
onClick={() => this.setState(prevState => ({ isHexMemo: !prevState.isHexMemo }))}
|
|
|
|
value='Hexadecimal Memo'
|
|
|
|
/>
|
|
|
|
</HexadecimalWrapper>
|
|
|
|
</ActionsWrapper>
|
2019-01-23 21:08:20 -08:00
|
|
|
<RevealsMain>
|
|
|
|
<Transition
|
|
|
|
native
|
|
|
|
items={showFee}
|
2019-01-29 07:36:13 -08:00
|
|
|
enter={[
|
|
|
|
{
|
|
|
|
height: 'auto',
|
|
|
|
opacity: 1,
|
|
|
|
overflow: 'visible',
|
|
|
|
},
|
|
|
|
]}
|
2019-01-25 13:57:12 -08:00
|
|
|
leave={{ height: 0, opacity: 0 }}
|
2019-01-23 21:08:20 -08:00
|
|
|
from={{
|
|
|
|
position: 'absolute',
|
|
|
|
overflow: 'hidden',
|
2019-01-25 13:57:12 -08:00
|
|
|
opacity: 0,
|
2019-01-23 21:08:20 -08:00
|
|
|
height: 0,
|
|
|
|
}}
|
|
|
|
>
|
2019-02-06 19:06:48 -08:00
|
|
|
{(show: boolean) => show
|
|
|
|
&& ((props: Object) => (
|
2019-01-29 07:36:13 -08:00
|
|
|
<animated.div style={props}>
|
|
|
|
<FeeWrapper id='send-fee-wrapper'>
|
|
|
|
<RowComponent alignItems='flex-end' justifyContent='space-between'>
|
2019-02-18 18:22:38 -08:00
|
|
|
<ColumnComponent width='64%'>
|
2019-02-18 15:40:15 -08:00
|
|
|
<Label value='Fee' />
|
2019-01-29 07:36:13 -08:00
|
|
|
<InputComponent
|
|
|
|
type='number'
|
|
|
|
onChange={this.handleChange('fee')}
|
|
|
|
value={String(fee)}
|
|
|
|
disabled={feeType !== FEES.CUSTOM}
|
2019-02-20 07:08:53 -08:00
|
|
|
bgColor={theme.colors.sendAdditionalInputBg(this.props)}
|
|
|
|
color={theme.colors.sendAdditionalInputText(this.props)}
|
2019-01-29 07:36:13 -08:00
|
|
|
name='fee'
|
|
|
|
/>
|
|
|
|
</ColumnComponent>
|
2019-02-18 18:22:38 -08:00
|
|
|
<ColumnComponent width='35%'>
|
2019-01-29 07:36:13 -08:00
|
|
|
<SelectComponent
|
|
|
|
placement='top'
|
|
|
|
value={String(feeType)}
|
2019-02-20 07:08:53 -08:00
|
|
|
bgColor={theme.colors.sendAdditionalInputBg(this.props)}
|
2019-01-29 07:36:13 -08:00
|
|
|
onChange={this.handleChangeFeeType}
|
|
|
|
options={Object.keys(FEES).map(cur => ({
|
|
|
|
label: cur.toLowerCase(),
|
|
|
|
value: String(FEES[cur]),
|
|
|
|
}))}
|
|
|
|
/>
|
|
|
|
</ColumnComponent>
|
|
|
|
</RowComponent>
|
|
|
|
</FeeWrapper>
|
|
|
|
</animated.div>
|
|
|
|
))
|
|
|
|
}
|
2019-01-23 21:08:20 -08:00
|
|
|
</Transition>
|
|
|
|
</RevealsMain>
|
2018-12-20 11:04:00 -08:00
|
|
|
{feeType === FEES.CUSTOM && (
|
|
|
|
<TextComponent value='Custom fees may compromise your privacy since fees are transparent' />
|
|
|
|
)}
|
2018-12-20 06:10:44 -08:00
|
|
|
</FormWrapper>
|
|
|
|
<SendWrapper>
|
|
|
|
<InfoCard>
|
|
|
|
<InfoContent>
|
|
|
|
<InfoCardLabel value='Available Funds:' />
|
2019-01-10 09:51:51 -08:00
|
|
|
<TextComponent value={zecBalance} size={1.25} isBold />
|
|
|
|
<InfoCardUSD value={zecBalanceInUsd} size={0.84375} />
|
2018-12-20 06:10:44 -08:00
|
|
|
</InfoContent>
|
2019-01-21 08:29:06 -08:00
|
|
|
<Divider opacity={0.3} />
|
2018-12-20 06:10:44 -08:00
|
|
|
<InfoContent>
|
|
|
|
<InfoCardLabel value='Sending' />
|
2019-01-10 09:51:51 -08:00
|
|
|
<TextComponent value={valueSent} size={1.25} isBold />
|
|
|
|
<InfoCardUSD value={valueSentInUsd} size={0.84375} />
|
2018-12-20 06:10:44 -08:00
|
|
|
</InfoContent>
|
|
|
|
</InfoCard>
|
2019-01-21 08:29:06 -08:00
|
|
|
<ConfirmDialogComponent
|
|
|
|
title='Please Confirm Transaction Details'
|
|
|
|
onConfirm={this.handleSubmit}
|
2019-01-24 19:44:14 -08:00
|
|
|
showButtons={!isSending && !error && !operationId}
|
|
|
|
onClose={this.reset}
|
2019-01-21 08:29:06 -08:00
|
|
|
renderTrigger={toggle => (
|
2019-02-19 08:03:27 -08:00
|
|
|
<SendButtonWrapper>
|
|
|
|
{showBalanceTooltip ? (
|
|
|
|
<SimpleTooltip>
|
|
|
|
<TooltipText value='You do not seem' />
|
|
|
|
<TooltipText value='to have enough funds' />
|
|
|
|
</SimpleTooltip>
|
|
|
|
) : null}
|
2019-02-18 09:00:41 -08:00
|
|
|
<FormButton
|
|
|
|
onClick={() => this.showModal(toggle)}
|
|
|
|
id='send-submit-button'
|
|
|
|
label='Send'
|
|
|
|
variant='secondary'
|
|
|
|
focused
|
|
|
|
isFluid
|
|
|
|
disabled={this.shouldDisableSendButton()}
|
|
|
|
/>
|
2019-02-19 08:03:27 -08:00
|
|
|
</SendButtonWrapper>
|
2019-01-21 08:29:06 -08:00
|
|
|
)}
|
|
|
|
>
|
2019-01-21 13:42:50 -08:00
|
|
|
{toggle => (
|
2019-01-24 11:26:03 -08:00
|
|
|
<ModalContent id='send-confirm-transaction-modal'>
|
2019-01-23 21:08:20 -08:00
|
|
|
{this.renderModalContent({
|
|
|
|
valueSent,
|
|
|
|
valueSentInUsd,
|
|
|
|
toggle,
|
|
|
|
})}
|
2019-01-21 13:42:50 -08:00
|
|
|
</ModalContent>
|
|
|
|
)}
|
2019-01-21 08:29:06 -08:00
|
|
|
</ConfirmDialogComponent>
|
2019-02-18 08:20:47 -08:00
|
|
|
<FormButton label='Clear Form' variant='secondary' />
|
2018-12-20 06:10:44 -08:00
|
|
|
</SendWrapper>
|
|
|
|
</RowComponent>
|
2018-12-19 13:41:43 -08:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-02-18 09:25:16 -08:00
|
|
|
|
|
|
|
export const SendView = withTheme(Component);
|