Merge branch 'develop' of github.com:andrerfneves/zec-react-wallet into develop
This commit is contained in:
commit
9eba0b5503
|
@ -0,0 +1,18 @@
|
|||
// @flow
|
||||
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { ascii2hex } from '../../app/utils/ascii-to-hexadecimal';
|
||||
|
||||
describe('filterObjectNullKeys', () => {
|
||||
test('should filter null keys from object', () => {
|
||||
expect(ascii2hex('zcash')).toEqual('7a63617368');
|
||||
expect(ascii2hex('some text with spaces')).toEqual(
|
||||
'736f6d652074657874207769746820737061636573',
|
||||
);
|
||||
expect(ascii2hex('0')).toEqual('30');
|
||||
expect(ascii2hex('16')).toEqual('3136');
|
||||
expect(ascii2hex()).toEqual('');
|
||||
expect(ascii2hex('')).toEqual('');
|
||||
});
|
||||
});
|
51
app/app.js
51
app/app.js
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
@ -8,18 +8,43 @@ import { ThemeProvider } from 'styled-components';
|
|||
import { configureStore, history } from './redux/create';
|
||||
import { Router } from './router/container';
|
||||
import theme, { GlobalStyle } from './theme';
|
||||
import electronStore from '../config/electron-store';
|
||||
import { DARK } from './constants/themes';
|
||||
|
||||
const store = configureStore({});
|
||||
|
||||
export const App = () => (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Fragment>
|
||||
<GlobalStyle />
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<Router />
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</Fragment>
|
||||
</ThemeProvider>
|
||||
);
|
||||
type Props = {};
|
||||
type State = {
|
||||
themeMode: string,
|
||||
};
|
||||
|
||||
export class App extends Component<Props, State> {
|
||||
state = {
|
||||
themeMode: electronStore.get('THEME_MODE') || DARK,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (!electronStore.has('THEME_MODE')) {
|
||||
electronStore.set('THEME_MODE', DARK);
|
||||
}
|
||||
|
||||
electronStore.onDidChange('THEME_MODE', newValue => this.setState({ themeMode: newValue }));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { themeMode } = this.state;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={{ ...theme, mode: themeMode }}>
|
||||
<Fragment>
|
||||
<GlobalStyle />
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<Router />
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</Fragment>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 747 B |
|
@ -4,17 +4,15 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import type { Node, ElementProps } from 'react';
|
||||
|
||||
type FlexProps =
|
||||
| {
|
||||
alignItems: string,
|
||||
justifyContent: string,
|
||||
}
|
||||
| Object;
|
||||
type FlexProps = PropsWithTheme<{
|
||||
alignItems: string,
|
||||
justifyContent: string,
|
||||
}>;
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: ${(props: FlexProps) => props.alignItems};
|
||||
justify-content: ${(props: FlexProps) => props.justifyContent};
|
||||
align-items: ${(props: FlexProps) => String(props.alignItems)};
|
||||
justify-content: ${(props: FlexProps) => String(props.justifyContent)};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -42,7 +42,7 @@ const ValueWrapper = styled.div`
|
|||
width: 95%;
|
||||
padding: 13px;
|
||||
opacity: ${(props: PropsWithTheme<{ hasValue: boolean }>) => (props.hasValue ? '1' : '0.2')};
|
||||
text-transform: capitalize;
|
||||
text-transform: ${(props: PropsWithTheme<{ capitalize: boolean }>) => (props.capitalize ? 'capitalize' : 'none')};
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -88,7 +88,7 @@ const Option = styled.button`
|
|||
background-color: #5d5d5d;
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
text-transform: capitalize;
|
||||
text-transform: ${(props: PropsWithTheme<{ capitalize: boolean }>) => (props.capitalize ? 'capitalize' : 'none')};
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #4e4b4b;
|
||||
|
||||
|
@ -110,6 +110,7 @@ type Props = {
|
|||
onChange: string => void,
|
||||
placement?: 'top' | 'bottom',
|
||||
bgColor?: string,
|
||||
capitalize?: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -125,6 +126,7 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
placeholder: '',
|
||||
placement: 'bottom',
|
||||
bgColor: theme.colors.inputBackground,
|
||||
capitalize: true,
|
||||
};
|
||||
|
||||
onSelect = (value: string) => {
|
||||
|
@ -162,7 +164,7 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
render() {
|
||||
const { isOpen } = this.state;
|
||||
const {
|
||||
value, options, placeholder, placement, bgColor,
|
||||
value, options, placeholder, placement, bgColor, capitalize,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -174,7 +176,7 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
onClick={() => this.setState(() => ({ isOpen: !isOpen }))}
|
||||
bgColor={bgColor}
|
||||
>
|
||||
<ValueWrapper hasValue={Boolean(value)}>
|
||||
<ValueWrapper hasValue={Boolean(value)} capitalize={capitalize}>
|
||||
{this.getSelectedLabel(value) || placeholder}
|
||||
</ValueWrapper>
|
||||
<SelectMenuButtonWrapper>
|
||||
|
@ -194,6 +196,7 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
key={label + optionValue}
|
||||
onClick={() => this.onSelect(optionValue)}
|
||||
bgColor={bgColor}
|
||||
capitalize={capitalize}
|
||||
>
|
||||
<TextComponent value={label} />
|
||||
</Option>
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
loadWalletSummarySuccess,
|
||||
loadWalletSummaryError,
|
||||
} from '../redux/modules/wallet';
|
||||
import sortBy from '../utils/sort-by';
|
||||
import { sortBy } from '../utils/sort-by';
|
||||
|
||||
import type { AppState } from '../types/app-state';
|
||||
import type { Dispatch } from '../types/redux';
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
resetSendTransaction,
|
||||
validateAddressSuccess,
|
||||
validateAddressError,
|
||||
loadAddressBalanceSuccess,
|
||||
loadAddressBalanceError,
|
||||
} from '../redux/modules/send';
|
||||
|
||||
import { filterObjectNullKeys } from '../utils/filter-object-null-keys';
|
||||
|
@ -23,10 +25,7 @@ import { filterObjectNullKeys } from '../utils/filter-object-null-keys';
|
|||
import type { AppState } from '../types/app-state';
|
||||
import type { Dispatch } from '../types/redux';
|
||||
|
||||
import {
|
||||
loadAddressesSuccess,
|
||||
loadAddressesError,
|
||||
} from '../redux/modules/receive';
|
||||
import { loadAddressesSuccess, loadAddressesError } from '../redux/modules/receive';
|
||||
|
||||
export type SendTransactionInput = {
|
||||
from: string,
|
||||
|
@ -36,8 +35,8 @@ export type SendTransactionInput = {
|
|||
memo: string,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({ walletSummary, sendStatus, receive }: AppState) => ({
|
||||
balance: walletSummary.total,
|
||||
const mapStateToProps = ({ sendStatus, receive }: AppState) => ({
|
||||
balance: sendStatus.addressBalance,
|
||||
zecPrice: sendStatus.zecPrice,
|
||||
addresses: receive.addresses,
|
||||
error: sendStatus.error,
|
||||
|
@ -144,6 +143,13 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
value: Number(store.get('ZEC_DOLLAR_PRICE')),
|
||||
}),
|
||||
),
|
||||
getAddressBalance: async ({ address }: { address: string }) => {
|
||||
const [err, balance] = await eres(rpc.z_getbalance(address));
|
||||
|
||||
if (err) return dispatch(loadAddressBalanceError({ error: "Can't load your balance address" }));
|
||||
|
||||
return dispatch(loadAddressBalanceSuccess({ balance }));
|
||||
},
|
||||
});
|
||||
|
||||
// $FlowFixMe
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import rpc from '../../services/api';
|
||||
import store from '../../config/electron-store';
|
||||
|
||||
import sortBy from '../utils/sort-by';
|
||||
import { sortBy } from '../utils/sort-by';
|
||||
|
||||
import type { AppState } from '../types/app-state';
|
||||
import type { Dispatch } from '../types/redux';
|
||||
|
|
|
@ -8,6 +8,8 @@ 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 LOAD_ADDRESS_BALANCE_SUCCESS = 'LOAD_ADDRESS_BALANCE_SUCCESS';
|
||||
export const LOAD_ADDRESS_BALANCE_ERROR = 'LOAD_ADDRESS_BALANCE_ERROR';
|
||||
|
||||
export const sendTransaction = () => ({
|
||||
type: SEND_TRANSACTION,
|
||||
|
@ -52,12 +54,27 @@ export const loadZECPrice = ({ value }: { value: number }) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const loadAddressBalanceSuccess = ({ balance }: { balance: number }) => ({
|
||||
type: LOAD_ADDRESS_BALANCE_SUCCESS,
|
||||
payload: {
|
||||
balance,
|
||||
},
|
||||
});
|
||||
|
||||
export const loadAddressBalanceError = ({ error }: { error: string }) => ({
|
||||
type: LOAD_ADDRESS_BALANCE_SUCCESS,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
export type State = {
|
||||
isSending: boolean,
|
||||
isToAddressValid: boolean,
|
||||
error: string | null,
|
||||
operationId: string | null,
|
||||
zecPrice: number,
|
||||
addressBalance: number,
|
||||
};
|
||||
|
||||
const initialState: State = {
|
||||
|
@ -66,6 +83,7 @@ const initialState: State = {
|
|||
operationId: null,
|
||||
isToAddressValid: false,
|
||||
zecPrice: 0,
|
||||
addressBalance: 0,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
|
@ -104,6 +122,10 @@ export default (state: State = initialState, action: Action): State => {
|
|||
};
|
||||
case LOAD_ZEC_PRICE:
|
||||
return { ...state, zecPrice: action.payload.value };
|
||||
case LOAD_ADDRESS_BALANCE_SUCCESS:
|
||||
return { ...state, addressBalance: action.payload.balance };
|
||||
case LOAD_ADDRESS_BALANCE_ERROR:
|
||||
return { ...state, error: action.payload.error };
|
||||
case RESET_SEND_TRANSACTION:
|
||||
return initialState;
|
||||
default:
|
||||
|
|
118
app/theme.js
118
app/theme.js
|
@ -12,7 +12,6 @@ const darkOne = '#F4B728';
|
|||
const blackTwo = '#171717';
|
||||
const lightOne = '#ffffff';
|
||||
const brandOne = '#000';
|
||||
// const brandTwo = '#3B3B3F';
|
||||
const brandThree = '#5d5d65';
|
||||
const buttonBorderColor = '#3e3c42';
|
||||
const activeItem = '#F4B728';
|
||||
|
@ -46,39 +45,106 @@ const appTheme: AppTheme = {
|
|||
small: 0.667,
|
||||
},
|
||||
colors: {
|
||||
// $FlowFixMe
|
||||
primary: theme('mode', {
|
||||
light: lightOne,
|
||||
dark: darkOne,
|
||||
}),
|
||||
// $FlowFixMe
|
||||
secondary: theme('mode', {
|
||||
light: darkOne,
|
||||
dark: lightOne,
|
||||
}),
|
||||
sidebarBg: brandOne,
|
||||
sidebarItem: brandThree,
|
||||
sidebarItemActive: activeItem,
|
||||
sidebarHoveredItem,
|
||||
sidebarHoveredItemLabel,
|
||||
cardBackgroundColor,
|
||||
text,
|
||||
activeItem,
|
||||
inactiveItem: brandThree,
|
||||
sidebarLogoGradientBegin,
|
||||
sidebarLogoGradientEnd,
|
||||
background,
|
||||
transactionSent,
|
||||
transactionReceived,
|
||||
transactionsDate,
|
||||
transactionsItemHovered,
|
||||
inputBackground: brandOne,
|
||||
selectButtonShadow,
|
||||
transactionsDetailsLabel,
|
||||
statusPillLabel,
|
||||
modalItemLabel: transactionsDate,
|
||||
blackTwo,
|
||||
buttonBorderColor,
|
||||
sidebarBg: theme('mode', {
|
||||
light: brandOne,
|
||||
dark: brandOne,
|
||||
}),
|
||||
sidebarItem: theme('mode', {
|
||||
light: brandThree,
|
||||
dark: brandThree,
|
||||
}),
|
||||
sidebarItemActive: theme('mode', {
|
||||
light: activeItem,
|
||||
dark: activeItem,
|
||||
}),
|
||||
sidebarHoveredItem: theme('mode', {
|
||||
light: sidebarHoveredItem,
|
||||
dark: sidebarHoveredItem,
|
||||
}),
|
||||
sidebarHoveredItemLabel: theme('mode', {
|
||||
light: sidebarHoveredItemLabel,
|
||||
dark: sidebarHoveredItemLabel,
|
||||
}),
|
||||
cardBackgroundColor: theme('mode', {
|
||||
light: cardBackgroundColor,
|
||||
dark: cardBackgroundColor,
|
||||
}),
|
||||
text: theme('mode', {
|
||||
light: '#000',
|
||||
dark: text,
|
||||
}),
|
||||
activeItem: theme('mode', {
|
||||
light: activeItem,
|
||||
dark: activeItem,
|
||||
}),
|
||||
inactiveItem: theme('mode', {
|
||||
light: brandThree,
|
||||
dark: brandThree,
|
||||
}),
|
||||
sidebarLogoGradientBegin: theme('mode', {
|
||||
light: sidebarLogoGradientBegin,
|
||||
dark: sidebarLogoGradientBegin,
|
||||
}),
|
||||
sidebarLogoGradientEnd: theme('mode', {
|
||||
light: sidebarLogoGradientEnd,
|
||||
dark: sidebarLogoGradientEnd,
|
||||
}),
|
||||
background: theme('mode', {
|
||||
light: '#FFF',
|
||||
dark: background,
|
||||
}),
|
||||
transactionSent: theme('mode', {
|
||||
light: transactionSent,
|
||||
dark: transactionSent,
|
||||
}),
|
||||
transactionReceived: theme('mode', {
|
||||
light: transactionReceived,
|
||||
dark: transactionReceived,
|
||||
}),
|
||||
transactionsDate: theme('mode', {
|
||||
light: transactionsDate,
|
||||
dark: transactionsDate,
|
||||
}),
|
||||
transactionsItemHovered: theme('mode', {
|
||||
light: transactionsItemHovered,
|
||||
dark: transactionsItemHovered,
|
||||
}),
|
||||
inputBackground: theme('mode', {
|
||||
light: brandOne,
|
||||
dark: brandOne,
|
||||
}),
|
||||
selectButtonShadow: theme('mode', {
|
||||
light: selectButtonShadow,
|
||||
dark: selectButtonShadow,
|
||||
}),
|
||||
transactionsDetailsLabel: theme('mode', {
|
||||
light: transactionsDetailsLabel,
|
||||
dark: transactionsDetailsLabel,
|
||||
}),
|
||||
statusPillLabel: theme('mode', {
|
||||
light: statusPillLabel,
|
||||
dark: statusPillLabel,
|
||||
}),
|
||||
modalItemLabel: theme('mode', {
|
||||
light: transactionsDate,
|
||||
dark: transactionsDate,
|
||||
}),
|
||||
blackTwo: theme('mode', {
|
||||
light: blackTwo,
|
||||
dark: blackTwo,
|
||||
}),
|
||||
buttonBorderColor: theme('mode', {
|
||||
light: buttonBorderColor,
|
||||
dark: buttonBorderColor,
|
||||
}),
|
||||
},
|
||||
sidebarWidth: '180px',
|
||||
headerHeight: '60px',
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// @flow
|
||||
export function ascii2hex(ascii: ?string): string {
|
||||
if (!ascii) return '';
|
||||
|
||||
return ascii
|
||||
.split('')
|
||||
.map(letter => letter.charCodeAt(0).toString(16))
|
||||
.join('');
|
||||
}
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
/* eslint-disable max-len */
|
||||
// $FlowFixMe
|
||||
export default <T>(field: string) => (arr: T[]): T[] => arr.sort((a, b) => (a[field] < b[field] ? 1 : -1));
|
||||
export const sortBy = <T>(field: string) => (arr: T[]): T[] => arr.sort((a, b) => (a[field] < b[field] ? 1 : -1));
|
||||
|
|
|
@ -18,6 +18,7 @@ import { Button } from '../components/button';
|
|||
import { ConfirmDialogComponent } from '../components/confirm-dialog';
|
||||
|
||||
import { formatNumber } from '../utils/format-number';
|
||||
import { ascii2hex } from '../utils/ascii-to-hexadecimal';
|
||||
|
||||
import type { SendTransactionInput } from '../containers/send';
|
||||
import type { State as SendState } from '../redux/modules/send';
|
||||
|
@ -27,6 +28,7 @@ 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 ArrowUpIcon from '../assets/images/arrow_up.png';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
|
@ -209,6 +211,35 @@ const RevealsMain = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
// $FlowFixMe
|
||||
const Checkbox = styled.input.attrs({
|
||||
type: 'checkbox',
|
||||
})`
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
const MaxAvailableAmount = styled.button`
|
||||
margin-top: -15px;
|
||||
margin-right: -15px;
|
||||
width: 45px;
|
||||
height: 48px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
border-left: ${props => `1px solid ${props.theme.colors.background}`};
|
||||
opacity: 0.8;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const MaxAvailableAmountImg = styled.img`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
...SendState,
|
||||
balance: number,
|
||||
|
@ -220,6 +251,7 @@ type Props = {
|
|||
validateAddress: ({ address: string }) => void,
|
||||
loadAddresses: () => void,
|
||||
loadZECPrice: () => void,
|
||||
getAddressBalance: ({ address: string }) => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
@ -230,6 +262,7 @@ type State = {
|
|||
feeType: string | number,
|
||||
fee: number | null,
|
||||
memo: string,
|
||||
isHexMemo: boolean,
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
|
@ -240,6 +273,7 @@ const initialState = {
|
|||
feeType: FEES.LOW,
|
||||
fee: FEES.LOW,
|
||||
memo: '',
|
||||
isHexMemo: false,
|
||||
};
|
||||
|
||||
export class SendView extends PureComponent<Props, State> {
|
||||
|
@ -253,18 +287,35 @@ export class SendView extends PureComponent<Props, State> {
|
|||
loadZECPrice();
|
||||
}
|
||||
|
||||
handleChange = (field: string) => (value: string) => {
|
||||
const { validateAddress } = this.props;
|
||||
handleChange = (field: string) => (value: string | number) => {
|
||||
const { validateAddress, getAddressBalance, balance } = this.props;
|
||||
const { fee, amount } = this.state;
|
||||
|
||||
if (field === 'to') {
|
||||
// eslint-disable-next-line max-len
|
||||
this.setState(() => ({ [field]: value }), () => validateAddress({ address: value }));
|
||||
this.setState(() => ({ [field]: value }), () => validateAddress({ address: String(value) }));
|
||||
} else if (field === 'amount') {
|
||||
const amountWithFee = new BigNumber(value).plus(fee || 0);
|
||||
|
||||
const validAmount = amountWithFee.isGreaterThan(balance)
|
||||
? new BigNumber(balance).minus(fee || 0).toNumber()
|
||||
: value;
|
||||
|
||||
this.setState(() => ({ [field]: validAmount }));
|
||||
} else {
|
||||
this.setState(() => ({ [field]: value }));
|
||||
if (field === 'from') getAddressBalance({ address: String(value) });
|
||||
|
||||
this.setState(
|
||||
() => ({ [field]: value }),
|
||||
() => {
|
||||
if (field === 'fee') this.handleChange('amount')(amount);
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleChangeFeeType = (value: string) => {
|
||||
const { amount } = this.state;
|
||||
|
||||
if (value === FEES.CUSTOM) {
|
||||
this.setState(() => ({
|
||||
feeType: FEES.CUSTOM,
|
||||
|
@ -273,16 +324,19 @@ export class SendView extends PureComponent<Props, State> {
|
|||
} else {
|
||||
const fee = new BigNumber(value);
|
||||
|
||||
this.setState(() => ({
|
||||
feeType: fee.toString(),
|
||||
fee: fee.toNumber(),
|
||||
}));
|
||||
this.setState(
|
||||
() => ({
|
||||
feeType: fee.toString(),
|
||||
fee: fee.toNumber(),
|
||||
}),
|
||||
() => this.handleChange('amount')(amount),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = () => {
|
||||
const {
|
||||
from, amount, to, memo, fee,
|
||||
from, amount, to, memo, fee, isHexMemo,
|
||||
} = this.state;
|
||||
const { sendTransaction } = this.props;
|
||||
|
||||
|
@ -293,7 +347,7 @@ export class SendView extends PureComponent<Props, State> {
|
|||
to,
|
||||
amount,
|
||||
fee,
|
||||
memo,
|
||||
memo: isHexMemo ? memo : ascii2hex(memo),
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -445,7 +499,7 @@ export class SendView extends PureComponent<Props, State> {
|
|||
append: 'USD $',
|
||||
});
|
||||
const valueSent = formatNumber({
|
||||
value: new BigNumber(fixedAmount).toFormat(2),
|
||||
value: new BigNumber(fixedAmount).toFormat(4),
|
||||
append: 'ZEC ',
|
||||
});
|
||||
const valueSentInUsd = formatNumber({
|
||||
|
@ -456,16 +510,25 @@ export class SendView extends PureComponent<Props, State> {
|
|||
return (
|
||||
<RowComponent id='send-wrapper' justifyContent='space-between'>
|
||||
<FormWrapper>
|
||||
<InputLabelComponent value='From' />
|
||||
<InputLabelComponent value='From an address' />
|
||||
<SelectComponent
|
||||
onChange={this.handleChange('from')}
|
||||
value={from}
|
||||
placeholder='Select a address'
|
||||
options={addresses.map(addr => ({ value: addr, label: addr }))}
|
||||
capitalize={false}
|
||||
/>
|
||||
<InputLabelComponent value='Amount' />
|
||||
<AmountWrapper isEmpty={isEmpty}>
|
||||
<AmountInput
|
||||
renderRight={() => (
|
||||
<MaxAvailableAmount
|
||||
onClick={() => this.handleChange('amount')(balance)}
|
||||
disabled={!from}
|
||||
>
|
||||
<MaxAvailableAmountImg src={ArrowUpIcon} />
|
||||
</MaxAvailableAmount>
|
||||
)}
|
||||
isEmpty={isEmpty}
|
||||
type='number'
|
||||
onChange={this.handleChange('amount')}
|
||||
|
@ -473,6 +536,7 @@ export class SendView extends PureComponent<Props, State> {
|
|||
placeholder='ZEC 0.0'
|
||||
min={0.01}
|
||||
name='amount'
|
||||
disabled={!from}
|
||||
/>
|
||||
</AmountWrapper>
|
||||
<InputLabelComponent value='To' />
|
||||
|
@ -491,6 +555,10 @@ export class SendView extends PureComponent<Props, State> {
|
|||
placeholder='Enter a text here'
|
||||
name='memo'
|
||||
/>
|
||||
<RowComponent justifyContent='flex-end'>
|
||||
<Checkbox onChange={event => this.setState({ isHexMemo: event.target.checked })} />
|
||||
<TextComponent value='Hexadecimal memo' />
|
||||
</RowComponent>
|
||||
<ShowFeeButton
|
||||
id='send-show-additional-options-button'
|
||||
onClick={() => this.setState(state => ({
|
||||
|
|
|
@ -17,8 +17,11 @@ import { InputComponent } from '../components/input';
|
|||
import { InputLabelComponent } from '../components/input-label';
|
||||
import { RowComponent } from '../components/row';
|
||||
import { Clipboard } from '../components/clipboard';
|
||||
import { SelectComponent } from '../components/select';
|
||||
|
||||
import rpc from '../../services/api';
|
||||
import { DARK, LIGHT } from '../constants/themes';
|
||||
import electronStore from '../../config/electron-store';
|
||||
|
||||
const HOME_DIR = electron.remote.app.getPath('home');
|
||||
|
||||
|
@ -67,6 +70,10 @@ const SettingsContent = styled(TextComponent)`
|
|||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
const ThemeSelectWrapper = styled.div`
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
type Key = {
|
||||
zAddress: string,
|
||||
key: string,
|
||||
|
@ -210,6 +217,23 @@ export class SettingsView extends PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<Wrapper>
|
||||
<ThemeSelectWrapper>
|
||||
<SettingsTitle value='Theme' />
|
||||
<SelectComponent
|
||||
onChange={newMode => electronStore.set('THEME_MODE', newMode)}
|
||||
options={[
|
||||
{
|
||||
label: 'Dark',
|
||||
value: DARK,
|
||||
},
|
||||
{
|
||||
label: 'Light',
|
||||
value: LIGHT,
|
||||
},
|
||||
]}
|
||||
value={electronStore.get('THEME_MODE')}
|
||||
/>
|
||||
</ThemeSelectWrapper>
|
||||
<ConfirmDialogComponent
|
||||
title='Export view keys'
|
||||
renderTrigger={toggleVisibility => (
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
declare module 'electron-store' {
|
||||
declare function callback(string, string): void;
|
||||
|
||||
declare class ElectronStore {
|
||||
constructor({
|
||||
defaults?: Object,
|
||||
|
@ -14,6 +16,7 @@ declare module 'electron-store' {
|
|||
has(key: string): boolean;
|
||||
delete(key: string): void;
|
||||
clear(): void;
|
||||
onDidChange(key: string, cb: typeof callback): void;
|
||||
size: number;
|
||||
store: Object;
|
||||
path: string;
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
import { ThemeSet } from 'styled-theming';
|
||||
|
||||
type Colors = {
|
||||
primary: string,
|
||||
secondary: string,
|
||||
sidebarBg: string,
|
||||
sidebarItem: string,
|
||||
sidebarItemActive: string,
|
||||
sidebarHoveredItem: string,
|
||||
sidebarHoveredItemLabel: string,
|
||||
cardBackgroundColor: string,
|
||||
text: string,
|
||||
activeItem: string,
|
||||
inactiveItem: string,
|
||||
sidebarLogoGradientBegin: string,
|
||||
sidebarLogoGradientEnd: string,
|
||||
background: string,
|
||||
transactionSent: string,
|
||||
transactionReceived: string,
|
||||
transactionsDate: string,
|
||||
transactionsItemHovered: string,
|
||||
inputBackground: string,
|
||||
selectButtonShadow: string,
|
||||
transactionsDetailsLabel: string,
|
||||
statusPillLabel: string,
|
||||
modalItemLabel: string,
|
||||
blackTwo: string,
|
||||
buttonBorderColor: string,
|
||||
primary: ThemeSet,
|
||||
secondary: ThemeSet,
|
||||
sidebarBg: ThemeSet,
|
||||
sidebarItem: ThemeSet,
|
||||
sidebarItemActive: ThemeSet,
|
||||
sidebarHoveredItem: ThemeSet,
|
||||
sidebarHoveredItemLabel: ThemeSet,
|
||||
cardBackgroundColor: ThemeSet,
|
||||
text: ThemeSet,
|
||||
activeItem: ThemeSet,
|
||||
inactiveItem: ThemeSet,
|
||||
sidebarLogoGradientBegin: ThemeSet,
|
||||
sidebarLogoGradientEnd: ThemeSet,
|
||||
background: ThemeSet,
|
||||
transactionSent: ThemeSet,
|
||||
transactionReceived: ThemeSet,
|
||||
transactionsDate: ThemeSet,
|
||||
transactionsItemHovered: ThemeSet,
|
||||
inputBackground: ThemeSet,
|
||||
selectButtonShadow: ThemeSet,
|
||||
transactionsDetailsLabel: ThemeSet,
|
||||
statusPillLabel: ThemeSet,
|
||||
modalItemLabel: ThemeSet,
|
||||
blackTwo: ThemeSet,
|
||||
buttonBorderColor: ThemeSet,
|
||||
};
|
||||
|
||||
type FontSize = {
|
||||
|
@ -54,7 +56,6 @@ type AppTheme = {
|
|||
transitionEase: string,
|
||||
};
|
||||
|
||||
// =(
|
||||
declare type PropsWithTheme<T = {}> = {
|
||||
...T,
|
||||
theme: AppTheme,
|
||||
|
|
Loading…
Reference in New Issue