Merge branch 'develop' of github.com:andrerfneves/zcash-reference-wallet into feature/build-pipeline
This commit is contained in:
commit
4c2839bacc
|
@ -1,6 +1,7 @@
|
|||
[ignore]
|
||||
.*/node_modules/polished/.*
|
||||
./__tests__/**.js
|
||||
./flow-typed/npm/styled-components_v4.x.x.js
|
||||
|
||||
[include]
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// @flow
|
||||
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
LOAD_ADDRESSES_SUCCESS,
|
||||
LOAD_ADDRESSES_ERROR,
|
||||
loadAddressesSuccess,
|
||||
loadAddressesError,
|
||||
} from '../../app/redux/modules/receive';
|
||||
|
||||
const store = configureStore()();
|
||||
|
||||
describe('Receive Actions', () => {
|
||||
beforeEach(() => store.clearActions());
|
||||
|
||||
test('should create an action to load addresses with success', () => {
|
||||
const payload = {
|
||||
addresses: [
|
||||
'tm0a9si0ds09gj02jj',
|
||||
'smas098gk02jf0kskk',
|
||||
],
|
||||
};
|
||||
|
||||
store.dispatch(loadAddressesSuccess(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_ADDRESSES_SUCCESS,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to load addresses with error', () => {
|
||||
const payload = {
|
||||
error: 'Something went wrong!',
|
||||
};
|
||||
|
||||
store.dispatch(loadAddressesError(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_ADDRESSES_ERROR,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
// @flow
|
||||
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
SEND_TRANSACTION,
|
||||
SEND_TRANSACTION_SUCCESS,
|
||||
SEND_TRANSACTION_ERROR,
|
||||
RESET_SEND_TRANSACTION,
|
||||
VALIDATE_ADDRESS_SUCCESS,
|
||||
VALIDATE_ADDRESS_ERROR,
|
||||
LOAD_ZEC_PRICE,
|
||||
sendTransaction,
|
||||
sendTransactionSuccess,
|
||||
sendTransactionError,
|
||||
resetSendTransaction,
|
||||
validateAddressSuccess,
|
||||
validateAddressError,
|
||||
loadZECPrice,
|
||||
} from '../../app/redux/modules/send';
|
||||
|
||||
const store = configureStore()();
|
||||
|
||||
describe('Send Actions', () => {
|
||||
beforeEach(() => store.clearActions());
|
||||
|
||||
test('should create an action to send a transaction', () => {
|
||||
store.dispatch(sendTransaction());
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: SEND_TRANSACTION,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to send transaction with success', () => {
|
||||
const payload = {
|
||||
operationId: '0b9ii4590ab-1d012klfo',
|
||||
};
|
||||
|
||||
store.dispatch(sendTransactionSuccess(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: SEND_TRANSACTION_SUCCESS,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to send transaction with error', () => {
|
||||
const payload = {
|
||||
error: 'Something went wrong!',
|
||||
};
|
||||
|
||||
store.dispatch(sendTransactionError(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: SEND_TRANSACTION_ERROR,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should reset a transaction', () => {
|
||||
store.dispatch(resetSendTransaction());
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: RESET_SEND_TRANSACTION,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should validate a address with success', () => {
|
||||
const payload = {
|
||||
isValid: true,
|
||||
};
|
||||
|
||||
store.dispatch(validateAddressSuccess(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: VALIDATE_ADDRESS_SUCCESS,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should validate a address with error', () => {
|
||||
store.dispatch(validateAddressError());
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: VALIDATE_ADDRESS_ERROR,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should load ZEC price', () => {
|
||||
const payload = {
|
||||
value: 1.35,
|
||||
};
|
||||
|
||||
store.dispatch(loadZECPrice(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_ZEC_PRICE,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
// @flow
|
||||
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
LOAD_TRANSACTIONS,
|
||||
LOAD_TRANSACTIONS_SUCCESS,
|
||||
LOAD_TRANSACTIONS_ERROR,
|
||||
loadTransactions,
|
||||
loadTransactionsSuccess,
|
||||
loadTransactionsError,
|
||||
} from '../../app/redux/modules/transactions';
|
||||
|
||||
const store = configureStore()();
|
||||
|
||||
describe('Transactions Actions', () => {
|
||||
beforeEach(() => store.clearActions());
|
||||
|
||||
test('should create an action to load transactions', () => {
|
||||
store.dispatch(loadTransactions());
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_TRANSACTIONS,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to load transactions with success', () => {
|
||||
const payload = {
|
||||
list: [],
|
||||
zecPrice: 0,
|
||||
};
|
||||
|
||||
store.dispatch(loadTransactionsSuccess(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_TRANSACTIONS_SUCCESS,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to load transactions with error', () => {
|
||||
const payload = {
|
||||
error: 'Something went wrong!',
|
||||
};
|
||||
|
||||
store.dispatch(loadTransactionsError(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_TRANSACTIONS_ERROR,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { Button } from '../../app/components/button';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<Button />', () => {
|
||||
test('should render primary button correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<Button
|
||||
label='Click me!'
|
||||
onClick={() => alert('Clicked')} // eslint-disable-line
|
||||
variant='primary'
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('PrimaryButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render secondary button correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<Button
|
||||
label='Click me!'
|
||||
onClick={() => alert('Clicked')} // eslint-disable-line
|
||||
variant='secondary'
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('SecondaryButton')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { Clipboard } from '../../app/components/clipboard';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<Clipboard />', () => {
|
||||
test('should render clipboard component correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<Clipboard text='Click me!' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('Clipboard')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render clipboard button correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<Clipboard text='Click me!' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('PrimaryButton')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { ColumnComponent } from '../../app/components/column';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<ColumnComponent />', () => {
|
||||
test('should render correctly', () => {
|
||||
// $FlowFixMe
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<ColumnComponent>
|
||||
<h3>ZEC</h3>
|
||||
<h3>React</h3>
|
||||
<h3>Wallet</h3>
|
||||
</ColumnComponent>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(container).toBeVisible();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { ConfirmDialogComponent } from '../../app/components/confirm-dialog';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<ConfirmDialogComponent />', () => {
|
||||
test('should render confirm dialog correctly', () => {
|
||||
const { container } = render(
|
||||
<ConfirmDialogComponent
|
||||
title='Confirm example'
|
||||
onConfirm={() => alert('Confirm')} // eslint-disable-line
|
||||
renderTrigger={toggle => (
|
||||
<button
|
||||
onClick={toggle}
|
||||
type='button'
|
||||
>
|
||||
Open!
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{(/* toggle */) => <div>Confirm content</div>}
|
||||
</ConfirmDialogComponent>,
|
||||
);
|
||||
|
||||
expect(container).toBeVisible();
|
||||
});
|
||||
|
||||
test('should render confirm dialog trigger', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ConfirmDialogComponent
|
||||
title='Confirm example'
|
||||
onConfirm={() => alert('Confirm')} // eslint-disable-line
|
||||
renderTrigger={toggle => (
|
||||
<button
|
||||
data-testid='ConfirmDialogTrigger'
|
||||
onClick={toggle}
|
||||
type='button'
|
||||
>
|
||||
Open!
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{(/* toggle */) => <div>Confirm content</div>}
|
||||
</ConfirmDialogComponent>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('ConfirmDialogTrigger')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { Divider } from '../../app/components/divider';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<Divider />', () => {
|
||||
test('should render correctly', () => {
|
||||
// $FlowFixMe
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<Divider opacity={0.3} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
const divider = container.querySelector('div');
|
||||
|
||||
expect(divider).toBeVisible();
|
||||
expect(divider).toHaveStyle('opacity: 0.3');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { DropdownComponent } from '../../app/components/dropdown';
|
||||
import { Button } from '../../app/components/button';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<DropdownComponent />', () => {
|
||||
test('should render dropdown correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<div
|
||||
style={{ height: '500px' }}
|
||||
data-testid='DropdownWrapper'
|
||||
>
|
||||
<DropdownComponent
|
||||
label='Addresses'
|
||||
renderTrigger={toggleVisibility => (
|
||||
<Button
|
||||
label='Show Dropdown'
|
||||
onClick={toggleVisibility}
|
||||
/>
|
||||
)}
|
||||
options={[
|
||||
{ label: 'asbh1yeasbdh23848asdasd', onClick: console.log },
|
||||
{ label: 'urtyruhjr374hbfdjdhuh', onClick: console.log },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('DropdownWrapper')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render dropdown button trigger correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<div style={{ height: '500px' }}>
|
||||
<DropdownComponent
|
||||
label='Addresses'
|
||||
renderTrigger={toggleVisibility => (
|
||||
<Button
|
||||
label='Show Dropdown'
|
||||
data-testid='DropdownAddressesWrapper'
|
||||
onClick={toggleVisibility}
|
||||
/>
|
||||
)}
|
||||
options={[
|
||||
{ label: 'asbh1yeasbdh23848asdasd', onClick: console.log },
|
||||
{ label: 'urtyruhjr374hbfdjdhuh', onClick: console.log },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('PrimaryButton')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup, queryByText } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { EmptyTransactionsComponent } from '../../app/components/empty-transactions';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<EmptyTransactions />', () => {
|
||||
test('should render correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<EmptyTransactionsComponent />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('NoTransactions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show label correctly', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<EmptyTransactionsComponent />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /transactions/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup, queryByText } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { InputLabelComponent } from '../../app/components/input-label';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<InputLabelComponent />', () => {
|
||||
test('should render correctly', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<InputLabelComponent value='From' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
const label = container.querySelector('p');
|
||||
|
||||
expect(label).toBeVisible();
|
||||
});
|
||||
|
||||
test('should render input label string', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<InputLabelComponent value='From' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /From/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { InputComponent } from '../../app/components/input';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<InputComponent />', () => {
|
||||
test('should render text input correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<InputComponent
|
||||
inputType='input'
|
||||
value='Hello World!'
|
||||
onChange={console.log} // eslint-disable-line
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('Input')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render textarea correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<InputComponent
|
||||
inputType='textarea'
|
||||
value='I am Zcash Electron Wallet'
|
||||
onChange={console.log} // eslint-disable-line
|
||||
rows={10}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('Textarea')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { LoadingScreen } from '../../app/components/loading-screen';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<LoadingScreen />', () => {
|
||||
test('should render status pill correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<LoadingScreen progress={83.0} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('LoadingScreen')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { ModalComponent } from '../../app/components/modal';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<ModalComponent />', () => {
|
||||
test('should render modal trigger correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ModalComponent
|
||||
renderTrigger={toggleVisibility => (
|
||||
<button
|
||||
type='button'
|
||||
data-testid='ModalTrigger'
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
Open Modal
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{toggleVisibility => (
|
||||
<div style={{ padding: '50px', backgroundColor: 'white' }}>
|
||||
Modal Content
|
||||
<button
|
||||
type='button'
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
Close Modal
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</ModalComponent>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('ModalTrigger')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { QRCode } from '../../app/components/qrcode';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<QRCode />', () => {
|
||||
test('should render qrcode component correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<QRCode value='https://z.cash.foundation' />,
|
||||
);
|
||||
|
||||
expect(queryByTestId('QRCode')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { RowComponent } from '../../app/components/row';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<RowComponent />', () => {
|
||||
test('should render correctly', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<RowComponent>
|
||||
<h3>ZEC</h3>
|
||||
<h3>React</h3>
|
||||
<h3>Wallet</h3>
|
||||
</RowComponent>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(container).toBeVisible();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup, queryByText } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { SelectComponent } from '../../app/components/select';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<SelectComponent />', () => {
|
||||
test('should generate snapshot correctly', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<SelectComponent
|
||||
onChange={console.log} // eslint-disable-line
|
||||
value='asbh1yeasbdh23848asdasd'
|
||||
placeholder='Select a address'
|
||||
options={[
|
||||
{ label: 'asbh1yeasbdh23848asdasd', value: '1' },
|
||||
{ label: 'urtyruhjr374hbfdjdhuh', value: '1' },
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(container).toBeVisible();
|
||||
});
|
||||
|
||||
test('should render correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<SelectComponent
|
||||
onChange={console.log}
|
||||
value='asbh1yeasbdh23848asdasd'
|
||||
placeholder='Select a address'
|
||||
options={[
|
||||
{ label: 'asbh1yeasbdh23848asdasd', value: '1' },
|
||||
{ label: 'urtyruhjr374hbfdjdhuh', value: '1' },
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('Select')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should render select trigger string', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<SelectComponent
|
||||
onChange={console.log}
|
||||
value='asbh1yeasbdh23848asdasd'
|
||||
placeholder='Select a address'
|
||||
options={[
|
||||
{ label: 'asbh1yeasbdh23848asdasd', value: '1' },
|
||||
{ label: 'urtyruhjr374hbfdjdhuh', value: '1' },
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /Select/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup, queryByText } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { StatusPill } from '../../app/components/status-pill';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<StatusPill />', () => {
|
||||
test('should render status pill correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<StatusPill progress={83.0} type='syncing' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('StatusPill')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show percentage on status pill syncing', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<StatusPill progress={56.0} type='syncing' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /%/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should hide percentage on status pill', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<StatusPill progress={100.0} type='ready' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /%/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should show error string and hide percentage on status pill', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<StatusPill progress={0.0} type='error' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /%/i)).not.toBeInTheDocument();
|
||||
expect(queryByText(container, /error/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup, queryByText } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { TextComponent } from '../../app/components/text';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<TextComponent />', () => {
|
||||
test('should render correctly', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<TextComponent value='ZEC React Wallet' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(container).toBeVisible();
|
||||
});
|
||||
|
||||
test('should render input label string', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<TextComponent value='Test' />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText(container, /Test/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { TransactionItemComponent } from '../../app/components/transaction-item';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<TransactionItem />', () => {
|
||||
test('should render a transaction item correctly', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<TransactionItemComponent
|
||||
type='send'
|
||||
address='123456789123456789123456789123456789'
|
||||
transactionId='a0s9dujo23j0'
|
||||
amount={0.8652}
|
||||
date={new Date().toISOString()}
|
||||
zecPrice={2.94}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { TransactionDailyComponent } from '../../app/components/transaction-daily';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<TransactionDailyComponent />', () => {
|
||||
describe('render()', () => {
|
||||
test('should render user daily transactions', () => {
|
||||
const { container } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<TransactionDailyComponent
|
||||
transactionsDate={new Date().toISOString()}
|
||||
zecPrice={1.345}
|
||||
transactions={[
|
||||
{
|
||||
type: 'receive',
|
||||
transactionId: 's0a8das098fgh2348a',
|
||||
address: '123456789123456789123456789123456789',
|
||||
amount: 1.7891,
|
||||
zecPrice: 1.345,
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
type: 'send',
|
||||
transactionId: '0asd908fgj90f01',
|
||||
address: '123456789123456789123456789123456789',
|
||||
amount: 0.8458,
|
||||
zecPrice: 1.344,
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { render, cleanup } from 'react-testing-library';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { WalletAddress } from '../../app/components/wallet-address';
|
||||
import appTheme from '../../app/theme';
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe('<WalletAddress />', () => {
|
||||
test('should render wallet address component correctly', () => {
|
||||
const { queryByTestId } = render(
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<div style={{ width: '700px' }}>
|
||||
<WalletAddress
|
||||
address='t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1'
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
expect(queryByTestId('Address')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
@ -8,6 +9,7 @@ beforeAll(async () => {
|
|||
await app.client.waitUntilWindowLoaded();
|
||||
await app.client.waitUntilTextExists('#sidebar', 'Console');
|
||||
});
|
||||
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Console', () => {
|
||||
|
@ -16,8 +18,10 @@ describe('Console', () => {
|
|||
|
||||
expect(app.client.getText('#header p:first-child')).resolves.toEqual('Console');
|
||||
|
||||
expect(app.client.element('#console-wrapper img').getAttribute('src')).resolves.toEqual(
|
||||
expect.stringContaining('/assets/console_zcash.png'),
|
||||
);
|
||||
expect(app.client.element('#console-wrapper img')
|
||||
.getAttribute('src'))
|
||||
.resolves.toEqual(
|
||||
expect.stringContaining('/assets/console_zcash.png'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
@ -9,29 +10,28 @@ beforeEach(async () => {
|
|||
await app.client.waitUntilTextExists('#sidebar', 'Send');
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
});
|
||||
|
||||
afterEach(() => app.stop());
|
||||
|
||||
describe('Send', () => {
|
||||
test('should load "Send Page"', async () => {
|
||||
expect(app.client.element('#send-wrapper').isVisible()).resolves.toEqual(true);
|
||||
expect(app.client.element('#send-wrapper')
|
||||
.isVisible()).resolves.toEqual(true);
|
||||
});
|
||||
|
||||
test('should show Additional Options click', async () => {
|
||||
expect(app.client.element('#send-wrapper #send-fee-wrapper').isVisible()).resolves.toEqual(
|
||||
false,
|
||||
);
|
||||
expect(app.client.element('#send-wrapper #send-fee-wrapper')
|
||||
.isVisible()).resolves.toEqual(false);
|
||||
|
||||
await app.client.element('#send-show-additional-options-button').click();
|
||||
|
||||
expect(app.client.element('#send-wrapper #send-fee-wrapper').isVisible()).resolves.toEqual(
|
||||
true,
|
||||
);
|
||||
expect(app.client.element('#send-wrapper #send-fee-wrapper')
|
||||
.isVisible()).resolves.toEqual(true);
|
||||
});
|
||||
|
||||
test('should disable send button if required fields are empty', async () => {
|
||||
expect(app.client.element('#send-submit-button').getAttribute('disabled')).resolves.toEqual(
|
||||
true,
|
||||
);
|
||||
expect(app.client.element('#send-submit-button')
|
||||
.getAttribute('disabled')).resolves.toEqual(true);
|
||||
});
|
||||
|
||||
test('should enable send button if required fields are filled', async () => {
|
||||
|
@ -68,9 +68,8 @@ describe('Send', () => {
|
|||
|
||||
await app.client.element('#send-submit-button').click();
|
||||
|
||||
expect(app.client.element('#send-confirm-transaction-modal').isVisible()).resolves.toEqual(
|
||||
true,
|
||||
);
|
||||
expect(app.client.element('#send-confirm-transaction-modal')
|
||||
.isVisible()).resolves.toEqual(true);
|
||||
});
|
||||
|
||||
test('should display a load indicator while the transaction is processed', async () => {
|
||||
|
@ -88,17 +87,19 @@ describe('Send', () => {
|
|||
|
||||
await app.client.element('#send-submit-button').click();
|
||||
await app.client.element('#confirm-modal-button').click();
|
||||
|
||||
expect(app.client.getAttribute('#send-confirm-transaction-modal img', 'src')).resolves.toEqual(
|
||||
expect.stringContaining('/assets/sync_icon.png'),
|
||||
);
|
||||
expect(app.client.getText('#send-confirm-transaction-modal p')).resolves.toEqual(
|
||||
'Processing transaction...',
|
||||
);
|
||||
expect(app.client.element('#confirm-modal-button').isVisible()).resolves.toEqual(false);
|
||||
expect(app.client.getText('#send-confirm-transaction-modal p'))
|
||||
.resolves.toEqual('Processing transaction...');
|
||||
expect(app.client.element('#confirm-modal-button')
|
||||
.isVisible()).resolves.toEqual(false);
|
||||
});
|
||||
|
||||
test('should show an error in invalid transaction', async () => {
|
||||
expect(app.client.element('#send-error-text').isVisible()).resolves.toEqual(false);
|
||||
expect(app.client.element('#send-error-text')
|
||||
.isVisible()).resolves.toEqual(false);
|
||||
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
|
@ -123,7 +124,8 @@ describe('Send', () => {
|
|||
});
|
||||
|
||||
test('should show a success screen after transaction and show a transaction item', async () => {
|
||||
expect(app.client.element('#send-success-wrapper').isVisible()).resolves.toEqual(false);
|
||||
expect(app.client.element('#send-success-wrapper')
|
||||
.isVisible()).resolves.toEqual(false);
|
||||
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
|
@ -147,9 +149,8 @@ describe('Send', () => {
|
|||
|
||||
await app.client.waitUntilTextExists('#transaction-item-operation-id-1', 'Send');
|
||||
|
||||
expect(await app.client.element('#transaction-item-operation-id-1 img').isVisible()).toEqual(
|
||||
true,
|
||||
);
|
||||
expect(await app.client.element('#transaction-item-operation-id-1 img')
|
||||
.isVisible()).toEqual(true);
|
||||
expect(
|
||||
await app.client.element('#transaction-item-operation-id-1 img').getAttribute('src'),
|
||||
).toEndWith('/assets/transaction_sent_icon.svg');
|
||||
|
|
|
@ -9,6 +9,7 @@ beforeAll(async () => {
|
|||
await app.client.waitUntilWindowLoaded();
|
||||
await app.client.waitUntilTextExists('#sidebar', 'Dashboard');
|
||||
});
|
||||
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Sidebar', () => {
|
||||
|
@ -17,9 +18,8 @@ describe('Sidebar', () => {
|
|||
|
||||
expect(await app.client.getUrl()).toEndWith('/');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(1)').getHTML()).toEqual(
|
||||
expect.stringContaining('Dashboard'),
|
||||
);
|
||||
expect(await app.client.element('#sidebar a:nth-child(1)')
|
||||
.getHTML()).toEqual(expect.stringContaining('Dashboard'));
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(1) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/dashboard_icon_active.svg'),
|
||||
|
@ -31,11 +31,11 @@ describe('Sidebar', () => {
|
|||
|
||||
expect(await app.client.getUrl()).toEndWith('/send');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(2)').getHTML()).toEqual(
|
||||
expect.stringContaining('Send'),
|
||||
);
|
||||
expect(await app.client.element('#sidebar a:nth-child(2)')
|
||||
.getHTML()).toEqual(expect.stringContaining('Send'));
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(2) img').getAttribute('src')).toEqual(
|
||||
expect(await app.client.element('#sidebar a:nth-child(2) img')
|
||||
.getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/send_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
@ -45,11 +45,11 @@ describe('Sidebar', () => {
|
|||
|
||||
expect(await app.client.getUrl()).toEndWith('/receive');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(3)').getHTML()).toEqual(
|
||||
expect.stringContaining('Receive'),
|
||||
);
|
||||
expect(await app.client.element('#sidebar a:nth-child(3)')
|
||||
.getHTML()).toEqual(expect.stringContaining('Receive'));
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(3) img').getAttribute('src')).toEqual(
|
||||
expect(await app.client.element('#sidebar a:nth-child(3) img')
|
||||
.getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/receive_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
@ -59,11 +59,11 @@ describe('Sidebar', () => {
|
|||
|
||||
expect(await app.client.getUrl()).toEndWith('/transactions');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(4)').getHTML()).toEqual(
|
||||
expect.stringContaining('Transactions'),
|
||||
);
|
||||
expect(await app.client.element('#sidebar a:nth-child(4)')
|
||||
.getHTML()).toEqual(expect.stringContaining('Transactions'));
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(4) img').getAttribute('src')).toEqual(
|
||||
expect(await app.client.element('#sidebar a:nth-child(4) img')
|
||||
.getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/transactions_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
@ -73,11 +73,11 @@ describe('Sidebar', () => {
|
|||
|
||||
expect(await app.client.getUrl()).toEndWith('/settings');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(5)').getHTML()).toEqual(
|
||||
expect.stringContaining('Settings'),
|
||||
);
|
||||
expect(await app.client.element('#sidebar a:nth-child(5)')
|
||||
.getHTML()).toEqual(expect.stringContaining('Settings'));
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(5) img').getAttribute('src')).toEqual(
|
||||
expect(await app.client.element('#sidebar a:nth-child(5) img')
|
||||
.getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/settings_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
@ -87,11 +87,11 @@ describe('Sidebar', () => {
|
|||
|
||||
expect(await app.client.getUrl()).toEndWith('/console');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(6)').getHTML()).toEqual(
|
||||
expect.stringContaining('Console'),
|
||||
);
|
||||
expect(await app.client.element('#sidebar a:nth-child(6)')
|
||||
.getHTML()).toEqual(expect.stringContaining('Console'));
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(6) img').getAttribute('src')).toEqual(
|
||||
expect(await app.client.element('#sidebar a:nth-child(6) img')
|
||||
.getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/console_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ beforeAll(async () => {
|
|||
await app.start();
|
||||
await app.client.waitUntilWindowLoaded();
|
||||
});
|
||||
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Startup', () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import walletSummaryReducer, {
|
||||
LOAD_WALLET_SUMMARY,
|
||||
LOAD_WALLET_SUMMARY_SUCCESS,
|
||||
|
@ -8,12 +9,14 @@ import walletSummaryReducer, {
|
|||
describe('WalletSummary Reducer', () => {
|
||||
test('should return the valid initial state', () => {
|
||||
const initialState = {
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
total: 0,
|
||||
shielded: 0,
|
||||
transparent: 0,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
dollarValue: 0,
|
||||
zecPrice: 0,
|
||||
};
|
||||
const action = {
|
||||
type: 'UNKNOWN_ACTION',
|
||||
|
@ -29,12 +32,14 @@ describe('WalletSummary Reducer', () => {
|
|||
payload: {},
|
||||
};
|
||||
const expectedState = {
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
total: 0,
|
||||
shielded: 0,
|
||||
transparent: 0,
|
||||
error: null,
|
||||
isLoading: true,
|
||||
dollarValue: 0,
|
||||
zecPrice: 0,
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||
|
@ -53,7 +58,9 @@ describe('WalletSummary Reducer', () => {
|
|||
...action.payload,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
dollarValue: 0,
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
zecPrice: 0,
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||
|
@ -72,7 +79,9 @@ describe('WalletSummary Reducer', () => {
|
|||
transparent: 0,
|
||||
error: action.payload.error,
|
||||
isLoading: false,
|
||||
dollarValue: 0,
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
zecPrice: 0,
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// @flow
|
||||
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
process(filename: any) {
|
||||
return `module.exports = ${JSON.stringify(path.basename(filename))};`;
|
||||
},
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
require('jest-extended');
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import 'babel-polyfill';
|
||||
|
||||
|
@ -11,7 +12,7 @@ const sleep = (time: number) => new Promise(resolve => setTimeout(resolve, time)
|
|||
createTestServer({
|
||||
httpPort: '18232',
|
||||
}).then(async (server) => {
|
||||
console.log('[MOCK RPC API]', server.url);
|
||||
console.log('[MOCK RPC API]', server.url); // eslint-disable-line
|
||||
|
||||
server.get('/', (req, res) => {
|
||||
res.send('Zcash RPC');
|
||||
|
@ -94,6 +95,10 @@ createTestServer({
|
|||
return res.send({
|
||||
result: [{ id: 'operation-id-1', status: 'success', result: { txid: 'txid-1' } }],
|
||||
});
|
||||
case 'z_getbalance':
|
||||
return res.send({
|
||||
result: 5,
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import electron from 'electron';
|
||||
import { Application } from 'spectron';
|
||||
|
|
|
@ -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('');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
// @flow
|
||||
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { filterObjectNullKeys } from '../../app/utils/filter-object-null-keys';
|
||||
|
||||
describe('filterObjectNullKeys', () => {
|
||||
test('should filter null keys from object', () => {
|
||||
const initialState = {
|
||||
name: 'John Doe',
|
||||
address: null,
|
||||
amount: 0,
|
||||
transactions: undefined,
|
||||
};
|
||||
|
||||
const expectedState = {
|
||||
name: 'John Doe',
|
||||
amount: 0,
|
||||
};
|
||||
|
||||
expect(filterObjectNullKeys(initialState)).toEqual(expectedState);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { formatNumber } from '../../app/utils/format-number';
|
||||
|
||||
describe('formatNumber', () => {
|
||||
test('should append ZEC in balance amount', () => {
|
||||
const myBalance = formatNumber({ value: 2.5, append: 'ZEC ' });
|
||||
|
||||
const expectedState = 'ZEC 2.5';
|
||||
|
||||
expect(myBalance).toEqual(expectedState);
|
||||
});
|
||||
|
||||
test('should multiply ZEC balance and show it in USD', () => {
|
||||
const myBalanceInUsd = formatNumber({
|
||||
value: new BigNumber(2.5).times(1.35).toNumber(),
|
||||
append: 'USD $',
|
||||
});
|
||||
|
||||
const expectedState = 'USD $3.375';
|
||||
|
||||
expect(myBalanceInUsd).toEqual(expectedState);
|
||||
});
|
||||
|
||||
test('should multiply decimal ZEC balance and show it in USD', () => {
|
||||
const myBalanceInUsd = formatNumber({
|
||||
value: new BigNumber(0.1).times(0.2).toNumber(),
|
||||
append: 'USD $',
|
||||
});
|
||||
|
||||
const expectedState = 'USD $0.02';
|
||||
|
||||
expect(myBalanceInUsd).toEqual(expectedState);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { sortByDescend } from '../../app/utils/sort-by-descend';
|
||||
|
||||
describe('truncateAddress', () => {
|
||||
test('should truncate ZEC address', () => {
|
||||
expect(
|
||||
sortByDescend('id')([{ id: 5 }, { id: 2 }, { id: 1 }, { id: 0 }, { id: 1 }, { id: 1 }]),
|
||||
).toEqual([{ id: 5 }, { id: 2 }, { id: 1 }, { id: 1 }, { id: 1 }, { id: 0 }]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { sortBy } from '../../app/utils/sort-by';
|
||||
|
||||
describe('truncateAddress', () => {
|
||||
test('should truncate ZEC address', () => {
|
||||
expect(
|
||||
sortBy('id')([{ id: 5 }, { id: 2 }, { id: 1 }, { id: 0 }, { id: 1 }, { id: 1 }]),
|
||||
).toEqual([{ id: 0 }, { id: 1 }, { id: 1 }, { id: 1 }, { id: 2 }, { id: 5 }]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { getTimestamp } from '../../app/utils/timestamp';
|
||||
|
||||
describe('generate timestamp', () => {
|
||||
test('should generate a random string', () => {
|
||||
const now = getTimestamp();
|
||||
|
||||
expect(now).toEqual(expect.any(Number));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
// @flow
|
||||
|
||||
import 'jest-dom/extend-expect';
|
||||
|
||||
import { truncateAddress } from '../../app/utils/truncate-address';
|
||||
|
||||
describe('truncateAddress', () => {
|
||||
test('should truncate ZEC address', () => {
|
||||
const myAddress = truncateAddress('t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1');
|
||||
|
||||
const expectedState = 't14oHp2v54vfmdgQ3v3S...8JKHTNi2a1';
|
||||
|
||||
expect(myAddress).toEqual(expectedState);
|
||||
});
|
||||
});
|
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 |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#FFF" d="M24 9h-9v-9h-6v9h-9v6h9v9h6v-9h9z"/></svg>
|
After Width: | Height: | Size: 146 B |
|
@ -1,13 +1,8 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { type ElementProps } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Link } from 'react-router-dom';
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable max-len */
|
||||
// $FlowFixMe
|
||||
import { darken } from 'polished';
|
||||
import type { ElementProps } from 'react';
|
||||
|
||||
const DefaultButton = styled.button`
|
||||
align-items: center;
|
||||
|
@ -15,13 +10,13 @@ const DefaultButton = styled.button`
|
|||
justify-content: center;
|
||||
padding: 10px 30px;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
||||
font-size: ${props => `${props.theme.fontSize.regular}em`};
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
transition: background-color 0.1s ${props => props.theme.colors.transitionEase};
|
||||
transition: background-color 0.1s ${props => props.theme.transitionEase};
|
||||
`;
|
||||
|
||||
const Primary = styled(DefaultButton)`
|
||||
|
@ -30,7 +25,7 @@ const Primary = styled(DefaultButton)`
|
|||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: ${props => darken(0.1, props.theme.colors.primary(props))};
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
@ -102,12 +97,12 @@ export const Button = ({
|
|||
const buttonLabel = isLoading ? 'Loading...' : label;
|
||||
|
||||
const component = variant === 'primary' ? (
|
||||
<Primary {...props}>
|
||||
<Primary {...props} data-testid='PrimaryButton'>
|
||||
{icon ? <Icon src={icon} /> : null}
|
||||
{buttonLabel}
|
||||
</Primary>
|
||||
) : (
|
||||
<Secondary {...props}>
|
||||
<Secondary {...props} data-testid='SecondaryButton'>
|
||||
{icon ? <Icon src={icon} /> : null}
|
||||
{buttonLabel}
|
||||
</Secondary>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { Button } from './button';
|
||||
|
@ -39,12 +40,14 @@ export class Clipboard extends PureComponent<Props, State> {
|
|||
const { copied } = this.state;
|
||||
|
||||
return (
|
||||
<Button
|
||||
label={copied ? 'Copied!' : 'Copy!'}
|
||||
className={className}
|
||||
onClick={this.handleClick}
|
||||
disabled={copied}
|
||||
/>
|
||||
<div data-testid='Clipboard'>
|
||||
<Button
|
||||
label={copied ? 'Copied!' : 'Copy!'}
|
||||
className={className}
|
||||
onClick={this.handleClick}
|
||||
disabled={copied}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,19 @@
|
|||
import React, { type Node, type ElementProps } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type FlexProps =
|
||||
| {
|
||||
alignItems: string,
|
||||
justifyContent: string,
|
||||
width: string,
|
||||
}
|
||||
| Object;
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: ${props => props.alignItems};
|
||||
justify-content: ${props => props.justifyContent};
|
||||
${props => props.width && `width: ${props.width};`}
|
||||
align-items: ${(props: FlexProps) => props.alignItems};
|
||||
justify-content: ${(props: FlexProps) => props.justifyContent};
|
||||
${(props: FlexProps) => props.width && `width: ${props.width};`}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
import React, { type Element } from 'react';
|
||||
|
||||
import React, { Fragment, type Element } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
|
@ -11,7 +12,7 @@ import CloseIcon from '../assets/images/close_icon.svg';
|
|||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
width: ${props => `${props.width}px`};
|
||||
width: ${(props: PropsWithTheme<{ width: number }>) => `${String(props.width)}px`};
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
@ -90,7 +91,7 @@ export const ConfirmDialogComponent = ({
|
|||
<Divider opacity={0.3} />
|
||||
{children(handleClose(toggle))}
|
||||
{showButtons && (
|
||||
<>
|
||||
<Fragment>
|
||||
<Btn
|
||||
id='confirm-modal-button'
|
||||
label='Confirm'
|
||||
|
@ -103,7 +104,7 @@ export const ConfirmDialogComponent = ({
|
|||
variant='secondary'
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</>
|
||||
</Fragment>
|
||||
)}
|
||||
</Wrapper>
|
||||
)}
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
type Props = PropsWithTheme<{
|
||||
color: ?string,
|
||||
opacity: number,
|
||||
marginBottom: string,
|
||||
marginTop: string,
|
||||
}>;
|
||||
|
||||
export const Divider = styled.div`
|
||||
width: 100%;
|
||||
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};
|
||||
background-color: ${(props: Props) => props.color || props.theme.colors.text};
|
||||
opacity: ${(props: Props) => String(props.opacity || 1)};
|
||||
margin-bottom: ${(props: Props) => props.marginBottom || '0'};
|
||||
margin-top: ${(props: Props) => props.marginTop || '0'};
|
||||
`;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { type Node, Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
|
@ -107,7 +108,7 @@ export class DropdownComponent extends Component<Props, State> {
|
|||
</MenuItem>
|
||||
)}
|
||||
{options.map(({ label: optionLabel, onClick }) => (
|
||||
<OptionItem onClick={onClick} key={optionLabel}>
|
||||
<OptionItem onClick={onClick} key={optionLabel} data-testid='DropdownOption'>
|
||||
<Option value={truncate ? truncateAddress(optionLabel) : optionLabel} />
|
||||
</OptionItem>
|
||||
))}
|
||||
|
@ -123,7 +124,12 @@ export class DropdownComponent extends Component<Props, State> {
|
|||
tipSize={7}
|
||||
body={body}
|
||||
>
|
||||
{renderTrigger(() => this.setState(state => ({ isOpen: !state.isOpen })), isOpen)}
|
||||
{renderTrigger(
|
||||
() => this.setState(state => ({
|
||||
isOpen: !state.isOpen,
|
||||
})),
|
||||
isOpen,
|
||||
)}
|
||||
</PopoverWithStyle>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -13,7 +14,7 @@ const Wrapper = styled.div`
|
|||
`;
|
||||
|
||||
export const EmptyTransactionsComponent = () => (
|
||||
<Wrapper>
|
||||
<Wrapper data-testid='NoTransactions'>
|
||||
<TextComponent value='No transactions!' />
|
||||
</Wrapper>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
import { Button } from './button';
|
||||
|
||||
import ErrorIcon from '../assets/images/error_icon.png';
|
||||
|
||||
const ModalWrapper = styled.div`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
const ChildrenWrapper = styled.div`
|
||||
width: 350px;
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 30px 0px black;
|
||||
position: relative;
|
||||
z-index: 90;
|
||||
min-height: 400px;
|
||||
`;
|
||||
|
||||
const Message = styled(TextComponent)`
|
||||
margin: 15px 0;
|
||||
`;
|
||||
|
||||
const ErrorImage = styled.img`
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
margin-bottom: 15px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
isVisible: boolean,
|
||||
message: string,
|
||||
onRequestClose: () => void,
|
||||
};
|
||||
|
||||
const modalRoot = document.getElementById('modal-root');
|
||||
|
||||
export class ErrorModalComponent extends PureComponent<Props> {
|
||||
element = document.createElement('div');
|
||||
|
||||
componentDidMount() {
|
||||
const { isVisible } = this.props;
|
||||
|
||||
if (isVisible) {
|
||||
if (modalRoot) modalRoot.appendChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const { isVisible } = this.props;
|
||||
|
||||
if (!prevProps.isVisible && isVisible) {
|
||||
if (modalRoot) modalRoot.appendChild(this.element);
|
||||
}
|
||||
|
||||
if (prevProps.isVisible && !isVisible) {
|
||||
if (modalRoot) modalRoot.removeChild(this.element);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isVisible, message, onRequestClose } = this.props;
|
||||
|
||||
return !isVisible
|
||||
? null
|
||||
: createPortal(
|
||||
<ModalWrapper id='error-modal-portal-wrapper'>
|
||||
<ChildrenWrapper>
|
||||
<ErrorImage src={ErrorIcon} alt='Error Icon' />
|
||||
<Message value={message} />
|
||||
<Button label='Ok!' onClick={onRequestClose} />
|
||||
</ChildrenWrapper>
|
||||
</ModalWrapper>,
|
||||
this.element,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ZcashLogo } from './zcash-logo';
|
||||
|
@ -9,6 +9,8 @@ import { Divider } from './divider';
|
|||
import { RowComponent } from './row';
|
||||
import { StatusPill } from './status-pill';
|
||||
|
||||
import { withSyncStatus } from '../../services/sync-status';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
height: ${props => props.theme.headerHeight};
|
||||
width: 100vw;
|
||||
|
@ -52,13 +54,15 @@ const Title = styled(TextComponent)`
|
|||
margin-bottom: 10px;
|
||||
text-transform: capitalize;
|
||||
letter-spacing: 0.25px;
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
};
|
||||
|
||||
const Status = withSyncStatus(StatusPill);
|
||||
|
||||
export const HeaderComponent = ({ title }: Props) => (
|
||||
<Wrapper id='header'>
|
||||
<LogoWrapper>
|
||||
|
@ -67,7 +71,7 @@ export const HeaderComponent = ({ title }: Props) => (
|
|||
<TitleWrapper>
|
||||
<TitleRow alignItems='center' justifyContent='space-around'>
|
||||
<Title value={title} />
|
||||
<StatusPill />
|
||||
<Status type='syncing' progress={0} />
|
||||
</TitleRow>
|
||||
<Divider opacity={0.1} />
|
||||
</TitleWrapper>
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
// @flow
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
|
||||
type Props = PropsWithTheme<{
|
||||
marginTop: string,
|
||||
}>;
|
||||
|
||||
export const InputLabelComponent = styled(TextComponent)`
|
||||
margin: ${props => props.marginTop || '20px'} 0 8.5px 0;
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
margin: ${(props: Props) => props.marginTop || '20px'} 0 8.5px 0;
|
||||
font-weight: ${(props: Props) => String(props.theme.fontWeight.bold)};
|
||||
`;
|
||||
|
|
|
@ -1,19 +1,40 @@
|
|||
// @flow
|
||||
/* eslint-disable max-len */
|
||||
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};
|
||||
type Props = {
|
||||
inputType?: 'input' | 'textarea',
|
||||
value: string,
|
||||
onChange?: string => void,
|
||||
onFocus?: (SyntheticFocusEvent<HTMLInputElement>) => void,
|
||||
rows?: number,
|
||||
disabled?: boolean,
|
||||
type?: string,
|
||||
step?: number,
|
||||
name?: string,
|
||||
renderRight?: () => Element<*> | null,
|
||||
bgColor?: string,
|
||||
};
|
||||
|
||||
type DefaultStylesProps = PropsWithTheme<{
|
||||
bgColor: ?string,
|
||||
withRightElement: boolean,
|
||||
}>;
|
||||
|
||||
// $FlowFixMe
|
||||
const getDefaultStyles: ($PropertyType<Props, 'inputType'>) => Element<*> = t => styled[t]`
|
||||
border-radius: ${(props: DefaultStylesProps) => props.theme.boxBorderRadius};
|
||||
border: none;
|
||||
background-color: ${props => props.bgColor || props.theme.colors.inputBackground};
|
||||
color: ${props => props.theme.colors.text};
|
||||
background-color: ${(props: DefaultStylesProps) => props.bgColor || props.theme.colors.inputBackground};
|
||||
color: ${(props: DefaultStylesProps) => props.theme.colors.text};
|
||||
padding: 15px;
|
||||
padding-right: ${props => (props.withRightElement ? '85px' : '15px')};
|
||||
padding-right: ${(props: DefaultStylesProps) => (props.withRightElement ? '85px' : '15px')};
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
font-family: ${(props: DefaultStylesProps) => props.theme.fontFamily};
|
||||
|
||||
::placeholder {
|
||||
opacity: 0.5;
|
||||
|
@ -33,20 +54,6 @@ const RightElement = styled.div`
|
|||
const Input = getDefaultStyles('input');
|
||||
const Textarea = getDefaultStyles('textarea');
|
||||
|
||||
type Props = {
|
||||
inputType?: 'input' | 'textarea',
|
||||
value: string,
|
||||
onChange?: string => void,
|
||||
onFocus?: (SyntheticFocusEvent<HTMLInputElement>) => void,
|
||||
rows?: number,
|
||||
disabled?: boolean,
|
||||
type?: string,
|
||||
step?: number,
|
||||
name?: string,
|
||||
renderRight?: () => Element<*> | null,
|
||||
bgColor?: string,
|
||||
};
|
||||
|
||||
export const InputComponent = ({
|
||||
inputType,
|
||||
bgColor,
|
||||
|
@ -55,18 +62,23 @@ export const InputComponent = ({
|
|||
...props
|
||||
}: Props) => {
|
||||
const rightElement = renderRight();
|
||||
|
||||
const inputTypes = {
|
||||
input: () => (
|
||||
<Input
|
||||
onChange={evt => onChange(evt.target.value)}
|
||||
withRightElement={Boolean(rightElement)}
|
||||
bgColor={bgColor}
|
||||
data-testid='Input'
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
textarea: () => (
|
||||
<Textarea onChange={evt => onChange(evt.target.value)} bgColor={bgColor} {...props} />
|
||||
<Textarea
|
||||
onChange={evt => onChange(evt.target.value)}
|
||||
bgColor={bgColor}
|
||||
data-testid='Textarea'
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import React, { type Element } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ErrorModalComponent } from './error-modal';
|
||||
|
||||
const Layout = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -14,12 +17,25 @@ const Layout = styled.div`
|
|||
`;
|
||||
|
||||
type Props = {
|
||||
chidren: any, // eslint-disable-line
|
||||
children: Element<*>,
|
||||
closeErrorModal: () => void,
|
||||
isErrorModalVisible: boolean,
|
||||
error: string,
|
||||
};
|
||||
|
||||
export const LayoutComponent = (props: Props) => {
|
||||
// $FlowFixMe
|
||||
const { children } = props; // eslint-disable-line
|
||||
const {
|
||||
children, error, isErrorModalVisible, closeErrorModal,
|
||||
} = props;
|
||||
|
||||
return <Layout id='layout'>{children}</Layout>;
|
||||
return (
|
||||
<Layout id='layout'>
|
||||
{children}
|
||||
<ErrorModalComponent
|
||||
message={error}
|
||||
isVisible={isErrorModalVisible}
|
||||
onRequestClose={closeErrorModal}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
import CircleProgressComponent from 'react-circle';
|
||||
import { TextComponent } from './text';
|
||||
|
@ -42,25 +45,34 @@ type Props = {
|
|||
|
||||
type State = {
|
||||
start: boolean,
|
||||
message: string,
|
||||
};
|
||||
|
||||
const TIME_DELAY_ANIM = 100;
|
||||
|
||||
export class LoadingScreen extends PureComponent<Props, State> {
|
||||
state = { start: false };
|
||||
state = { start: false, message: 'ZEC Wallet Starting' };
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.setState(() => ({ start: true }));
|
||||
}, TIME_DELAY_ANIM);
|
||||
|
||||
ipcRenderer.on('zcashd-params-download', (event: Object, message: string) => {
|
||||
this.setState(() => ({ message }));
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ipcRenderer.removeAllListeners('zcashd-log');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { start } = this.state;
|
||||
const { start, message } = this.state;
|
||||
const { progress } = this.props;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Wrapper data-testid='LoadingScreen'>
|
||||
<Transition
|
||||
native
|
||||
items={start}
|
||||
|
@ -73,19 +85,29 @@ export class LoadingScreen extends PureComponent<Props, State> {
|
|||
opacity: 0,
|
||||
}}
|
||||
>
|
||||
{() => props => (
|
||||
<animated.div style={props} id='loading-screen'>
|
||||
{() => (props: Object) => (
|
||||
<animated.div
|
||||
id='loading-screen'
|
||||
style={{
|
||||
...props,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<CircleWrapper>
|
||||
<Logo src={zcashLogo} alt='Zcash logo' />
|
||||
<Logo src={zcashLogo} alt='Zcash Logo' />
|
||||
<CircleProgressComponent
|
||||
progress={progress}
|
||||
s // TODO: check if this has any effect
|
||||
responsive
|
||||
showPercentage={false}
|
||||
progressColor={theme.colors.activeItem}
|
||||
bgColor={theme.colors.inactiveItem}
|
||||
/>
|
||||
</CircleWrapper>
|
||||
<TextComponent value='ZEC Wallet Starting' />
|
||||
<TextComponent value={message} />
|
||||
</animated.div>
|
||||
)}
|
||||
</Transition>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent, Fragment, type Element } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
|
@ -69,23 +70,19 @@ export class ModalComponent extends PureComponent<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
open = () => {
|
||||
this.setState(
|
||||
() => ({ isVisible: true }),
|
||||
() => {
|
||||
if (modalRoot) modalRoot.appendChild(this.element);
|
||||
},
|
||||
);
|
||||
};
|
||||
open = () => this.setState(
|
||||
() => ({ isVisible: true }),
|
||||
() => {
|
||||
if (modalRoot) modalRoot.appendChild(this.element);
|
||||
},
|
||||
);
|
||||
|
||||
close = () => {
|
||||
this.setState(
|
||||
() => ({ isVisible: false }),
|
||||
() => {
|
||||
if (modalRoot) modalRoot.removeChild(this.element);
|
||||
},
|
||||
);
|
||||
};
|
||||
close = () => this.setState(
|
||||
() => ({ isVisible: false }),
|
||||
() => {
|
||||
if (modalRoot) modalRoot.removeChild(this.element);
|
||||
},
|
||||
);
|
||||
|
||||
render() {
|
||||
const { renderTrigger, children, closeOnBackdropClick } = this.props;
|
||||
|
@ -95,22 +92,21 @@ export class ModalComponent extends PureComponent<Props, State> {
|
|||
return (
|
||||
<Fragment>
|
||||
{renderTrigger(toggleVisibility)}
|
||||
{isVisible
|
||||
? createPortal(
|
||||
<ModalWrapper
|
||||
id='modal-portal-wrapper'
|
||||
onClick={(event) => {
|
||||
if (
|
||||
closeOnBackdropClick
|
||||
&& event.target.id === 'modal-portal-wrapper'
|
||||
) this.close();
|
||||
}}
|
||||
>
|
||||
<ChildrenWrapper>{children(toggleVisibility)}</ChildrenWrapper>
|
||||
</ModalWrapper>,
|
||||
this.element,
|
||||
)
|
||||
: null}
|
||||
{!isVisible ? null : createPortal(
|
||||
<ModalWrapper
|
||||
id='modal-portal-wrapper'
|
||||
data-testid='Modal'
|
||||
onClick={(event) => {
|
||||
if (
|
||||
closeOnBackdropClick
|
||||
&& event.target.id === 'modal-portal-wrapper'
|
||||
) this.close();
|
||||
}}
|
||||
>
|
||||
<ChildrenWrapper>{children(toggleVisibility)}</ChildrenWrapper>
|
||||
</ModalWrapper>,
|
||||
this.element,
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,12 +12,15 @@ import { ModalComponent } from './modal.js'
|
|||
|
||||
<PropsTable of={ModalComponent} />
|
||||
|
||||
## Basic usage
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<ModalComponent
|
||||
renderTrigger={toggleVisibility => (
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
Open Modal
|
||||
</button>
|
||||
)}
|
||||
|
@ -25,7 +28,10 @@ import { ModalComponent } from './modal.js'
|
|||
{toggleVisibility => (
|
||||
<div style={{ padding: '50px', backgroundColor: 'white' }}>
|
||||
Modal Content
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
Close Modal
|
||||
</button>
|
||||
</div>
|
||||
|
@ -40,7 +46,10 @@ import { ModalComponent } from './modal.js'
|
|||
closeOnEsc={false}
|
||||
closeOnBackdropClick={false}
|
||||
renderTrigger={toggleVisibility => (
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
Open Modal
|
||||
</button>
|
||||
)}
|
||||
|
@ -48,7 +57,10 @@ import { ModalComponent } from './modal.js'
|
|||
{toggleVisibility => (
|
||||
<div style={{ padding: '50px', backgroundColor: 'white' }}>
|
||||
Modal Content
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
Close Modal
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,11 @@ type Props = {
|
|||
};
|
||||
|
||||
export const QRCode = ({ value, size }: Props) => (
|
||||
<QR value={value} size={size} />
|
||||
<QR
|
||||
data-testid='QRCode'
|
||||
value={value}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
|
||||
QRCode.defaultProps = {
|
||||
|
|
|
@ -12,13 +12,13 @@ import { QRCode } from './qrcode.js'
|
|||
|
||||
<PropsTable of={QRCode} />
|
||||
|
||||
## Basic usage
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<QRCode value='https://z.cash.foundation' />
|
||||
</Playground>
|
||||
|
||||
## Custom size
|
||||
## Custom Size
|
||||
|
||||
<Playground>
|
||||
<QRCode
|
||||
|
|
|
@ -4,11 +4,15 @@ import React from 'react';
|
|||
import styled from 'styled-components';
|
||||
import type { Node, ElementProps } from 'react';
|
||||
|
||||
type FlexProps = PropsWithTheme<{
|
||||
alignItems: string,
|
||||
justifyContent: string,
|
||||
}>;
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: ${props => props.alignItems};
|
||||
justify-content: ${props => props.justifyContent};
|
||||
align-items: ${(props: FlexProps) => String(props.alignItems)};
|
||||
justify-content: ${(props: FlexProps) => String(props.justifyContent)};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
|
@ -20,7 +24,7 @@ type Props = {
|
|||
};
|
||||
|
||||
export const RowComponent = ({ children, ...props }: Props) => (
|
||||
<Flex {...props}>{React.Children.map(children, ch => ch)}</Flex>
|
||||
<Flex {...props}>{React.Children.map(children, (ch: Node) => ch)}</Flex>
|
||||
);
|
||||
|
||||
RowComponent.defaultProps = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -9,30 +10,39 @@ import ChevronDown from '../assets/images/chevron-down.svg';
|
|||
import theme from '../theme';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
type SelectWrapperProps = PropsWithTheme<{
|
||||
bgColor: ?string,
|
||||
isOpen: boolean,
|
||||
placement: string,
|
||||
}>;
|
||||
|
||||
const SelectWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
border-radius: ${(props: SelectWrapperProps) => props.theme.boxBorderRadius};
|
||||
border: none;
|
||||
background-color: ${props => props.bgColor || props.theme.colors.inputBackground};
|
||||
color: ${props => props.theme.colors.text};
|
||||
background-color: ${(props: SelectWrapperProps) => props.bgColor || props.theme.colors.inputBackground};
|
||||
color: ${(props: SelectWrapperProps) => props.theme.colors.text};
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
font-family: ${(props: SelectWrapperProps) => props.theme.fontFamily};
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
${props => props.isOpen
|
||||
&& `border-${props.placement}-left-radius: 0; border-${props.placement}-right-radius: 0;`}
|
||||
${(props: SelectWrapperProps) => (props.isOpen
|
||||
? `border-${String(props.placement)}-left-radius: 0; border-${String(
|
||||
props.placement,
|
||||
)}-right-radius: 0;`
|
||||
: '')}
|
||||
`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const ValueWrapper = styled.div`
|
||||
width: 95%;
|
||||
padding: 13px;
|
||||
opacity: ${props => (props.hasValue ? '1' : '0.2')};
|
||||
text-transform: capitalize;
|
||||
opacity: ${(props: PropsWithTheme<{ hasValue: boolean }>) => (props.hasValue ? '1' : '0.2')};
|
||||
text-transform: ${(props: PropsWithTheme<{ capitalize: boolean }>) => (props.capitalize ? 'capitalize' : 'none')};
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -46,15 +56,16 @@ const SelectMenuButtonWrapper = styled.div`
|
|||
padding: 13px;
|
||||
border-left: ${props => `1px solid ${props.theme.colors.background}`};
|
||||
`;
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const SelectMenuButton = styled.button`
|
||||
padding: 3px 7px;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid ${props => (props.isOpen ? props.theme.colors.primary : '#29292D')};
|
||||
border: 1px solid
|
||||
${(props: PropsWithTheme<{ isOpen: boolean }>) => (props.isOpen ? props.theme.colors.primary : '#29292D')};
|
||||
border-radius: 100%;
|
||||
`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 10px;
|
||||
|
@ -66,7 +77,7 @@ const OptionsWrapper = styled.div`
|
|||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
${props => `${props.placement}: ${`-${props.optionsAmount * 40}px`}`};
|
||||
${(props: PropsWithTheme<{ placement: string, optionsAmount: number }>) => `${String(props.placement)}: ${`-${String((props.optionsAmount || 0) * 40)}px`}`};
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
|
@ -77,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;
|
||||
|
||||
|
@ -99,7 +110,9 @@ type Props = {
|
|||
onChange: string => void,
|
||||
placement?: 'top' | 'bottom',
|
||||
bgColor?: string,
|
||||
capitalize?: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isOpen: boolean,
|
||||
};
|
||||
|
@ -113,6 +126,7 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
placeholder: '',
|
||||
placement: 'bottom',
|
||||
bgColor: theme.colors.inputBackground,
|
||||
capitalize: true,
|
||||
};
|
||||
|
||||
onSelect = (value: string) => {
|
||||
|
@ -123,7 +137,10 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
|
||||
handleClickOutside = (event: Object) => {
|
||||
const { isOpen } = this.state;
|
||||
if (isOpen && event.target.id !== 'select-options-wrapper') this.setState(() => ({ isOpen: false }));
|
||||
|
||||
if (isOpen && event.target.id !== 'select-options-wrapper') {
|
||||
this.setState(() => ({ isOpen: false }));
|
||||
}
|
||||
};
|
||||
|
||||
getSelectedLabel = (value: string) => {
|
||||
|
@ -145,20 +162,21 @@ export class SelectComponent extends PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
value, options, placeholder, placement, bgColor,
|
||||
} = this.props;
|
||||
const { isOpen } = this.state;
|
||||
const {
|
||||
value, options, placeholder, placement, bgColor, capitalize,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SelectWrapper
|
||||
data-testid='Select'
|
||||
id='select-component'
|
||||
isOpen={isOpen}
|
||||
placement={placement}
|
||||
onClick={() => this.setState(() => ({ isOpen: !isOpen }))}
|
||||
bgColor={bgColor}
|
||||
>
|
||||
<ValueWrapper hasValue={Boolean(value)}>
|
||||
<ValueWrapper hasValue={Boolean(value)} capitalize={capitalize}>
|
||||
{this.getSelectedLabel(value) || placeholder}
|
||||
</ValueWrapper>
|
||||
<SelectMenuButtonWrapper>
|
||||
|
@ -178,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>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
/* eslint-disable max-len */
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -10,18 +11,20 @@ const Wrapper = styled.div`
|
|||
flex-direction: column;
|
||||
width: ${props => props.theme.sidebarWidth};
|
||||
height: ${props => `calc(100vh - ${props.theme.headerHeight})`};
|
||||
font-family: ${props => props.theme.fontFamily}
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
background-color: ${props => props.theme.colors.sidebarBg};
|
||||
padding-top: 15px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
/* eslint-disable max-len */
|
||||
type StyledLinkProps = PropsWithTheme<{ isActive: boolean }>;
|
||||
const StyledLink = styled.a`
|
||||
color: ${props => (props.isActive ? props.theme.colors.sidebarItemActive : props.theme.colors.sidebarItem)};
|
||||
font-size: ${props => `${props.theme.fontSize.regular}em`};
|
||||
color: ${(props: StyledLinkProps) => (props.isActive ? props.theme.colors.sidebarItemActive : props.theme.colors.sidebarItem)};
|
||||
font-size: ${(props: StyledLinkProps) => `${props.theme.fontSize.regular}em`};
|
||||
text-decoration: none;
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
background-color: ${props => (props.isActive ? `${props.theme.colors.sidebarHoveredItem}` : 'transparent')};
|
||||
font-weight: ${(props: StyledLinkProps) => String(props.theme.fontWeight.bold)};
|
||||
background-color: ${(props: StyledLinkProps) => (props.isActive ? `${props.theme.colors.sidebarHoveredItem}` : 'transparent')};
|
||||
letter-spacing: 0.25px;
|
||||
padding: 25px 20px;
|
||||
height: 35px;
|
||||
|
@ -29,13 +32,13 @@ const StyledLink = styled.a`
|
|||
display: flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
border-right: ${props => (props.isActive ? `3px solid ${props.theme.colors.sidebarItemActive}` : 'none')};
|
||||
border-right: ${(props: StyledLinkProps) => (props.isActive ? `3px solid ${props.theme.colors.sidebarItemActive(props)}` : 'none')};
|
||||
cursor: pointer;
|
||||
transition: all 0.03s ${props => props.theme.colors.transitionEase};
|
||||
transition: all 0.03s ${(props: StyledLinkProps) => props.theme.transitionEase};
|
||||
|
||||
&:hover {
|
||||
color: ${props => (props.isActive ? props.theme.colors.sidebarItemActive : '#ddd')}
|
||||
background-color: ${props => props.theme.colors.sidebarHoveredItem};
|
||||
color: ${(props: StyledLinkProps) => (props.isActive ? props.theme.colors.sidebarItemActive : '#ddd')}
|
||||
background-color: ${(props: StyledLinkProps) => props.theme.colors.sidebarHoveredItem};
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -45,7 +48,7 @@ const Icon = styled.img`
|
|||
margin-right: 13px;
|
||||
|
||||
${StyledLink}:hover & {
|
||||
filter: ${props => (props.isActive ? 'none' : 'brightness(300%)')};
|
||||
filter: ${(props: StyledLinkProps) => (props.isActive ? 'none' : 'brightness(300%)')};
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -72,7 +75,7 @@ export const SidebarComponent = ({ options, location, history }: Props) => (
|
|||
key={item.route}
|
||||
onClick={() => (isActive ? {} : history.push(item.route))}
|
||||
>
|
||||
<Icon isActive={isActive} src={item.icon(isActive)} alt={`${item.route}`} />
|
||||
<Icon isActive={isActive} src={item.icon(isActive)} Alt={`${item.route}`} />
|
||||
{item.label}
|
||||
</StyledLink>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import eres from 'eres';
|
||||
|
@ -15,7 +16,6 @@ const rotate = keyframes`
|
|||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
@ -34,12 +34,14 @@ const Icon = styled.img`
|
|||
height: 12px;
|
||||
margin-right: 8px;
|
||||
animation: 2s linear infinite;
|
||||
animation-name: ${props => (props.animated ? rotate : 'none')};
|
||||
|
||||
animation-name: ${/** $FlowFixMe */
|
||||
(props: PropsWithTheme<{ animated: boolean }>) => (props.animated ? rotate : 'none')};
|
||||
`;
|
||||
|
||||
const StatusPillLabel = styled(TextComponent)`
|
||||
color: ${props => props.theme.colors.statusPillLabel};
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
padding-top: 1px;
|
||||
|
@ -81,6 +83,8 @@ export class StatusPill extends Component<Props, State> {
|
|||
getBlockchainStatus = async () => {
|
||||
const [blockchainErr, blockchaininfo] = await eres(rpc.getblockchaininfo());
|
||||
|
||||
if (blockchainErr || !blockchaininfo) return;
|
||||
|
||||
const newProgress = blockchaininfo.verificationprogress * 100;
|
||||
|
||||
this.setState({
|
||||
|
@ -104,11 +108,12 @@ export class StatusPill extends Component<Props, State> {
|
|||
type, icon, progress, isSyncing,
|
||||
} = this.state;
|
||||
const showPercent = isSyncing ? `(${progress.toFixed(2)}%)` : '';
|
||||
const typeText = type === 'ready' ? 'Synced' : type;
|
||||
|
||||
return (
|
||||
<Wrapper id='status-pill'>
|
||||
<Icon src={icon} animated={isSyncing} />
|
||||
<StatusPillLabel value={`${type} ${showPercent}`} />
|
||||
<StatusPillLabel value={`${typeText} ${showPercent}`} />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,10 +13,42 @@ import { DoczWrapper } from '../theme.js'
|
|||
|
||||
<PropsTable of={StatusPill} />
|
||||
|
||||
## Basic Usage
|
||||
## Syching
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => <StatusPill percent={83.} />}
|
||||
{() =>
|
||||
<StatusPill
|
||||
progress={99.3}
|
||||
type='syncing'
|
||||
/>
|
||||
}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
||||
|
||||
## Ready
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() =>
|
||||
<StatusPill
|
||||
progress={100.}
|
||||
type='ready'
|
||||
/>
|
||||
}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
||||
|
||||
|
||||
## With Error
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() =>
|
||||
<StatusPill
|
||||
progress={0.}
|
||||
type='error'
|
||||
/>
|
||||
}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
/* eslint-disable max-len */
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
@ -6,16 +7,6 @@ import type { ElementProps } from 'react';
|
|||
|
||||
import theme from '../theme';
|
||||
|
||||
const Text = styled.p`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
font-size: ${props => props.size};
|
||||
color: ${props => props.color || props.theme.colors.text};
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: ${props => (props.isBold ? props.theme.fontWeight.bold : props.theme.fontWeight.default)};
|
||||
text-align: ${props => props.align};
|
||||
`;
|
||||
|
||||
export type Props = {
|
||||
...ElementProps<'p'>,
|
||||
value: string,
|
||||
|
@ -26,6 +17,16 @@ export type Props = {
|
|||
align?: string,
|
||||
};
|
||||
|
||||
const Text = styled.p`
|
||||
font-family: ${(props: PropsWithTheme<Props>) => props.theme.fontFamily};
|
||||
font-size: ${(props: PropsWithTheme<Props>) => String(props.size)};
|
||||
color: ${(props: PropsWithTheme<Props>) => props.color || props.theme.colors.text};
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: ${(props: PropsWithTheme<Props>) => String(props.isBold ? props.theme.fontWeight.bold : props.theme.fontWeight.default)};
|
||||
text-align: ${(props: PropsWithTheme<Props>) => props.align || 'left'};
|
||||
`;
|
||||
|
||||
export const TextComponent = ({
|
||||
value, isBold, color, className, size, align, id,
|
||||
}: Props) => (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -24,7 +25,7 @@ const Day = 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};
|
||||
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
|
@ -34,31 +35,25 @@ type Props = {
|
|||
zecPrice: number,
|
||||
};
|
||||
|
||||
export const TransactionDailyComponent = ({
|
||||
transactionsDate,
|
||||
transactions,
|
||||
zecPrice,
|
||||
}: Props) => (
|
||||
<Wrapper>
|
||||
export const TransactionDailyComponent = ({ transactionsDate, transactions, zecPrice }: Props) => (
|
||||
<Wrapper data-testid='TransactionsDaily'>
|
||||
<Day value={transactionsDate} />
|
||||
<TransactionsWrapper>
|
||||
{transactions.map(
|
||||
({
|
||||
date, type, address, amount, transactionId,
|
||||
}, idx) => (
|
||||
<Fragment key={`${address}-${type}-${amount}-${date}`}>
|
||||
<TransactionItemComponent
|
||||
transactionId={transactionId}
|
||||
type={type}
|
||||
date={date}
|
||||
address={address || ''}
|
||||
amount={amount}
|
||||
zecPrice={zecPrice}
|
||||
/>
|
||||
{idx < transactions.length - 1 && <Divider />}
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
{transactions.map(({
|
||||
date, type, address, amount, transactionId,
|
||||
}, idx) => (
|
||||
<Fragment key={`${address}-${type}-${amount}-${date}`}>
|
||||
<TransactionItemComponent
|
||||
transactionId={transactionId}
|
||||
type={type}
|
||||
date={date}
|
||||
address={address || ''}
|
||||
amount={amount}
|
||||
zecPrice={zecPrice}
|
||||
/>
|
||||
{idx < transactions.length - 1 && <Divider />}
|
||||
</Fragment>
|
||||
))}
|
||||
</TransactionsWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import dateFns from 'date-fns';
|
||||
|
@ -90,7 +91,7 @@ const Divider = styled.div`
|
|||
`;
|
||||
|
||||
const Label = styled(TextComponent)`
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
font-weight: ${props => String(props.theme.fontWeight.bold)};
|
||||
color: ${props => props.theme.colors.transactionsDetailsLabel};
|
||||
margin-bottom: 5px;
|
||||
letter-spacing: 0.25px;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import dateFns from 'date-fns';
|
||||
|
@ -34,7 +35,7 @@ const Icon = styled.img`
|
|||
|
||||
/* eslint-disable max-len */
|
||||
const TransactionTypeLabel = styled(TextComponent)`
|
||||
color: ${props => (props.isReceived ? props.theme.colors.transactionReceived : props.theme.colors.transactionSent)};
|
||||
color: ${(props: PropsWithTheme<{ isReceived: boolean }>) => (props.isReceived ? props.theme.colors.transactionReceived : props.theme.colors.transactionSent)};
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
/* eslint-enable max-len */
|
||||
|
@ -42,7 +43,7 @@ const TransactionTypeLabel = styled(TextComponent)`
|
|||
const TransactionAddress = styled(TextComponent)`
|
||||
color: #a7a7a7;
|
||||
|
||||
${Wrapper}:hover & {
|
||||
${String(Wrapper)}:hover & {
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
|
||||
import { ColumnComponent } from './column';
|
||||
import { Button } from './button';
|
||||
|
@ -17,6 +17,7 @@ const AddressWrapper = styled.div`
|
|||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
padding: 7px 13px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
|
@ -35,68 +36,29 @@ const Input = styled.input`
|
|||
}
|
||||
`;
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const Btn = styled(Button)`
|
||||
border-width: 1px;
|
||||
font-weight: ${props => props.theme.fontWeight.regular};
|
||||
border-color: ${props => (props.isVisible ? props.theme.colors.primary : props.theme.colors.buttonBorderColor)};
|
||||
padding: 8px 10px;
|
||||
min-width: 260px;
|
||||
|
||||
img {
|
||||
height: 12px;
|
||||
width: 20px;
|
||||
}
|
||||
`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const QRCodeWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
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 PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
export class WalletAddress extends Component<Props, State> {
|
||||
state = {
|
||||
isVisible: false,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { isVisible: Boolean(props.isVisible) };
|
||||
}
|
||||
|
||||
show = () => this.setState(() => ({ isVisible: true }));
|
||||
|
||||
hide = () => this.setState(() => ({ isVisible: false }));
|
||||
|
@ -105,7 +67,6 @@ export class WalletAddress extends PureComponent<Props, State> {
|
|||
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%'>
|
||||
|
@ -115,43 +76,18 @@ export class WalletAddress extends PureComponent<Props, State> {
|
|||
onChange={() => {}}
|
||||
onFocus={event => event.currentTarget.select()}
|
||||
/>
|
||||
<Btn
|
||||
<Button
|
||||
icon={eyeIcon}
|
||||
label={buttonLabel}
|
||||
label={`${isVisible ? 'Hide' : 'Show'} full address and QR Code`}
|
||||
onClick={toggleVisibility}
|
||||
variant='secondary'
|
||||
isVisible={isVisible}
|
||||
/>
|
||||
</AddressWrapper>
|
||||
<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>
|
||||
{!isVisible ? null : (
|
||||
<QRCodeWrapper>
|
||||
<QRCode value={address} />
|
||||
</QRCodeWrapper>
|
||||
)}
|
||||
</ColumnComponent>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
@ -38,7 +39,7 @@ const Label = styled(TextComponent)`
|
|||
|
||||
const USDValue = styled(TextComponent)`
|
||||
opacity: 0.5;
|
||||
font-weight: ${props => props.theme.fontWeight.light};
|
||||
font-weight: ${props => String(props.theme.fontWeight.light)};
|
||||
`;
|
||||
|
||||
const ShieldedValue = styled(Label)`
|
||||
|
|
|
@ -13,7 +13,7 @@ import { DoczWrapper } from '../theme.js'
|
|||
|
||||
<PropsTable of={WalletSummaryComponent} />
|
||||
|
||||
## Basic usage
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import React, { type ComponentType, Component } from 'react';
|
||||
|
||||
import { LoadingScreen } from './loading-screen';
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
import React from 'react';
|
||||
|
||||
export const ZcashLogo = () => (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='-75 -10 175 175'>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='-75 -10 175 175'
|
||||
>
|
||||
<defs>
|
||||
<style>{'.a{ fill:#040508; }'}</style>
|
||||
</defs>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import isDev from 'electron-is-dev';
|
||||
|
||||
export const ZCASH_EXPLORER_BASE_URL = isDev
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import DashboardIcon from '../assets/images/dashboard_icon.svg';
|
||||
import DashboardIconActive from '../assets/images/dashboard_icon_active.svg';
|
||||
import ConsoleIcon from '../assets/images/console_icon.svg';
|
||||
|
@ -25,8 +26,7 @@ export const MENU_OPTIONS = [
|
|||
{
|
||||
label: 'Dashboard',
|
||||
route: DASHBOARD_ROUTE,
|
||||
// eslint-disable-next-line
|
||||
icon: (isActive: boolean) => isActive ? DashboardIconActive : DashboardIcon,
|
||||
icon: (isActive: boolean) => (isActive ? DashboardIconActive : DashboardIcon),
|
||||
},
|
||||
{
|
||||
label: 'Send',
|
||||
|
@ -41,8 +41,7 @@ export const MENU_OPTIONS = [
|
|||
{
|
||||
label: 'Transactions',
|
||||
route: TRANSACTIONS_ROUTE,
|
||||
// eslint-disable-next-line
|
||||
icon: (isActive: boolean) => isActive ? TransactionsIconActive : TransactionsIcon,
|
||||
icon: (isActive: boolean) => (isActive ? TransactionsIconActive : TransactionsIcon),
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { closeErrorModal } from '../redux/modules/app';
|
||||
|
||||
import { LayoutComponent } from '../components/layout';
|
||||
|
||||
import type { Dispatch } from '../types/redux';
|
||||
import type { AppState } from '../types/app-state';
|
||||
|
||||
const mapStateToProps = ({ app }: AppState) => ({
|
||||
isErrorModalVisible: app.isErrorModalVisible,
|
||||
error: app.error,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
closeErrorModal: () => dispatch(closeErrorModal()),
|
||||
});
|
||||
|
||||
// $FlowFixMe
|
||||
export const AppContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(LayoutComponent);
|
|
@ -8,13 +8,14 @@ import dateFns from 'date-fns';
|
|||
import { BigNumber } from 'bignumber.js';
|
||||
import { DashboardView } from '../views/dashboard';
|
||||
import rpc from '../../services/api';
|
||||
import { listShieldedTransactions } from '../../services/shielded-transactions';
|
||||
import store from '../../config/electron-store';
|
||||
import {
|
||||
loadWalletSummary,
|
||||
loadWalletSummarySuccess,
|
||||
loadWalletSummaryError,
|
||||
} from '../redux/modules/wallet';
|
||||
import { sortBy } from '../utils/sort-by';
|
||||
import { sortByDescend } from '../utils/sort-by-descend';
|
||||
|
||||
import type { AppState } from '../types/app-state';
|
||||
import type { Dispatch } from '../types/redux';
|
||||
|
@ -47,7 +48,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
);
|
||||
}
|
||||
|
||||
const formattedTransactions = flow([
|
||||
const formattedTransactions: Array<Object> = flow([
|
||||
arr => arr.map(transaction => ({
|
||||
transactionId: transaction.txid,
|
||||
type: transaction.category,
|
||||
|
@ -58,10 +59,11 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')),
|
||||
obj => Object.keys(obj).map(day => ({
|
||||
day,
|
||||
list: sortBy('date')(obj[day]),
|
||||
jsDay: new Date(day),
|
||||
list: sortByDescend('date')(obj[day]),
|
||||
})),
|
||||
sortBy('day'),
|
||||
])(transactions);
|
||||
sortByDescend('jsDay'),
|
||||
])([...transactions, ...listShieldedTransactions()]);
|
||||
|
||||
if (!zAddresses.length) {
|
||||
const [, newZAddress] = await eres(rpc.z_getnewaddress());
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import eres from 'eres';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
@ -7,6 +8,9 @@ import { ReceiveView } from '../views/receive';
|
|||
import {
|
||||
loadAddressesSuccess,
|
||||
loadAddressesError,
|
||||
getNewAddressSuccess,
|
||||
getNewAddressError,
|
||||
type addressType,
|
||||
} from '../redux/modules/receive';
|
||||
|
||||
import rpc from '../../services/api';
|
||||
|
@ -22,9 +26,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
loadAddresses: async () => {
|
||||
const [zAddressesErr, zAddresses] = await eres(rpc.z_listaddresses());
|
||||
|
||||
const [tAddressesErr, transparentAddresses] = await eres(
|
||||
rpc.getaddressesbyaccount(''),
|
||||
);
|
||||
const [tAddressesErr, transparentAddresses] = await eres(rpc.getaddressesbyaccount(''));
|
||||
|
||||
if (zAddressesErr || tAddressesErr) return dispatch(loadAddressesError({ error: 'Something went wrong!' }));
|
||||
|
||||
|
@ -34,6 +36,15 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
}),
|
||||
);
|
||||
},
|
||||
getNewAddress: async ({ type }: { type: addressType }) => {
|
||||
const [error, address] = await eres(
|
||||
type === 'shielded' ? rpc.z_getnewaddress() : rpc.getnewaddress(''),
|
||||
);
|
||||
|
||||
if (error || !address) return dispatch(getNewAddressError({ error: 'Unable to generate a new address' }));
|
||||
|
||||
dispatch(getNewAddressSuccess({ address }));
|
||||
},
|
||||
});
|
||||
|
||||
// $FlowFixMe
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import eres from 'eres';
|
||||
import { connect } from 'react-redux';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
|
||||
import store from '../../config/electron-store';
|
||||
|
@ -15,9 +16,12 @@ import {
|
|||
resetSendTransaction,
|
||||
validateAddressSuccess,
|
||||
validateAddressError,
|
||||
loadAddressBalanceSuccess,
|
||||
loadAddressBalanceError,
|
||||
} from '../redux/modules/send';
|
||||
|
||||
import { filterObjectNullKeys } from '../utils/filter-object-null-keys';
|
||||
import { saveShieldedTransaction } from '../../services/shielded-transactions';
|
||||
|
||||
import type { AppState } from '../types/app-state';
|
||||
import type { Dispatch } from '../types/redux';
|
||||
|
@ -32,8 +36,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,
|
||||
|
@ -89,6 +93,15 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
|
||||
if (operationStatus && operationStatus.status === 'success') {
|
||||
clearInterval(interval);
|
||||
if (from.startsWith('z')) {
|
||||
saveShieldedTransaction({
|
||||
category: 'send',
|
||||
time: Date.now() / 1000,
|
||||
address: '(Shielded)',
|
||||
amount: new BigNumber(amount).toNumber(),
|
||||
memo,
|
||||
});
|
||||
}
|
||||
dispatch(sendTransactionSuccess({ operationId: operationStatus.result.txid }));
|
||||
}
|
||||
|
||||
|
@ -140,6 +153,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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { SettingsView } from '../views/settings';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import eres from 'eres';
|
||||
import { connect } from 'react-redux';
|
||||
import flow from 'lodash.flow';
|
||||
import groupBy from 'lodash.groupby';
|
||||
import dateFns from 'date-fns';
|
||||
|
@ -13,9 +14,10 @@ import {
|
|||
loadTransactionsError,
|
||||
} from '../redux/modules/transactions';
|
||||
import rpc from '../../services/api';
|
||||
import { listShieldedTransactions } from '../../services/shielded-transactions';
|
||||
import store from '../../config/electron-store';
|
||||
|
||||
import { sortBy } from '../utils/sort-by';
|
||||
import { sortByDescend } from '../utils/sort-by-descend';
|
||||
|
||||
import type { AppState } from '../types/app-state';
|
||||
import type { Dispatch } from '../types/redux';
|
||||
|
@ -31,7 +33,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
getTransactions: async () => {
|
||||
dispatch(loadTransactions());
|
||||
|
||||
const [transactionsErr, transactions = []] = await eres(rpc.listtransactions());
|
||||
const [transactionsErr, transactions = []] = await eres(rpc.listtransactions('', 200));
|
||||
|
||||
if (transactionsErr) {
|
||||
return dispatch(loadTransactionsError({ error: transactionsErr.message }));
|
||||
|
@ -48,10 +50,11 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|||
arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')),
|
||||
obj => Object.keys(obj).map(day => ({
|
||||
day,
|
||||
list: sortBy('date')(obj[day]),
|
||||
jsDay: new Date(day),
|
||||
list: sortByDescend('date')(obj[day]),
|
||||
})),
|
||||
sortBy('day'),
|
||||
])(transactions);
|
||||
sortByDescend('jsDay'),
|
||||
])([...transactions, ...listShieldedTransactions()]);
|
||||
|
||||
dispatch(
|
||||
loadTransactionsSuccess({
|
||||
|
|
|
@ -7,6 +7,7 @@ import thunk from 'redux-thunk';
|
|||
import type { RouterHistory } from 'react-router-dom';
|
||||
|
||||
import { createRootReducer } from './modules/reducer';
|
||||
import { errorHandler } from './errorHandler';
|
||||
|
||||
export const history: RouterHistory = createHashHistory();
|
||||
|
||||
|
@ -14,7 +15,8 @@ const shouldEnableDevTools = (process.env.NODE_ENV !== 'production' || process.e
|
|||
&& window.devToolsExtension;
|
||||
|
||||
export const configureStore = (initialState: Object) => {
|
||||
const middleware = applyMiddleware(thunk, routerMiddleware(history));
|
||||
// $FlowFixMe
|
||||
const middleware = applyMiddleware(thunk, routerMiddleware(history), errorHandler);
|
||||
|
||||
const enhancer = compose(
|
||||
middleware,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// @flow
|
||||
import { LOAD_ADDRESSES_ERROR } from './modules/receive';
|
||||
import { LOAD_TRANSACTIONS_ERROR } from './modules/transactions';
|
||||
import { LOAD_WALLET_SUMMARY_ERROR } from './modules/wallet';
|
||||
import { showErrorModal } from './modules/app';
|
||||
|
||||
import type { Middleware } from '../types/redux';
|
||||
|
||||
const ERRORS = [LOAD_ADDRESSES_ERROR, LOAD_TRANSACTIONS_ERROR, LOAD_WALLET_SUMMARY_ERROR];
|
||||
|
||||
export const errorHandler: Middleware = ({ dispatch }) => next => (action) => {
|
||||
// eslint-disable-next-line max-len
|
||||
if (ERRORS.includes(action.type)) return dispatch(showErrorModal({ error: action.payload.error || 'Something went wrong!' }));
|
||||
|
||||
return next(action);
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
// @flow
|
||||
|
||||
import type { Action } from '../../types/redux';
|
||||
|
||||
// Actions
|
||||
export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL';
|
||||
export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL';
|
||||
|
||||
export const showErrorModal = ({ error }: { error: string }) => ({
|
||||
type: SHOW_ERROR_MODAL,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
export const closeErrorModal = () => ({
|
||||
type: HIDE_ERROR_MODAL,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
export type State = {
|
||||
isErrorModalVisible: boolean,
|
||||
error: string | null,
|
||||
};
|
||||
|
||||
const initialState: State = {
|
||||
isErrorModalVisible: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
export default (state: State = initialState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case SHOW_ERROR_MODAL:
|
||||
return { isErrorModalVisible: true, error: action.payload.error };
|
||||
case HIDE_ERROR_MODAL:
|
||||
return { isErrorModalVisible: false, error: null };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -1,9 +1,12 @@
|
|||
// @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 GET_NEW_ADDRESS_SUCCESS = 'GET_NEW_ADDRESS_SUCCESS';
|
||||
export const GET_NEW_ADDRESS_ERROR = 'GET_NEW_ADDRESS_ERROR';
|
||||
|
||||
export const loadAddressesSuccess = ({ addresses }: { addresses: string[] }) => ({
|
||||
type: LOAD_ADDRESSES_SUCCESS,
|
||||
|
@ -17,6 +20,22 @@ export const loadAddressesError = ({ error }: { error: string }) => ({
|
|||
payload: { error },
|
||||
});
|
||||
|
||||
export const getNewAddressSuccess = ({ address }: { address: string }) => ({
|
||||
type: GET_NEW_ADDRESS_SUCCESS,
|
||||
payload: {
|
||||
address,
|
||||
},
|
||||
});
|
||||
|
||||
export const getNewAddressError = ({ error }: { error: string }) => ({
|
||||
type: GET_NEW_ADDRESS_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
export type addressType = 'transparent' | 'shielded';
|
||||
|
||||
export type State = {
|
||||
addresses: string[],
|
||||
error: string | null,
|
||||
|
@ -40,6 +59,16 @@ export default (state: State = initialState, action: Action) => {
|
|||
error: action.payload.error,
|
||||
addresses: [],
|
||||
};
|
||||
case GET_NEW_ADDRESS_SUCCESS:
|
||||
return {
|
||||
error: null,
|
||||
addresses: [...state.addresses, action.payload.address],
|
||||
};
|
||||
case GET_NEW_ADDRESS_ERROR:
|
||||
return {
|
||||
...state,
|
||||
error: action.payload.error,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { combineReducers } from 'redux';
|
|||
import { connectRouter } from 'connected-react-router';
|
||||
import type { RouterHistory } from 'react-router-dom';
|
||||
|
||||
import app from './app';
|
||||
import wallet from './wallet';
|
||||
import transactions from './transactions';
|
||||
import send from './send';
|
||||
|
@ -11,6 +12,7 @@ import receive from './receive';
|
|||
|
||||
// $FlowFixMe
|
||||
export const createRootReducer = (history: RouterHistory) => combineReducers({
|
||||
app,
|
||||
walletSummary: wallet,
|
||||
transactions,
|
||||
sendStatus: send,
|
||||
|
|
|
@ -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,7 +83,9 @@ const initialState: State = {
|
|||
operationId: null,
|
||||
isToAddressValid: false,
|
||||
zecPrice: 0,
|
||||
addressBalance: 0,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
export default (state: State = initialState, action: Action): State => {
|
||||
switch (action.type) {
|
||||
|
@ -103,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:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import type { Action } from '../../types/redux';
|
||||
import type { Transaction } from '../../components/transaction-item';
|
||||
|
||||
|
@ -46,6 +47,7 @@ const initialState = {
|
|||
error: null,
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
export default (state: State = initialState, action: Action) => {
|
||||
switch (action.type) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import type { Action } from '../../types/redux';
|
||||
import type { TransactionsList } from './transactions';
|
||||
|
||||
|
@ -65,6 +66,7 @@ const initialState = {
|
|||
addresses: [],
|
||||
transactions: [],
|
||||
};
|
||||
|
||||
// eslint-disable-next-line
|
||||
export default (state: State = initialState, action: Action) => {
|
||||
switch (action.type) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
import { compose } from 'redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { RouterComponent } from './router';
|
||||
|
|
|
@ -15,7 +15,7 @@ import { ReceiveContainer } from '../containers/receive';
|
|||
import { SettingsContainer } from '../containers/settings';
|
||||
import { NotFoundView } from '../views/not-found';
|
||||
import { ConsoleView } from '../views/console';
|
||||
import { LayoutComponent } from '../components/layout';
|
||||
import { AppContainer as LayoutComponent } from '../containers/app';
|
||||
import { HeaderComponent } from '../components/header';
|
||||
|
||||
import {
|
||||
|
@ -55,17 +55,39 @@ export const RouterComponent = ({
|
|||
<FullWrapper>
|
||||
<HeaderComponent title={getTitle(location.pathname)} />
|
||||
<ContentWrapper>
|
||||
<SidebarContainer location={location} history={history} />
|
||||
<SidebarContainer
|
||||
location={location}
|
||||
history={history}
|
||||
/>
|
||||
{/* $FlowFixMe */}
|
||||
<LayoutComponent>
|
||||
<ScrollTopComponent>
|
||||
<Switch>
|
||||
<Route exact path={DASHBOARD_ROUTE} component={DashboardContainer} />
|
||||
<Route path={SEND_ROUTE} component={SendContainer} />
|
||||
<Route path={RECEIVE_ROUTE} component={ReceiveContainer} />
|
||||
<Route path={SETTINGS_ROUTE} component={SettingsContainer} />
|
||||
<Route path={CONSOLE_ROUTE} component={ConsoleView} />
|
||||
<Route path={TRANSACTIONS_ROUTE} component={TransactionsContainer} />
|
||||
<Route
|
||||
exact
|
||||
path={DASHBOARD_ROUTE}
|
||||
component={DashboardContainer}
|
||||
/>
|
||||
<Route
|
||||
path={SEND_ROUTE}
|
||||
component={SendContainer}
|
||||
/>
|
||||
<Route
|
||||
path={RECEIVE_ROUTE}
|
||||
component={ReceiveContainer}
|
||||
/>
|
||||
<Route
|
||||
path={SETTINGS_ROUTE}
|
||||
component={SettingsContainer}
|
||||
/>
|
||||
<Route
|
||||
path={CONSOLE_ROUTE}
|
||||
component={ConsoleView}
|
||||
/>
|
||||
<Route
|
||||
path={TRANSACTIONS_ROUTE}
|
||||
component={TransactionsContainer}
|
||||
/>
|
||||
<Route component={NotFoundView} />
|
||||
</Switch>
|
||||
</ScrollTopComponent>
|
||||
|
|
125
app/theme.js
125
app/theme.js
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import React, { Fragment, type Node } from 'react';
|
||||
import theme from 'styled-theming';
|
||||
import { ThemeProvider, createGlobalStyle } from 'styled-components';
|
||||
// $FlowFixMe
|
||||
|
@ -11,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';
|
||||
|
@ -30,7 +30,7 @@ const selectButtonShadow = 'rgba(238,201,76,0.65)';
|
|||
const statusPillLabel = '#727272';
|
||||
const transactionsDetailsLabel = transactionsDate;
|
||||
|
||||
const appTheme = {
|
||||
const appTheme: AppTheme = {
|
||||
mode: DARK,
|
||||
fontFamily: 'Roboto',
|
||||
fontWeight: {
|
||||
|
@ -53,29 +53,98 @@ const appTheme = {
|
|||
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',
|
||||
|
@ -94,9 +163,7 @@ export const GlobalStyle = createGlobalStyle`
|
|||
}
|
||||
`;
|
||||
|
||||
/* eslint-disable react/prop-types */
|
||||
// $FlowFixMe
|
||||
export const DoczWrapper = ({ children }) => (
|
||||
export const DoczWrapper = ({ children }: { children: () => Node<*> }) => (
|
||||
<ThemeProvider theme={appTheme}>
|
||||
<Fragment>
|
||||
<GlobalStyle />
|
||||
|
|
|
@ -4,10 +4,12 @@ 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';
|
||||
import type { State as App } from '../redux/modules/app';
|
||||
|
||||
export type AppState = {
|
||||
walletSummary: WalletSummaryState,
|
||||
transactions: TransactionsState,
|
||||
sendStatus: SendState,
|
||||
receive: ReceiveState,
|
||||
app: App,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
// @flow
|
||||
|
||||
type State = {||};
|
||||
import type { AppState } from './app-state';
|
||||
|
||||
export type Action = { type: $Subtype<string>, payload: Object };
|
||||
export type GetState = () => State;
|
||||
export type GetState = () => AppState;
|
||||
export type Dispatch = (action: Action) => void;
|
||||
export type Middleware = ({ dispatch: Dispatch, getState: GetState }) => (
|
||||
(Action) => void,
|
||||
) => Action => void;
|
||||
|
|
|
@ -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('');
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
export const filterObjectNullKeys = (obj: Object) => Object.keys(obj).reduce((acc, cur) => {
|
||||
if (obj[cur] === null || obj[cur] === undefined || obj[cur] === '') {
|
||||
return acc;
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
// @flow
|
||||
export const formatNumber = ({ value, append = '' }: { value: number | string, append?: string }) => `${append}${(value || 0).toLocaleString()}`;
|
||||
|
||||
export const formatNumber = ({ value, append = '' }: { value: number, append?: string }) => `${append}${(value || 0).toLocaleString()}`;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
|
||||
// eslint-disable-next-line
|
||||
import electron from 'electron';
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
/* eslint-disable max-len */
|
||||
|
||||
// $FlowFixMe
|
||||
export const sortByDescend = <T>(field: string) => (arr: T[]): T[] => arr.sort((a, b) => (a[field] < b[field] ? 1 : -1));
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue