Merge pull request #51 from andrerfneves/feature/ui-improvements-send-receive
UI improvements to Send & Receive Views
This commit is contained in:
commit
197932d091
|
@ -64,12 +64,13 @@ export class LoadingScreen extends PureComponent<Props, State> {
|
|||
<Transition
|
||||
native
|
||||
items={start}
|
||||
enter={[{ height: 'auto' }]}
|
||||
leave={{ height: 0 }}
|
||||
enter={[{ height: 'auto', opacity: 1 }]}
|
||||
leave={{ height: 0, opacity: 0 }}
|
||||
from={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
}}
|
||||
>
|
||||
{() => props => (
|
||||
|
|
|
@ -13,7 +13,7 @@ const ModalWrapper = styled.div`
|
|||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
const ChildrenWrapper = styled.div`
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
|
||||
import { ColumnComponent } from './column';
|
||||
import { Button } from './button';
|
||||
|
@ -36,16 +37,16 @@ const Input = styled.input`
|
|||
|
||||
const Btn = styled(Button)`
|
||||
border-width: 1px;
|
||||
font-weight: ${props => props.theme.fontWeight.light};
|
||||
font-weight: ${props => props.theme.fontWeight.regular};
|
||||
border-color: ${props => (props.isVisible
|
||||
? props.theme.colors.primary : props.theme.colors.buttonBorderColor
|
||||
)};
|
||||
padding: 6px 10px;
|
||||
width: 50%;
|
||||
padding: 8px 10px;
|
||||
min-width: 260px;
|
||||
|
||||
img {
|
||||
height: 12px;
|
||||
width: 16px;
|
||||
width: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -59,36 +60,52 @@ const QRCodeWrapper = styled.div`
|
|||
width: 100%;
|
||||
`;
|
||||
|
||||
const RevealsMain = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: ${props => (props.isVisible ? '178px' : 0)}
|
||||
transition: all 0.25s ease-in-out;
|
||||
|
||||
& > div {
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
address: string,
|
||||
isVisible?: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isVisible: boolean,
|
||||
};
|
||||
|
||||
export class WalletAddress extends Component<Props, State> {
|
||||
state = {
|
||||
export class WalletAddress extends PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
isVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
show = () => {
|
||||
this.setState(
|
||||
() => ({ isVisible: true }),
|
||||
);
|
||||
};
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
hide = () => {
|
||||
this.setState(
|
||||
() => ({ isVisible: false }),
|
||||
);
|
||||
};
|
||||
this.state = { isVisible: Boolean(props.isVisible) };
|
||||
}
|
||||
|
||||
show = () => this.setState(() => ({ isVisible: true }));
|
||||
|
||||
hide = () => this.setState(() => ({ isVisible: false }));
|
||||
|
||||
render() {
|
||||
const { address } = this.props;
|
||||
const { isVisible } = this.state;
|
||||
const toggleVisibility = isVisible ? this.hide : this.show;
|
||||
const buttonLabel = `${isVisible ? 'Hide' : 'Show'} details and QR Code`;
|
||||
|
||||
return (
|
||||
<ColumnComponent width='100%'>
|
||||
|
@ -100,19 +117,37 @@ export class WalletAddress extends Component<Props, State> {
|
|||
/>
|
||||
<Btn
|
||||
icon={eyeIcon}
|
||||
label={`${isVisible ? 'Hide' : 'Show'} full address and QR Code`}
|
||||
label={buttonLabel}
|
||||
onClick={toggleVisibility}
|
||||
variant='secondary'
|
||||
isVisible={isVisible}
|
||||
/>
|
||||
</AddressWrapper>
|
||||
{isVisible
|
||||
? (
|
||||
<QRCodeWrapper>
|
||||
<QRCode value={address} />
|
||||
</QRCodeWrapper>
|
||||
)
|
||||
: null}
|
||||
<RevealsMain isVisible={isVisible}>
|
||||
<Transition
|
||||
native
|
||||
items={isVisible}
|
||||
enter={[{
|
||||
height: 'auto',
|
||||
opacity: 1,
|
||||
}]}
|
||||
leave={{ height: 0, opacity: 0 }}
|
||||
from={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
}}
|
||||
>
|
||||
{show => show && (props => (
|
||||
<animated.div style={props}>
|
||||
<QRCodeWrapper>
|
||||
<QRCode value={address} />
|
||||
</QRCodeWrapper>
|
||||
</animated.div>
|
||||
))}
|
||||
</Transition>
|
||||
</RevealsMain>
|
||||
</ColumnComponent>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,50 +1,165 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { Fragment, PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
|
||||
import { InputLabelComponent } from '../components/input-label';
|
||||
import { RowComponent } from '../components/row';
|
||||
import { TextComponent } from '../components/text';
|
||||
import { WalletAddress } from '../components/wallet-address';
|
||||
|
||||
type Props = {
|
||||
addresses: Array<string>,
|
||||
loadAddresses: () => void,
|
||||
};
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-top: ${props => props.theme.layoutContentPaddingTop};
|
||||
`;
|
||||
import MenuIcon from '../assets/images/menu_icon.svg';
|
||||
|
||||
const Row = styled(RowComponent)`
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
const Label = styled(InputLabelComponent)`
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
text-transform: uppercase;
|
||||
color: ${props => props.theme.colors.transactionsDate};
|
||||
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
export class ReceiveView extends PureComponent<Props> {
|
||||
const ShowMoreButton = styled.button`
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
color: ${props => props.theme.colors.text};
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const ShowMoreIcon = styled.img`
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border: 1px solid ${props => props.theme.colors.text};
|
||||
border-radius: 100%;
|
||||
margin-right: 11.5px;
|
||||
`;
|
||||
|
||||
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 = {
|
||||
addresses: Array<string>,
|
||||
loadAddresses: () => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
showAdditionalOptions: boolean,
|
||||
}
|
||||
|
||||
export class ReceiveView extends PureComponent<Props, State> {
|
||||
state = {
|
||||
showAdditionalOptions: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { loadAddresses } = this.props;
|
||||
|
||||
loadAddresses();
|
||||
}
|
||||
|
||||
toggleAdditionalOptions = () => this.setState((prevState: State) => ({
|
||||
showAdditionalOptions: !prevState.showAdditionalOptions,
|
||||
}));
|
||||
|
||||
renderShieldedAddresses = (address: string) => {
|
||||
const { showAdditionalOptions } = this.state;
|
||||
const buttonText = `${showAdditionalOptions ? 'Hide' : 'Show'} Other Address Types`;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Label value='Shielded Address' />
|
||||
<Row
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
>
|
||||
<WalletAddress address={address} />
|
||||
</Row>
|
||||
<Row>
|
||||
<ShowMoreButton
|
||||
onClick={this.toggleAdditionalOptions}
|
||||
isActive={showAdditionalOptions}
|
||||
>
|
||||
<ShowMoreIcon
|
||||
isActive={showAdditionalOptions}
|
||||
src={MenuIcon}
|
||||
alt='More Options'
|
||||
/>
|
||||
<TextComponent value={buttonText} />
|
||||
</ShowMoreButton>
|
||||
</Row>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderTransparentAddresses = (address: string) => {
|
||||
const { showAdditionalOptions } = this.state;
|
||||
|
||||
return (
|
||||
<RevealsMain>
|
||||
<Transition
|
||||
native
|
||||
items={showAdditionalOptions}
|
||||
enter={[{ height: 'auto', opacity: 1 }]}
|
||||
leave={{ height: 0, opacity: 0 }}
|
||||
from={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
}}
|
||||
>
|
||||
{show => show && (props => (
|
||||
<animated.div style={props}>
|
||||
<Label value='Transparent Address (not private)' />
|
||||
<Row
|
||||
key={address}
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
>
|
||||
<WalletAddress address={address} />
|
||||
</Row>
|
||||
</animated.div>
|
||||
))}
|
||||
</Transition>
|
||||
</RevealsMain>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { addresses } = this.props;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Label value='Addresses: ' />
|
||||
{(addresses || []).map(address => (
|
||||
<Row key={address} alignItems='center' justifyContent='space-between'>
|
||||
<WalletAddress address={address} />
|
||||
</Row>
|
||||
))}
|
||||
</Wrapper>
|
||||
<div>
|
||||
{(addresses || []).map((address, index) => {
|
||||
if (index === 0) return this.renderShieldedAddresses(address);
|
||||
return this.renderTransparentAddresses(address);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
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';
|
||||
|
||||
|
@ -87,7 +88,7 @@ const ShowFeeButton = styled.button`
|
|||
outline: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
margin: 30px 0;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
|
@ -105,7 +106,7 @@ const SeeMoreIcon = styled.img`
|
|||
|
||||
const FeeWrapper = styled.div`
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
border-radius: 4px;
|
||||
padding: 13px 19px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
@ -181,12 +182,28 @@ const ValidateStatusIcon = styled.img`
|
|||
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,
|
||||
|
@ -229,7 +246,8 @@ export class SendView extends PureComponent<Props, State> {
|
|||
|
||||
if (field === 'to') {
|
||||
// eslint-disable-next-line max-len
|
||||
this.setState({ [field]: value }, () => validateAddress({ address: value }));
|
||||
this.setState(() => ({ [field]: value }),
|
||||
() => validateAddress({ address: value }));
|
||||
} else {
|
||||
this.setState(() => ({ [field]: value }));
|
||||
}
|
||||
|
@ -237,17 +255,17 @@ export class SendView extends PureComponent<Props, State> {
|
|||
|
||||
handleChangeFeeType = (value: string) => {
|
||||
if (value === FEES.CUSTOM) {
|
||||
this.setState({
|
||||
this.setState(() => ({
|
||||
feeType: FEES.CUSTOM,
|
||||
fee: null,
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
const fee = new BigNumber(value);
|
||||
|
||||
this.setState({
|
||||
this.setState(() => ({
|
||||
feeType: fee.toString(),
|
||||
fee: fee.toNumber(),
|
||||
});
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -305,12 +323,18 @@ export class SendView extends PureComponent<Props, State> {
|
|||
return isToAddressValid ? (
|
||||
<RowComponent alignItems='center'>
|
||||
<ValidateStatusIcon src={ValidIcon} />
|
||||
<ItemLabel value='VALID' color={theme.colors.transactionReceived} />
|
||||
<ItemLabel
|
||||
value='VALID'
|
||||
color={theme.colors.transactionReceived}
|
||||
/>
|
||||
</RowComponent>
|
||||
) : (
|
||||
<RowComponent alignItems='center'>
|
||||
<ValidateStatusIcon src={InvalidIcon} />
|
||||
<ItemLabel value='INVALID' color={theme.colors.transactionSent} />
|
||||
<ItemLabel
|
||||
value='INVALID'
|
||||
color={theme.colors.transactionSent}
|
||||
/>
|
||||
</RowComponent>
|
||||
);
|
||||
};
|
||||
|
@ -332,17 +356,23 @@ export class SendView extends PureComponent<Props, State> {
|
|||
|
||||
if (isSending) {
|
||||
return (
|
||||
<>
|
||||
<Fragment>
|
||||
<Loader src={LoadingIcon} />
|
||||
<TextComponent value='Processing transaction...' />
|
||||
</>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (operationId) {
|
||||
return (
|
||||
<ColumnComponent width='100%' id='send-success-wrapper'>
|
||||
<TextComponent value={`Transaction ID: ${operationId}`} align='center' />
|
||||
<ColumnComponent
|
||||
width='100%'
|
||||
id='send-success-wrapper'
|
||||
>
|
||||
<TextComponent
|
||||
value={`Transaction ID: ${operationId}`}
|
||||
align='center'
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => {
|
||||
|
@ -462,40 +492,71 @@ export class SendView extends PureComponent<Props, State> {
|
|||
/>
|
||||
<ShowFeeButton
|
||||
id='send-show-additional-options-button'
|
||||
onClick={() => this.setState(state => ({ showFee: !state.showFee }))}
|
||||
onClick={() => this.setState(state => ({
|
||||
showFee: !state.showFee,
|
||||
}))}
|
||||
>
|
||||
<SeeMoreIcon src={MenuIcon} alt='Show more icon' />
|
||||
<TextComponent value={`${showFee ? 'Hide' : 'Show'} Additional Options`} />
|
||||
<SeeMoreIcon
|
||||
src={MenuIcon}
|
||||
alt='Show more icon'
|
||||
/>
|
||||
<TextComponent
|
||||
value={`${showFee ? 'Hide' : 'Show'} Additional Options`}
|
||||
/>
|
||||
</ShowFeeButton>
|
||||
{showFee && (
|
||||
<FeeWrapper id='send-fee-wrapper'>
|
||||
<RowComponent alignItems='flex-end' justifyContent='space-between'>
|
||||
<ColumnComponent width='74%'>
|
||||
<InputLabelComponent value='Fee' marginTop='0px' />
|
||||
<InputComponent
|
||||
type='number'
|
||||
onChange={this.handleChange('fee')}
|
||||
value={String(fee)}
|
||||
disabled={feeType !== FEES.CUSTOM}
|
||||
bgColor={theme.colors.blackTwo}
|
||||
name='fee'
|
||||
/>
|
||||
</ColumnComponent>
|
||||
<ColumnComponent width='25%'>
|
||||
<SelectComponent
|
||||
onChange={this.handleChangeFeeType}
|
||||
value={String(feeType)}
|
||||
options={Object.keys(FEES).map(cur => ({
|
||||
label: cur.toLowerCase(),
|
||||
value: String(FEES[cur]),
|
||||
}))}
|
||||
placement='top'
|
||||
bgColor={theme.colors.blackTwo}
|
||||
/>
|
||||
</ColumnComponent>
|
||||
</RowComponent>
|
||||
</FeeWrapper>
|
||||
)}
|
||||
<RevealsMain>
|
||||
<Transition
|
||||
native
|
||||
items={showFee}
|
||||
enter={[{
|
||||
height: 'auto',
|
||||
opacity: 1,
|
||||
overflow: 'visible',
|
||||
}]}
|
||||
leave={{ height: 0, opacity: 0 }}
|
||||
from={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
}}
|
||||
>
|
||||
{show => show && (props => (
|
||||
<animated.div style={props}>
|
||||
<FeeWrapper id='send-fee-wrapper'>
|
||||
<RowComponent
|
||||
alignItems='flex-end'
|
||||
justifyContent='space-between'
|
||||
>
|
||||
<ColumnComponent width='74%'>
|
||||
<InputLabelComponent value='Fee' />
|
||||
<InputComponent
|
||||
type='number'
|
||||
onChange={this.handleChange('fee')}
|
||||
value={String(fee)}
|
||||
disabled={feeType !== FEES.CUSTOM}
|
||||
bgColor={theme.colors.blackTwo}
|
||||
name='fee'
|
||||
/>
|
||||
</ColumnComponent>
|
||||
<ColumnComponent width='25%'>
|
||||
<SelectComponent
|
||||
placement='top'
|
||||
value={String(feeType)}
|
||||
bgColor={theme.colors.blackTwo}
|
||||
onChange={this.handleChangeFeeType}
|
||||
options={Object.keys(FEES).map(cur => ({
|
||||
label: cur.toLowerCase(),
|
||||
value: String(FEES[cur]),
|
||||
}))}
|
||||
/>
|
||||
</ColumnComponent>
|
||||
</RowComponent>
|
||||
</FeeWrapper>
|
||||
</animated.div>
|
||||
))}
|
||||
</Transition>
|
||||
</RevealsMain>
|
||||
{feeType === FEES.CUSTOM && (
|
||||
<TextComponent value='Custom fees may compromise your privacy since fees are transparent' />
|
||||
)}
|
||||
|
@ -517,26 +578,34 @@ export class SendView extends PureComponent<Props, State> {
|
|||
<ConfirmDialogComponent
|
||||
title='Please Confirm Transaction Details'
|
||||
onConfirm={this.handleSubmit}
|
||||
showButtons={!isSending && !error && !operationId}
|
||||
onClose={this.reset}
|
||||
renderTrigger={toggle => (
|
||||
<FormButton
|
||||
onClick={() => this.showModal(toggle)}
|
||||
id='send-submit-button'
|
||||
label='Send'
|
||||
variant='secondary'
|
||||
focused
|
||||
onClick={() => this.showModal(toggle)}
|
||||
isFluid
|
||||
disabled={!from || !amount || !to || !fee}
|
||||
/>
|
||||
)}
|
||||
showButtons={!isSending && !error && !operationId}
|
||||
onClose={this.reset}
|
||||
>
|
||||
{toggle => (
|
||||
<ModalContent id='send-confirm-transaction-modal'>
|
||||
{this.renderModalContent({ valueSent, valueSentInUsd, toggle })}
|
||||
{this.renderModalContent({
|
||||
valueSent,
|
||||
valueSentInUsd,
|
||||
toggle,
|
||||
})}
|
||||
</ModalContent>
|
||||
)}
|
||||
</ConfirmDialogComponent>
|
||||
<FormButton label='Cancel' variant='secondary' />
|
||||
<FormButton
|
||||
label='Cancel'
|
||||
variant='secondary'
|
||||
/>
|
||||
</SendWrapper>
|
||||
</RowComponent>
|
||||
);
|
||||
|
|
|
@ -53,7 +53,7 @@ const createWindow = () => {
|
|||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1000,
|
||||
height: 600,
|
||||
height: 660,
|
||||
transparent: false,
|
||||
frame: true,
|
||||
resizable: true,
|
||||
|
|
Loading…
Reference in New Issue