Merge pull request #51 from andrerfneves/feature/ui-improvements-send-receive

UI improvements to Send & Receive Views
This commit is contained in:
André Neves 2019-01-25 17:53:43 -05:00 committed by GitHub
commit 197932d091
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 323 additions and 103 deletions

View File

@ -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 => (

View File

@ -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`

View File

@ -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>
);
}

View File

@ -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>
);
}
}

View File

@ -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>
);

View File

@ -53,7 +53,7 @@ const createWindow = () => {
mainWindow = new BrowserWindow({
width: 1000,
height: 600,
height: 660,
transparent: false,
frame: true,
resizable: true,