chore(git): merge with developer

This commit is contained in:
George Lima 2019-01-24 11:56:49 -03:00
parent 495bcd7548
commit e68583b363
27 changed files with 951 additions and 329 deletions

View File

@ -34,7 +34,7 @@
"max-len": [
"error",
{
"code": 80,
"code": 100,
"tabWidth": 2,
"ignoreUrls": true,
"ignoreComments": true,

View File

@ -9,6 +9,9 @@ import { Link } from 'react-router-dom';
import { darken } from 'polished';
const DefaultButton = styled.button`
align-items: center;
display: flex;
justify-content: center;
padding: 10px 30px;
font-family: ${props => props.theme.fontFamily};
font-weight: ${props => props.theme.fontWeight.bold};
@ -19,7 +22,6 @@ const DefaultButton = styled.button`
border-radius: 100px;
transition: background-color 0.1s
${props => props.theme.colors.transitionEase};
width: 100%;
`;
const Primary = styled(DefaultButton)`
@ -32,7 +34,7 @@ const Primary = styled(DefaultButton)`
}
&:disabled {
background-color: #3e3c42;
background-color: ${props => props.theme.colors.buttonBorderColor};
cursor: not-allowed;
opacity: 0.8;
}
@ -41,7 +43,7 @@ const Primary = styled(DefaultButton)`
const Secondary = styled(DefaultButton)`
background-color: transparent;
color: ${props => props.theme.colors.secondary};
border: 2px solid #3e3c42;
border: 2px solid ${props => props.theme.colors.buttonBorderColor};
&:hover {
border-color: ${props => props.theme.colors.primary};
@ -50,16 +52,16 @@ const Secondary = styled(DefaultButton)`
&:disabled {
background-color: Transparent;
cursor: not-allowed;
color: #3e3c42;
color: ${props => props.theme.colors.buttonBorderColor};
&:hover {
border-color: #3e3c42;
border-color: ${props => props.theme.colors.buttonBorderColor};
}
}
`;
const Icon = styled.img`
height: 9px;
height: 12px;
width: 12px;
margin-right: 10px;
`;

View File

@ -50,54 +50,64 @@ type Props = {
renderTrigger: (() => void) => Element<*>,
title: string,
onConfirm: () => void,
onClose?: () => void,
showButtons?: boolean,
width?: number,
isLoading?: boolean,
children: Element<*>,
children: (() => void) => Element<*>,
};
export const ConfirmDialogComponent = ({
children,
title,
onConfirm,
onClose,
renderTrigger,
showButtons,
isLoading,
width,
}: Props) => (
<ModalComponent
renderTrigger={renderTrigger}
closeOnBackdropClick={false}
closeOnEsc={false}
>
{toggle => (
<Wrapper width={width}>
<CloseIconWrapper>
<CloseIconImg src={CloseIcon} onClick={toggle} />
</CloseIconWrapper>
<TitleWrapper>
<TextComponent value={title} align='center' />
</TitleWrapper>
<Divider />
{React.Children.map(children, _ => _)}
{showButtons && (
<>
<Btn label='Confirm' onClick={onConfirm} isLoading={isLoading} />
<Btn
label='Cancel'
onClick={toggle}
variant='secondary'
disabled={isLoading}
/>
</>
)}
</Wrapper>
)}
</ModalComponent>
);
}: Props) => {
const handleClose = toggle => () => {
toggle();
if (onClose) onClose();
};
return (
<ModalComponent
renderTrigger={renderTrigger}
closeOnBackdropClick={false}
closeOnEsc={false}
>
{toggle => (
<Wrapper width={width}>
<CloseIconWrapper>
<CloseIconImg src={CloseIcon} onClick={handleClose(toggle)} />
</CloseIconWrapper>
<TitleWrapper>
<TextComponent value={title} align='center' />
</TitleWrapper>
<Divider opacity={0.3} />
{children(handleClose(toggle))}
{showButtons && (
<>
<Btn label='Confirm' onClick={onConfirm} isLoading={isLoading} />
<Btn
label='Cancel'
onClick={handleClose(toggle)}
variant='secondary'
disabled={isLoading}
/>
</>
)}
</Wrapper>
)}
</ModalComponent>
);
};
ConfirmDialogComponent.defaultProps = {
showButtons: true,
width: 460,
isLoading: false,
onClose: () => {},
};

View File

@ -24,7 +24,7 @@ import { DoczWrapper } from '../theme.js'
onConfirm={() => alert('Confirm')}
renderTrigger={toggle => <button onClick={toggle}> Open! </button>}
>
<div>Confirm content</div>
{toggle => <div>Confirm content</div>}
</ConfirmDialogComponent>
)}
</DoczWrapper>

View File

@ -6,4 +6,6 @@ export const Divider = styled.div`
height: 1px;
background-color: ${props => props.color || props.theme.colors.text};
opacity: ${props => props.opacity || 1};
margin-bottom: ${props => props.marginBottom || 0};
margin-top: ${props => props.marginTop || 0};
`;

View File

@ -4,6 +4,6 @@ import styled from 'styled-components';
import { TextComponent } from './text';
export const InputLabelComponent = styled(TextComponent)`
margin: 20px 0 8.5px 0;
margin: ${props => props.marginTop || '20px'} 0 8.5px 0;
font-weight: ${props => props.theme.fontWeight.bold};
`;

View File

@ -1,14 +1,16 @@
// @flow
import React from 'react';
import React, { type Element } from 'react';
import styled from 'styled-components';
import theme from '../theme';
const getDefaultStyles = t => styled[t]`
border-radius: ${props => props.theme.boxBorderRadius};
border: none;
background-color: ${props => props.theme.colors.inputBackground};
background-color: ${props => props.bgColor || props.theme.colors.inputBackground};
color: ${props => props.theme.colors.text};
padding: 15px;
padding-right: ${props => (props.withRightElement ? '85px' : '15px')};
width: 100%;
outline: none;
font-family: ${props => props.theme.fontFamily};
@ -18,6 +20,16 @@ const getDefaultStyles = t => styled[t]`
}
`;
const Wrapper = styled.div`
position: relative;
`;
const RightElement = styled.div`
position: absolute;
top: 15px;
right: 15px;
`;
const Input = getDefaultStyles('input');
const Textarea = getDefaultStyles('textarea');
@ -31,19 +43,34 @@ type Props = {
type?: string,
step?: number,
name?: string,
renderRight?: () => Element<*> | null,
bgColor?: string,
};
export const InputComponent = ({
inputType,
bgColor,
onChange = () => {},
renderRight = () => null,
...props
}: Props) => {
const rightElement = renderRight();
const inputTypes = {
input: () => (
<Input onChange={evt => onChange(evt.target.value)} {...props} />
<Input
onChange={evt => onChange(evt.target.value)}
withRightElement={Boolean(rightElement)}
bgColor={bgColor}
{...props}
/>
),
textarea: () => (
<Textarea onChange={evt => onChange(evt.target.value)} {...props} />
<Textarea
onChange={evt => onChange(evt.target.value)}
bgColor={bgColor}
{...props}
/>
),
};
@ -51,7 +78,12 @@ export const InputComponent = ({
throw new Error(`Invalid input type: ${String(inputType)}`);
}
return inputTypes[inputType || 'input']();
return (
<Wrapper>
{inputTypes[inputType || 'input']()}
{rightElement && <RightElement>{rightElement}</RightElement>}
</Wrapper>
);
};
InputComponent.defaultProps = {
@ -60,4 +92,9 @@ InputComponent.defaultProps = {
disabled: false,
type: 'text',
name: '',
renderRight: () => null,
onChange: () => {},
onFocus: () => {},
step: 1,
bgColor: theme.colors.inputBackground,
};

View File

@ -1,6 +1,7 @@
// @flow
import React from 'react';
import React, { PureComponent } from 'react';
import styled from 'styled-components';
import { Transition, animated } from 'react-spring';
import CircleProgressComponent from 'react-circle';
import { TextComponent } from './text';
@ -35,18 +36,59 @@ const Logo = styled.img`
left: calc(50% - 25px);
`;
export const LoadingScreen = ({ progress }: { progress: number }) => (
<Wrapper id='loading-screen'>
<CircleWrapper>
<Logo src={zcashLogo} alt='Zcash logo' />
<CircleProgressComponent
progress={progress}
responsive
showPercentage={false}
progressColor={theme.colors.activeItem}
bgColor={theme.colors.inactiveItem}
/>
</CircleWrapper>
<TextComponent value='ZEC Wallet Starting' />
</Wrapper>
);
type Props = {
progress: number,
};
type State = {
start: boolean,
};
const TIME_DELAY_ANIM = 100;
export class LoadingScreen extends PureComponent<Props, State> {
state = { start: false };
componentDidMount() {
setTimeout(() => {
this.setState(() => ({ start: true }));
}, TIME_DELAY_ANIM);
}
render() {
const { start } = this.state;
const { progress } = this.props;
return (
<Wrapper>
<Transition
native
items={start}
enter={[{ height: 'auto' }]}
leave={{ height: 0 }}
from={{
position: 'absolute',
overflow: 'hidden',
height: 0,
}}
>
{() => props => (
<animated.div style={props}>
<CircleWrapper>
<Logo src={zcashLogo} alt='Zcash logo' />
<CircleProgressComponent
progress={progress}
responsive
showPercentage={false}
progressColor={theme.colors.activeItem}
bgColor={theme.colors.inactiveItem}
/>
</CircleWrapper>
<TextComponent value='ZEC Wallet Starting' />
</animated.div>
)}
</Transition>
</Wrapper>
);
}
}

View File

@ -6,13 +6,16 @@ import { TextComponent } from './text';
import ChevronUp from '../assets/images/chevron-up.svg';
import ChevronDown from '../assets/images/chevron-down.svg';
import theme from '../theme';
/* eslint-disable max-len */
const SelectWrapper = styled.div`
align-items: center;
display: flex;
flex-direction: row;
border-radius: ${props => props.theme.boxBorderRadius};
border: none;
background-color: ${props => props.theme.colors.inputBackground};
background-color: ${props => props.bgColor || props.theme.colors.inputBackground};
color: ${props => props.theme.colors.text};
width: 100%;
outline: none;
@ -25,6 +28,7 @@ const SelectWrapper = styled.div`
props.placement
}-right-radius: 0;`}
`;
/* eslint-enable max-len */
const ValueWrapper = styled.div`
width: 95%;
@ -51,11 +55,8 @@ const SelectMenuButton = styled.button`
padding: 3px 7px;
outline: none;
background-color: transparent;
border: 1px solid ${props => props.theme.colors.text};
border: 1px solid ${props => (props.isOpen ? props.theme.colors.primary : '#29292D')};
border-radius: 100%;
box-shadow: ${props => `0px 0px 10px 0px ${
props.theme.colors.selectButtonShadow
}, 0px 0px 10px 0px ${props.theme.colors.selectButtonShadow} inset`};
`;
/* eslint-enable max-len */
@ -69,7 +70,7 @@ const OptionsWrapper = styled.div`
flex-direction: column;
position: absolute;
width: 100%;
${props => `${props.placement}: ${`-${props.optionsAmount * 60}px`}`};
${props => `${props.placement}: ${`-${props.optionsAmount * 62}px`}`};
overflow-y: auto;
`;
@ -77,7 +78,8 @@ const Option = styled.button`
border: none;
background: none;
height: 60px;
background-color: ${props => props.theme.colors.inputBackground};
background-color: ${
props => props.bgColor || props.theme.colors.inputBackground};
cursor: pointer;
z-index: 99;
text-transform: capitalize;
@ -93,6 +95,7 @@ type Props = {
placeholder?: string,
onChange: string => void,
placement?: 'top' | 'bottom',
bgColor?: string,
};
type State = {
isOpen: boolean,
@ -106,6 +109,7 @@ export class SelectComponent extends PureComponent<Props, State> {
static defaultProps = {
placeholder: '',
placement: 'bottom',
bgColor: theme.colors.inputBackground,
};
onSelect = (value: string) => {
@ -130,7 +134,7 @@ export class SelectComponent extends PureComponent<Props, State> {
const { placement } = this.props;
const { isOpen } = this.state;
if (placement === 'bottom') {
if (placement === 'top') {
return isOpen ? ChevronUp : ChevronDown;
}
@ -139,7 +143,7 @@ export class SelectComponent extends PureComponent<Props, State> {
render() {
const {
value, options, placeholder, placement,
value, options, placeholder, placement, bgColor,
} = this.props;
const { isOpen } = this.state;
@ -149,12 +153,13 @@ export class SelectComponent extends PureComponent<Props, State> {
isOpen={isOpen}
placement={placement}
onClick={() => this.setState(() => ({ isOpen: !isOpen }))}
bgColor={bgColor}
>
<ValueWrapper hasValue={Boolean(value)}>
{this.getSelectedLabel(value) || placeholder}
</ValueWrapper>
<SelectMenuButtonWrapper>
<SelectMenuButton>
<SelectMenuButton isOpen={isOpen}>
<Icon src={this.getSelectIcon()} />
</SelectMenuButton>
</SelectMenuButtonWrapper>
@ -169,6 +174,7 @@ export class SelectComponent extends PureComponent<Props, State> {
id={optionValue}
key={label + optionValue}
onClick={() => this.onSelect(optionValue)}
bgColor={bgColor}
>
<TextComponent value={label} />
</Option>

View File

@ -43,6 +43,7 @@ const StatusPillLabel = styled(TextComponent)`
text-transform: uppercase;
font-size: 10px;
padding-top: 1px;
user-select: none;
`;
type Props = {};
@ -51,17 +52,17 @@ type State = {
type: string,
icon: string,
progress: number,
isSynching: boolean,
isSyncing: boolean,
};
export class StatusPill extends Component<Props, State> {
timer: ?IntervalID = null;
state = {
type: 'synching',
type: 'syncing',
icon: syncIcon,
progress: 0,
isSynching: true,
isSyncing: true,
};
componentDidMount() {
@ -88,7 +89,7 @@ export class StatusPill extends Component<Props, State> {
? {
type: 'ready',
icon: readyIcon,
isSynching: false,
isSyncing: false,
}
: {}),
});
@ -100,13 +101,13 @@ export class StatusPill extends Component<Props, State> {
render() {
const {
type, icon, progress, isSynching,
type, icon, progress, isSyncing,
} = this.state;
const showPercent = isSynching ? `(${progress.toFixed(2)}%)` : '';
const showPercent = isSyncing ? `(${progress.toFixed(2)}%)` : '';
return (
<Wrapper id='status-pill'>
<Icon src={icon} animated={isSynching} />
<Icon src={icon} animated={isSyncing} />
<StatusPillLabel value={`${type} ${showPercent}`} />
</Wrapper>
);

View File

@ -23,7 +23,7 @@ const Wrapper = styled(RowComponent)`
cursor: pointer;
&:hover {
background-color: #101010;
background-color: #0a0a0a;
}
`;
@ -39,6 +39,14 @@ const TransactionTypeLabel = styled(TextComponent)`
text-transform: capitalize;
`;
const TransactionAddress = styled(TextComponent)`
color: #a7a7a7;
${Wrapper}:hover & {
color: #fff;
}
`;
const TransactionTime = styled(TextComponent)`
color: ${props => props.theme.colors.inactiveItem};
`;
@ -102,7 +110,7 @@ export const TransactionItemComponent = ({
<TransactionTime value={transactionTime} />
</TransactionColumn>
</RowComponent>
<TextComponent value={transactionAddress} align='left' />
<TransactionAddress value={transactionAddress} />
</RowComponent>
<ColumnComponent alignItems='flex-end'>
<TextComponent

View File

@ -34,6 +34,21 @@ const Input = styled.input`
}
`;
const Btn = styled(Button)`
border-width: 1px;
font-weight: ${props => props.theme.fontWeight.light};
border-color: ${props => (props.isVisible
? props.theme.colors.primary : props.theme.colors.buttonBorderColor
)};
padding: 6px 10px;
width: 50%;
img {
height: 12px;
width: 16px;
}
`;
const QRCodeWrapper = styled.div`
align-items: center;
display: flex;
@ -83,11 +98,12 @@ export class WalletAddress extends Component<Props, State> {
onChange={() => {}}
onFocus={event => event.currentTarget.select()}
/>
<Button
<Btn
icon={eyeIcon}
label={`${isVisible ? 'Hide' : 'Show'} full address and QR Code`}
onClick={toggleVisibility}
variant='secondary'
isVisible={isVisible}
/>
</AddressWrapper>
{isVisible

View File

@ -4,8 +4,6 @@ import styled from 'styled-components';
import { TextComponent } from './text';
import { RowComponent } from './row';
import { DropdownComponent } from './dropdown';
import MenuIcon from '../assets/images/menu_icon.svg';
import formatNumber from '../utils/formatNumber';
@ -47,40 +45,11 @@ const ShieldedValue = styled(Label)`
color: ${props => props.theme.colors.activeItem};
`;
const SeeMoreButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
outline: none;
border-style: solid;
border-radius: 100%;
border-width: 1px;
border-color: ${props => (props.isOpen
? props.theme.colors.activeItem
: props.theme.colors.inactiveItem)};
background-color: transparent;
padding: 5px;
cursor: pointer;
position: absolute;
right: 10px;
top: 10px;
&:hover {
border-color: ${props => props.theme.colors.activeItem};
}
`;
const SeeMoreIcon = styled.img`
width: 25px;
height: 25px;
`;
type Props = {
total: number,
shielded: number,
transparent: number,
zecPrice: number,
addresses: string[],
};
export const WalletSummaryComponent = ({
@ -88,19 +57,12 @@ export const WalletSummaryComponent = ({
shielded,
transparent,
zecPrice,
addresses,
}: Props) => (
<Wrapper>
<DropdownComponent
label='All Addresses'
renderTrigger={(toggleVisibility, isOpen) => (
<SeeMoreButton onClick={toggleVisibility} isOpen={isOpen}>
<SeeMoreIcon src={MenuIcon} alt='Menu Icon' />
</SeeMoreButton>
)}
options={addresses.map(addr => ({ label: addr, onClick: x => x }))}
<AllAddresses
value='ALL ADDRESSES'
isBold
/>
<AllAddresses value='ALL ADDRESSES' isBold />
<ValueBox>
<TextComponent
size={theme.fontSize.medium * 2.5}
@ -129,7 +91,11 @@ export const WalletSummaryComponent = ({
/>
</ValueBox>
<ValueBox>
<Label value='&#9679; TRANSPARENT' isBold size={theme.fontSize.small} />
<Label
value='&#9679; TRANSPARENT'
isBold
size={theme.fontSize.small}
/>
<TextComponent
value={`ZEC ${formatNumber({ value: transparent })}`}
isBold

View File

@ -1,8 +1,8 @@
// @flow
export default {
LOW: 1,
MEDIUM: 5,
HIGH: 9,
LOW: 0.001,
MEDIUM: 0.005,
HIGH: 0.009,
CUSTOM: 'custom',
};

View File

@ -1,13 +1,43 @@
// @flow
import eres from 'eres';
import { connect } from 'react-redux';
import { ReceiveView } from '../views/receive';
import type { AppState } from '../types/app-state';
import {
loadAddressesSuccess,
loadAddressesError,
} from '../redux/modules/receive';
const mapStateToProps = ({ walletSummary }: AppState) => ({
addresses: walletSummary.addresses,
import rpc from '../../services/api';
import type { AppState } from '../types/app-state';
import type { Dispatch } from '../types/redux';
const mapStateToProps = ({ receive }: AppState) => ({
addresses: receive.addresses,
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
loadAddresses: async () => {
const [zAddressesErr, zAddresses] = await eres(rpc.z_listaddresses());
const [tAddressesErr, transparentAddresses] = await eres(
rpc.getaddressesbyaccount(''),
);
if (zAddressesErr || tAddressesErr) return dispatch(loadAddressesError({ error: 'Something went wrong!' }));
dispatch(
loadAddressesSuccess({
addresses: [...zAddresses, ...transparentAddresses],
}),
);
},
});
// $FlowFixMe
export const ReceiveContainer = connect(mapStateToProps)(ReceiveView);
export const ReceiveContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(ReceiveView);

View File

@ -3,14 +3,18 @@ import { connect } from 'react-redux';
import eres from 'eres';
import { BigNumber } from 'bignumber.js';
import store from '../../config/electron-store';
import rpc from '../../services/api';
import { SendView } from '../views/send';
import {
loadZECPrice,
sendTransaction,
sendTransactionSuccess,
sendTransactionError,
resetSendTransaction,
validateAddressSuccess,
validateAddressError,
} from '../redux/modules/send';
import filterObjectNullKeys from '../utils/filterObjectNullKeys';
@ -18,6 +22,11 @@ import filterObjectNullKeys from '../utils/filterObjectNullKeys';
import type { AppState } from '../types/app-state';
import type { Dispatch } from '../types/redux';
import {
loadAddressesSuccess,
loadAddressesError,
} from '../redux/modules/receive';
export type SendTransactionInput = {
from: string,
to: string,
@ -26,13 +35,14 @@ export type SendTransactionInput = {
memo: string,
};
const mapStateToProps = ({ walletSummary, sendStatus }: AppState) => ({
const mapStateToProps = ({ walletSummary, sendStatus, receive }: AppState) => ({
balance: walletSummary.total,
zecPrice: walletSummary.zecPrice,
addresses: walletSummary.addresses,
zecPrice: sendStatus.zecPrice,
addresses: receive.addresses,
error: sendStatus.error,
isSending: sendStatus.isSending,
operationId: sendStatus.operationId,
isToAddressValid: sendStatus.isToAddressValid,
});
const mapDispatchToProps = (dispatch: Dispatch) => ({
@ -61,12 +71,87 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
),
);
// eslint-disable-next-line
// eslint-disable-next-line max-len
if (sendErr || !operationId) return dispatch(sendTransactionError({ error: sendErr.message }));
dispatch(sendTransactionSuccess({ operationId }));
/**
Output is a list of operation status objects.
[
{operationid: opid-12ee,
status: queued},
{operationid: opd-098a, status: executing},
{operationid: opid-9876, status: failed}
]
When the operation succeeds, the status object will also include the result.
{operationid: opid-0e0e, status:success, execution_time:25, result: {txid:af3887654,...}}
then the promise will only be resolved when a "success" or "failure" status is obtained
*/
const interval = setInterval(async () => {
const [, status] = await eres(rpc.z_getoperationstatus());
const operationStatus = status.find(({ id }) => operationId === id);
if (operationStatus && operationStatus.status === 'success') {
clearInterval(interval);
dispatch(
sendTransactionSuccess({ operationId: operationStatus.result.txid }),
);
}
if (operationStatus && operationStatus.status === 'failed') {
clearInterval(interval);
dispatch(
sendTransactionError({ error: operationStatus.error.message }),
);
}
}, 2000);
},
resetSendView: () => dispatch(resetSendTransaction()),
validateAddress: async ({ address }: { address: string }) => {
if (address.startsWith('z')) {
const [, validationResult] = await eres(rpc.z_validateaddress(address));
return dispatch(
validateAddressSuccess({
isValid: Boolean(validationResult && validationResult.isvalid),
}),
);
}
const [, validationResult] = await eres(rpc.validateaddress(address));
if (validationResult) {
return dispatch(
validateAddressSuccess({
isValid: Boolean(validationResult && validationResult.isvalid),
}),
);
}
return dispatch(validateAddressError());
},
loadAddresses: async () => {
const [zAddressesErr, zAddresses] = await eres(rpc.z_listaddresses());
const [tAddressesErr, transparentAddresses] = await eres(
rpc.getaddressesbyaccount(''),
);
if (zAddressesErr || tAddressesErr) return dispatch(loadAddressesError({ error: 'Something went wrong!' }));
dispatch(
loadAddressesSuccess({
addresses: [...zAddresses, ...transparentAddresses],
}),
);
},
loadZECPrice: () => dispatch(
loadZECPrice({
value: store.get('ZEC_DOLLAR_PRICE'),
}),
),
});
// $FlowFixMe

View File

@ -0,0 +1,49 @@
// @flow
import type { Action } from '../../types/redux';
// Actions
export const LOAD_ADDRESSES_SUCCESS = 'LOAD_ADDRESSES_SUCCESS';
export const LOAD_ADDRESSES_ERROR = 'LOAD_ADDRESSES_ERROR';
export const loadAddressesSuccess = ({
addresses,
}: {
addresses: string[],
}) => ({
type: LOAD_ADDRESSES_SUCCESS,
payload: {
addresses,
},
});
export const loadAddressesError = ({ error }: { error: string }) => ({
type: LOAD_ADDRESSES_ERROR,
payload: { error },
});
export type State = {
addresses: string[],
error: string | null,
};
const initialState: State = {
addresses: [],
error: null,
};
export default (state: State = initialState, action: Action) => {
switch (action.type) {
case LOAD_ADDRESSES_SUCCESS:
return {
error: null,
addresses: action.payload.addresses,
};
case LOAD_ADDRESSES_ERROR:
return {
error: action.payload.error,
addresses: [],
};
default:
return state;
}
};

View File

@ -7,11 +7,13 @@ import type { RouterHistory } from 'react-router-dom';
import wallet from './wallet';
import transactions from './transactions';
import send from './send';
import receive from './receive';
// $FlowFixMe
export const createRootReducer = (history: RouterHistory) => combineReducers({
walletSummary: wallet,
transactions,
sendStatus: send,
receive,
router: connectRouter(history),
});

View File

@ -5,6 +5,9 @@ export const SEND_TRANSACTION = 'SEND_TRANSACTION';
export const SEND_TRANSACTION_SUCCESS = 'SEND_TRANSACTION_SUCCESS';
export const SEND_TRANSACTION_ERROR = 'SEND_TRANSACTION_ERROR';
export const RESET_SEND_TRANSACTION = 'RESET_SEND_TRANSACTION';
export const VALIDATE_ADDRESS_SUCCESS = 'VALIDATE_ADDRESS_SUCCESS';
export const VALIDATE_ADDRESS_ERROR = 'VALIDATE_ADDRESS_SUCCESS';
export const LOAD_ZEC_PRICE = 'LOAD_ZEC_PRICE';
export const sendTransaction = () => ({
type: SEND_TRANSACTION,
@ -34,34 +37,76 @@ export const resetSendTransaction = () => ({
payload: {},
});
export const validateAddressSuccess = ({ isValid }: { isValid: boolean }) => ({
type: VALIDATE_ADDRESS_SUCCESS,
payload: {
isValid,
},
});
export const validateAddressError = () => ({
type: VALIDATE_ADDRESS_ERROR,
payload: {},
});
export const loadZECPrice = ({ value }: { value: number }) => ({
type: LOAD_ZEC_PRICE,
payload: {
value,
},
});
export type State = {
isSending: boolean,
isToAddressValid: boolean,
error: string | null,
operationId: string | null,
zecPrice: number,
};
const initialState = {
const initialState: State = {
isSending: false,
error: null,
operationId: null,
isToAddressValid: false,
zecPrice: 0,
};
export default (state: State = initialState, action: Action): State => {
switch (action.type) {
case SEND_TRANSACTION:
return { isSending: true, error: null, operationId: null };
return {
...state,
isSending: true,
error: null,
operationId: null,
};
case SEND_TRANSACTION_SUCCESS:
return {
...state,
isSending: false,
error: null,
operationId: action.payload.operationId,
};
case SEND_TRANSACTION_ERROR:
return {
...state,
isSending: false,
error: action.payload.error,
operationId: null,
};
case VALIDATE_ADDRESS_SUCCESS:
return {
...state,
isToAddressValid: action.payload.isValid,
};
case VALIDATE_ADDRESS_ERROR:
return {
...state,
isToAddressValid: false,
};
case LOAD_ZEC_PRICE:
return { ...state, zecPrice: action.payload.value };
case RESET_SEND_TRANSACTION:
return initialState;
default:

View File

@ -8,10 +8,12 @@ import { normalize } from 'polished'; // eslint-disable-line
import { DARK } from './constants/themes';
const darkOne = '#F4B728';
const blackTwo = '#171717';
const lightOne = '#ffffff';
const brandOne = '#000';
// const brandTwo = '#3B3B3F';
const brandThree = '#5d5d65';
const buttonBorderColor = '#3e3c42';
const activeItem = '#F4B728';
const text = '#FFF';
const cardBackgroundColor = '#000';
@ -71,6 +73,9 @@ const appTheme = {
selectButtonShadow,
transactionsDetailsLabel,
statusPillLabel,
modalItemLabel: transactionsDate,
blackTwo,
buttonBorderColor,
},
sidebarWidth: '180px',
headerHeight: '60px',

View File

@ -3,9 +3,11 @@
import type { State as WalletSummaryState } from '../redux/modules/wallet';
import type { State as TransactionsState } from '../redux/modules/transactions';
import type { State as SendState } from '../redux/modules/send';
import type { State as ReceiveState } from '../redux/modules/receive';
export type AppState = {
walletSummary: WalletSummaryState,
transactions: TransactionsState,
sendStatus: SendState,
receive: ReceiveState,
};

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react';
import React, { PureComponent } from 'react';
import styled from 'styled-components';
import { InputLabelComponent } from '../components/input-label';
@ -8,6 +8,7 @@ import { WalletAddress } from '../components/wallet-address';
type Props = {
addresses: Array<string>,
loadAddresses: () => void,
};
const Wrapper = styled.div`
@ -19,21 +20,31 @@ const Row = styled(RowComponent)`
`;
const Label = styled(InputLabelComponent)`
margin: 0;
margin-left: 0;
margin-right: 0;
margin-bottom: 10px;
margin-top: 10px;
`;
export const ReceiveView = ({ addresses }: Props) => (
<Wrapper>
<Label value='Addresses: ' />
{(addresses || []).map(address => (
<Row
key={address}
alignItems='center'
justifyContent='space-between'
>
<WalletAddress address={address} />
</Row>
))}
</Wrapper>
);
export class ReceiveView extends PureComponent<Props> {
componentDidMount() {
const { loadAddresses } = this.props;
loadAddresses();
}
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>
);
}
}

View File

@ -1,6 +1,6 @@
// @flow
import React, { PureComponent } from 'react';
import styled from 'styled-components';
import styled, { keyframes } from 'styled-components';
import { BigNumber } from 'bignumber.js';
import FEES from '../constants/fees';
@ -13,12 +13,39 @@ 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/formatNumber';
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%;
@ -29,6 +56,28 @@ const SendWrapper = styled(ColumnComponent)`
width: 25%;
`;
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 => (props.isEmpty ? '0' : '1')};
color: #fff;
z-index: 10;
}
`;
const AmountInput = styled(InputComponent)`
padding-left: ${props => (props.isEmpty ? '15' : '50')}px;
`;
const ShowFeeButton = styled.button`
background: none;
border: none;
@ -36,12 +85,32 @@ const ShowFeeButton = styled.button`
width: 100%;
color: ${props => props.theme.colors.text};
outline: none;
display: flex;
align-items: center;
margin-top: 30px;
opacity: 0.7;
&:hover {
text-decoration: underline;
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: ${props => props.theme.colors.cardBackgroundColor};
border-radius: 6px;
padding: 13px 19px;
margin-bottom: 20px;
margin-top: 10px;
`;
const InfoCard = styled.div`
width: 100%;
background-color: ${props => props.theme.colors.cardBackgroundColor};
@ -66,18 +135,62 @@ const FormButton = styled(Button)`
margin: 10px 0;
`;
const SuccessWrapper = styled(ColumnComponent)`
const ModalContent = styled(ColumnComponent)`
min-height: 400px;
align-items: center;
justify-content: center;
height: 100%;
p {
word-break: break-all;
}
`;
type Props = SendState & {
const ConfirmItemWrapper = styled(RowComponent)`
padding: 22.5px 33.5px;
width: 100%;
`;
const ItemLabel = styled(TextComponent)`
font-weight: ${props => props.theme.fontWeight.bold};
font-size: ${props => props.theme.fontSize.small};
color: ${props => 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 => props.theme.fontWeight.bold};
`;
const SendUSDValue = styled(TextComponent)`
opacity: 0.5;
font-weight: ${props => 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;
`;
type Props = {
...SendState,
balance: number,
zecPrice: number,
addresses: string[],
sendTransaction: SendTransactionInput => void,
resetSendView: () => void,
validateAddress: ({ address: string }) => void,
loadAddresses: () => void,
loadZECPrice: () => void,
};
type State = {
@ -93,7 +206,7 @@ type State = {
const initialState = {
showFee: false,
from: '',
amount: '0',
amount: '',
to: '',
feeType: FEES.LOW,
fee: FEES.LOW,
@ -104,40 +217,38 @@ export class SendView extends PureComponent<Props, State> {
state = initialState;
componentDidMount() {
const { resetSendView } = this.props;
const { resetSendView, loadAddresses, loadZECPrice } = this.props;
resetSendView();
loadAddresses();
loadZECPrice();
}
handleChange = (field: string) => (value: string) => {
if (field === 'amount') {
if (value !== '') {
this.setState(() => ({
[field]: value,
}));
}
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) => {
this.setState(
{
feeType: value,
if (value === FEES.CUSTOM) {
this.setState({
feeType: FEES.CUSTOM,
fee: null,
},
() => {
if (
value === String(FEES.LOW)
|| value === String(FEES.MEDIUM)
|| value === String(FEES.HIGH)
) {
this.setState(() => ({
fee: Number(value),
}));
}
},
);
});
} else {
const fee = new BigNumber(value);
this.setState({
feeType: fee.toString(),
fee: fee.toNumber(),
});
}
};
handleSubmit = () => {
@ -157,32 +268,153 @@ export class SendView extends PureComponent<Props, State> {
});
};
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;
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 ? (
<RowComponent alignItems='center'>
<ValidateStatusIcon src={ValidIcon} />
<ItemLabel value='VALID' color={theme.colors.transactionReceived} />
</RowComponent>
) : (
<RowComponent alignItems='center'>
<ValidateStatusIcon src={InvalidIcon} />
<ItemLabel value='INVALID' color={theme.colors.transactionSent} />
</RowComponent>
);
};
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 (
<>
<Loader src={LoadingIcon} />
<TextComponent value='Processing transaction...' />
</>
);
}
if (operationId) {
return (
<>
<TextComponent value={`Transaction ID: ${operationId}`} align='center' />
<button
type='button'
onClick={() => {
this.reset();
toggle();
}}
>
Send again!
</button>
</>
);
}
if (error) return <TextComponent value={error} />;
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' />
</>
);
};
render() {
const {
addresses,
balance,
zecPrice,
isSending,
error,
operationId,
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.00' : 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(amount).toNumber(),
value: new BigNumber(fixedAmount).toFormat(2),
append: 'ZEC ',
});
const valueSentInUsd = formatNumber({
@ -190,20 +422,9 @@ export class SendView extends PureComponent<Props, State> {
append: 'USD $',
});
return operationId ? (
<SuccessWrapper id='send-success-wrapper'>
<TextComponent value={`Processing operation: ${operationId}`} />
<TextComponent value={`Amount: ${amount}`} />
<TextComponent value={`From: ${from}`} />
<TextComponent value={`To: ${to}`} />
<button type='button' onClick={this.reset}>
Send again!
</button>
</SuccessWrapper>
) : (
<RowComponent id='send-wrapper' justifyContent='space-between'>
return (
<RowComponent justifyContent='space-between'>
<FormWrapper>
{error && <TextComponent id='send-error-text' value={error} />}
<InputLabelComponent value='From' />
<SelectComponent
onChange={this.handleChange('from')}
@ -212,20 +433,22 @@ export class SendView extends PureComponent<Props, State> {
options={addresses.map(addr => ({ value: addr, label: addr }))}
/>
<InputLabelComponent value='Amount' />
<InputComponent
name='amount'
type='number'
onChange={this.handleChange('amount')}
value={String(amount)}
placeholder='ZEC 0.0'
step={0.01}
/>
<AmountWrapper isEmpty={isEmpty}>
<AmountInput
isEmpty={isEmpty}
type='number'
onChange={this.handleChange('amount')}
value={String(amount)}
placeholder='ZEC 0.0'
min={0.01}
/>
</AmountWrapper>
<InputLabelComponent value='To' />
<InputComponent
onChange={this.handleChange('to')}
value={to}
placeholder='Enter Address'
name='to'
renderRight={to ? this.renderValidationStatus : () => null}
/>
<InputLabelComponent value='Memo' />
<InputComponent
@ -237,43 +460,38 @@ 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 }))}
>
<TextComponent
paddingTop='10px'
value={`${showFee ? 'Hide' : 'Show'} Additional Options`}
align='right'
/>
<SeeMoreIcon src={MenuIcon} alt='Show more icon' />
<TextComponent value={`${showFee ? 'Hide' : 'Show'} Additional Options`} />
</ShowFeeButton>
{showFee && (
<RowComponent
id='send-fee-wrapper'
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}
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'
/>
</ColumnComponent>
</RowComponent>
<FeeWrapper>
<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}
/>
</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>
)}
{feeType === FEES.CUSTOM && (
<TextComponent value='Custom fees may compromise your privacy since fees are transparent' />
@ -286,22 +504,33 @@ export class SendView extends PureComponent<Props, State> {
<TextComponent value={zecBalance} size={1.25} isBold />
<InfoCardUSD value={zecBalanceInUsd} size={0.84375} />
</InfoContent>
<Divider opacity={0.5} />
<Divider opacity={0.3} />
<InfoContent>
<InfoCardLabel value='Sending' />
<TextComponent value={valueSent} size={1.25} isBold />
<InfoCardUSD value={valueSentInUsd} size={0.84375} />
</InfoContent>
</InfoCard>
<FormButton
id='send-submit-button'
label='Send'
variant='secondary'
focused
onClick={this.handleSubmit}
isLoading={isSending}
disabled={!from || !amount || !to || !fee}
/>
<ConfirmDialogComponent
title='Please Confirm Transaction Details'
onConfirm={this.handleSubmit}
renderTrigger={toggle => (
<FormButton
label='Send'
variant='secondary'
focused
onClick={() => this.showModal(toggle)}
/>
)}
showButtons={!isSending && !error && !operationId}
onClose={this.reset}
>
{toggle => (
<ModalContent>
{this.renderModalContent({ valueSent, valueSentInUsd, toggle })}
</ModalContent>
)}
</ConfirmDialogComponent>
<FormButton label='Cancel' variant='secondary' />
</SendWrapper>
</RowComponent>

View File

@ -47,6 +47,25 @@ const ClipboardButton = styled(Clipboard)`
margin-left: 5px;
`;
const SettingsWrapper = styled.div`
margin-bottom: 45px;
min-width: 200px;
width: 37%;
`;
const SettingsTitle = styled(TextComponent)`
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;
`;
const SettingsContent = styled(TextComponent)`
margin-bottom: 20px;
margin-top: 10px;
`;
type Key = {
zAddress: string,
key: string,
@ -198,94 +217,117 @@ export class SettingsView extends PureComponent<Props, State> {
<ConfirmDialogComponent
title='Export view keys'
renderTrigger={toggleVisibility => (
<Btn label='Export view keys' onClick={toggleVisibility} />
<SettingsWrapper>
<SettingsTitle value='Export view keys' />
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
<Btn label='Export view keys' onClick={toggleVisibility} />
</SettingsWrapper>
)}
onConfirm={this.exportViewKeys}
showButtons={!successExportViewKeys}
width={750}
>
<ModalContent>
{successExportViewKeys ? (
viewKeys.map(({ zAddress, key }) => (
<>
<InputLabelComponent value={zAddress} />
<RowComponent alignItems='center'>
<InputComponent
value={key}
onFocus={(event) => {
event.currentTarget.select();
}}
/>
<ClipboardButton text={key} />
</RowComponent>
</>
))
) : (
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' />
)}
</ModalContent>
{() => (
<ModalContent>
{successExportViewKeys ? (
viewKeys.map(({ zAddress, key }) => (
<>
<InputLabelComponent value={zAddress} />
<RowComponent alignItems='center'>
<InputComponent
value={key}
onFocus={(event) => {
event.currentTarget.select();
}}
/>
<ClipboardButton text={key} />
</RowComponent>
</>
))
) : (
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' />
)}
</ModalContent>
)}
</ConfirmDialogComponent>
<ConfirmDialogComponent
title='Export private keys'
renderTrigger={toggleVisibility => (
<Btn label='Export private keys' onClick={toggleVisibility} />
<SettingsWrapper>
<SettingsTitle value='Export private keys' />
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
<Btn label='Export private keys' onClick={toggleVisibility} />
</SettingsWrapper>
)}
onConfirm={this.exportPrivateKeys}
showButtons={!successExportPrivateKeys}
width={750}
>
<ModalContent>
{successExportPrivateKeys ? (
privateKeys.map(({ zAddress, key }) => (
<>
<InputLabelComponent value={zAddress} />
<RowComponent alignItems='center'>
<InputComponent
value={key}
onFocus={(event) => {
event.currentTarget.select();
}}
/>
<ClipboardButton text={key} />
</RowComponent>
</>
))
) : (
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' />
)}
</ModalContent>
{() => (
<ModalContent>
{successExportPrivateKeys ? (
privateKeys.map(({ zAddress, key }) => (
<>
<InputLabelComponent value={zAddress} />
<RowComponent alignItems='center'>
<InputComponent
value={key}
onFocus={(event) => {
event.currentTarget.select();
}}
/>
<ClipboardButton text={key} />
</RowComponent>
</>
))
) : (
<TextComponent value='Ut id vulputate arcu. Curabitur mattis aliquam magna sollicitudin vulputate. Morbi tempus bibendum porttitor. Quisque dictum ac ipsum a luctus. Donec et lacus ac erat consectetur molestie a id erat.' />
)}
</ModalContent>
)}
</ConfirmDialogComponent>
<ConfirmDialogComponent
title='Import private keys'
renderTrigger={toggleVisibility => (
<Btn label='Import private keys' onClick={toggleVisibility} />
<SettingsWrapper>
<SettingsTitle value='Import private keys' />
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
<Btn label='Import private keys' onClick={toggleVisibility} />
</SettingsWrapper>
)}
onConfirm={this.importPrivateKeys}
showButtons={!successImportPrivateKeys}
width={900}
isLoading={isLoading}
>
<ModalContent>
<InputLabelComponent value='Please paste your private keys here, one per line. The keys will be imported into your zcashd node' />
<InputComponent
value={importedPrivateKeys}
onChange={value => this.setState({ importedPrivateKeys: value })}
inputType='textarea'
rows={10}
/>
{successImportPrivateKeys && (
<TextComponent
value='Private keys imported in your node'
align='center'
{() => (
<ModalContent>
<InputLabelComponent value='Please paste your private keys here, one per line. The keys will be imported into your zcashd node' />
<InputComponent
value={importedPrivateKeys}
onChange={value => this.setState({ importedPrivateKeys: value })
}
inputType='textarea'
rows={10}
/>
)}
{error && <TextComponent value={error} align='center' />}
</ModalContent>
{successImportPrivateKeys && (
<TextComponent
value='Private keys imported in your node'
align='center'
/>
)}
{error && <TextComponent value={error} align='center' />}
</ModalContent>
)}
</ConfirmDialogComponent>
<Btn label='Backup wallet.dat' onClick={this.backupWalletDat} />
<SettingsWrapper>
<SettingsTitle value='Backup Wallet' />
<SettingsContent value='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.' />
<Btn label='Backup wallet.dat' onClick={this.backupWalletDat} />
</SettingsWrapper>
</Wrapper>
);
};

View File

@ -59,11 +59,11 @@
"electron-positioner": "^4.1.0",
"eslint": "^5.8.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-flowtype": "^3.2.0",
"eslint-plugin-flowtype": "^3.2.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jest": "^22.1.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-react": "^7.12.4",
"file-loader": "^2.0.0",
"flow-bin": "^0.91.0",
"flow-coverage-report": "^0.6.0",
@ -117,6 +117,7 @@
"react-popover": "^0.5.10",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"react-spring": "^7.2.10",
"redux": "^4.0.1",
"redux-thunk": "^2.2.0",
"styled-components": "^4.1.1",

View File

@ -746,8 +746,8 @@ export type APIMethods = {
z_exportwallet: (filename: string) => Promise<string>,
z_getbalance: (address: string, minconf?: number) => Promise<number>,
z_getnewaddress: (type: ?string) => Promise<string>,
z_getoperationresult: (operationid?: string[]) => Promise<Object[]>,
z_getoperationstatus: (operationid?: string[]) => Promise<Object[]>,
z_getoperationresult: (operationid?: string) => Promise<Object[]>,
z_getoperationstatus: (operationids?: string) => Promise<Object[]>,
z_gettotalbalance: (
minconf?: number,
includeWatchonly?: boolean,

View File

@ -6436,6 +6436,18 @@ error-stack-parser@^2.0.0:
dependencies:
stackframe "^1.0.4"
es-abstract@^1.11.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==
dependencies:
es-to-primitive "^1.2.0"
function-bind "^1.1.1"
has "^1.0.3"
is-callable "^1.1.4"
is-regex "^1.0.4"
object-keys "^1.0.12"
es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
@ -6447,7 +6459,7 @@ es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0:
is-callable "^1.1.3"
is-regex "^1.0.4"
es-to-primitive@^1.1.1:
es-to-primitive@^1.1.1, es-to-primitive@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377"
integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==
@ -6572,10 +6584,10 @@ eslint-module-utils@^2.2.0:
debug "^2.6.8"
pkg-dir "^1.0.0"
eslint-plugin-flowtype@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.2.0.tgz#824364ed5940a404b91326fdb5a313a2a74760df"
integrity sha512-baJmzngM6UKbEkJ5OY3aGw2zjXBt5L2QKZvTsOlXX7yHKIjNRrlJx2ods8Rng6EdqPR9rVNIQNYHpTs0qfn2qA==
eslint-plugin-flowtype@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-3.2.1.tgz#45e032aee54e695dfc41a891e92b7afedfc62c77"
integrity sha512-1lymqM8Cawxu5xsS8TaCrLWJYUmUdoG4hCfa7yWOhCf0qZn/CvI8FxqkhdOP6bAosBn5zeYxKe3Q/4rfKN8a+A==
dependencies:
lodash "^4.17.10"
@ -6614,16 +6626,18 @@ eslint-plugin-jsx-a11y@^6.0.3:
has "^1.0.3"
jsx-ast-utils "^2.0.1"
eslint-plugin-react@^7.7.0:
version "7.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz#c01a7af6f17519457d6116aa94fc6d2ccad5443c"
integrity sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==
eslint-plugin-react@^7.12.4:
version "7.12.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.12.4.tgz#b1ecf26479d61aee650da612e425c53a99f48c8c"
integrity sha512-1puHJkXJY+oS1t467MjbqjvX53uQ05HXwjqDgdbGBqf5j9eeydI54G3KwiJmWciQ0HTBacIKw2jgwSBSH3yfgQ==
dependencies:
array-includes "^3.0.3"
doctrine "^2.1.0"
has "^1.0.3"
jsx-ast-utils "^2.0.1"
object.fromentries "^2.0.0"
prop-types "^15.6.2"
resolve "^1.9.0"
eslint-restricted-globals@^0.1.1:
version "0.1.1"
@ -11742,6 +11756,16 @@ object.entries@^1.0.4:
function-bind "^1.1.0"
has "^1.0.1"
object.fromentries@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab"
integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==
dependencies:
define-properties "^1.1.2"
es-abstract "^1.11.0"
function-bind "^1.1.1"
has "^1.0.1"
object.getownpropertydescriptors@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
@ -13777,6 +13801,13 @@ react-sizes@^1.0.4:
lodash.throttle "^4.1.1"
prop-types "^15.6.0"
react-spring@^7.2.10:
version "7.2.10"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-7.2.10.tgz#eacbf429327bea058677c3a01af87b9c387f4aa6"
integrity sha512-bsbgQh1DzTqjwuD1tDZ7Nu04G5JhNNXdezkcJ5xXNboedN/MPe0K+X/MtypUxXBUCLYT0r5oKT3zj4STqVpnPA==
dependencies:
"@babel/runtime" "^7.0.0"
react-testing-library@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-5.3.1.tgz#d70ab711c8cef604fd70c9ec422d59e7e51ae112"