Merge pull request #14 from andrerfneves/feature/wallet-summary
Feature/wallet summary
|
@ -0,0 +1 @@
|
||||||
|
ZEC_PRICE_API_KEY=
|
|
@ -34,7 +34,7 @@
|
||||||
"max-len": [
|
"max-len": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"code": 120,
|
"code": 80,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"ignoreUrls": true,
|
"ignoreUrls": true,
|
||||||
"ignoreComments": true,
|
"ignoreComments": true,
|
||||||
|
@ -43,7 +43,6 @@
|
||||||
"ignoreTrailingComments": true
|
"ignoreTrailingComments": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"consistent-return": 0,
|
"consistent-return": 0
|
||||||
"react/destructuring-assignment": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,5 @@ flow-coverage
|
||||||
build
|
build
|
||||||
.docz
|
.docz
|
||||||
coverage
|
coverage
|
||||||
flow-typed
|
flow-typed
|
||||||
|
.env
|
|
@ -1,29 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import configureStore from 'redux-mock-store';
|
|
||||||
|
|
||||||
import { ADD_TODO, addTodo } from '../../app/redux/modules/todo';
|
|
||||||
|
|
||||||
const store = configureStore()();
|
|
||||||
|
|
||||||
describe('Todo Actions', () => {
|
|
||||||
beforeEach(() => store.clearActions());
|
|
||||||
|
|
||||||
test('should create an action to add a new todo', () => {
|
|
||||||
const text = 'Hello World!';
|
|
||||||
|
|
||||||
store.dispatch(addTodo(text));
|
|
||||||
|
|
||||||
expect(store.getActions()[0]).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: ADD_TODO,
|
|
||||||
payload: {
|
|
||||||
text,
|
|
||||||
id: expect.any(String),
|
|
||||||
editing: false,
|
|
||||||
createdAt: expect.any(Number),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LOAD_WALLET_SUMMARY,
|
||||||
|
LOAD_WALLET_SUMMARY_SUCCESS,
|
||||||
|
LOAD_WALLET_SUMMARY_ERROR,
|
||||||
|
loadWalletSummary,
|
||||||
|
loadWalletSummarySuccess,
|
||||||
|
loadWalletSummaryError,
|
||||||
|
} from '../../app/redux/modules/wallet';
|
||||||
|
|
||||||
|
const store = configureStore()();
|
||||||
|
|
||||||
|
describe('WalletSummary Actions', () => {
|
||||||
|
beforeEach(() => store.clearActions());
|
||||||
|
|
||||||
|
test('should create an action to load wallet summary', () => {
|
||||||
|
store.dispatch(loadWalletSummary());
|
||||||
|
|
||||||
|
expect(store.getActions()[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: LOAD_WALLET_SUMMARY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create an action to load wallet summary', () => {
|
||||||
|
const payload = {
|
||||||
|
total: 5000,
|
||||||
|
transparent: 5000,
|
||||||
|
shielded: 5000,
|
||||||
|
addresses: [],
|
||||||
|
transactions: {},
|
||||||
|
zecPrice: 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
store.dispatch(loadWalletSummarySuccess(payload));
|
||||||
|
|
||||||
|
expect(store.getActions()[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: LOAD_WALLET_SUMMARY_SUCCESS,
|
||||||
|
payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create an action to load wallet summary with error', () => {
|
||||||
|
const payload = {
|
||||||
|
error: 'Something went wrong!',
|
||||||
|
};
|
||||||
|
|
||||||
|
store.dispatch(loadWalletSummaryError(payload));
|
||||||
|
|
||||||
|
expect(store.getActions()[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
type: LOAD_WALLET_SUMMARY_ERROR,
|
||||||
|
payload,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -13,7 +13,7 @@ describe('<Sidebar />', () => {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<SidebarComponent />
|
<SidebarComponent location={{ pathname: '/', hash: '/', search: '' }} />
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { getApp } from '../setup/utils';
|
import { getApp } from '../setup/utils';
|
||||||
|
|
||||||
describe('ZCash', () => {
|
describe('Zcash', () => {
|
||||||
const app = getApp();
|
const app = getApp();
|
||||||
|
|
||||||
beforeEach(() => app.start());
|
beforeEach(() => app.start());
|
||||||
|
@ -11,7 +11,8 @@ describe('ZCash', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should open the window', () => {
|
test('should open the window', () => {
|
||||||
app.client.getWindowCount()
|
app.client
|
||||||
|
.getWindowCount()
|
||||||
.then((count: number) => expect(count).toEqual(1));
|
.then((count: number) => expect(count).toEqual(1));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import todoReducer, { ADD_TODO } from '../../app/redux/modules/todo';
|
|
||||||
|
|
||||||
describe('Todo Reducer', () => {
|
|
||||||
test('should return the valid initial state', () => {
|
|
||||||
const initialState = [];
|
|
||||||
const action = {
|
|
||||||
type: 'UNKNOWN_ACTION',
|
|
||||||
payload: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(todoReducer(undefined, action)).toEqual(initialState);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should add a new todo', () => {
|
|
||||||
const action = {
|
|
||||||
type: ADD_TODO,
|
|
||||||
payload: {
|
|
||||||
id: 'abc123',
|
|
||||||
text: 'Hello World!',
|
|
||||||
editing: false,
|
|
||||||
createdAt: new Date().getTime(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const expectedState = [action.payload];
|
|
||||||
|
|
||||||
expect(todoReducer(undefined, action)).toEqual(expectedState);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
// @flow
|
||||||
|
import walletSummaryReducer, {
|
||||||
|
LOAD_WALLET_SUMMARY,
|
||||||
|
LOAD_WALLET_SUMMARY_SUCCESS,
|
||||||
|
LOAD_WALLET_SUMMARY_ERROR,
|
||||||
|
} from '../../app/redux/modules/wallet';
|
||||||
|
|
||||||
|
describe('WalletSummary Reducer', () => {
|
||||||
|
test('should return the valid initial state', () => {
|
||||||
|
const initialState = {
|
||||||
|
total: 0,
|
||||||
|
shielded: 0,
|
||||||
|
transparent: 0,
|
||||||
|
error: null,
|
||||||
|
isLoading: false,
|
||||||
|
dollarValue: 0,
|
||||||
|
};
|
||||||
|
const action = {
|
||||||
|
type: 'UNKNOWN_ACTION',
|
||||||
|
payload: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(walletSummaryReducer(undefined, action)).toEqual(initialState);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should load the wallet summary', () => {
|
||||||
|
const action = {
|
||||||
|
type: LOAD_WALLET_SUMMARY,
|
||||||
|
payload: {},
|
||||||
|
};
|
||||||
|
const expectedState = {
|
||||||
|
total: 0,
|
||||||
|
shielded: 0,
|
||||||
|
transparent: 0,
|
||||||
|
error: null,
|
||||||
|
isLoading: true,
|
||||||
|
dollarValue: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should load the wallet summary with success', () => {
|
||||||
|
const action = {
|
||||||
|
type: LOAD_WALLET_SUMMARY_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
total: 1000,
|
||||||
|
transparent: 1000,
|
||||||
|
shielded: 1000,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expectedState = {
|
||||||
|
...action.payload,
|
||||||
|
error: null,
|
||||||
|
isLoading: false,
|
||||||
|
dollarValue: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should load the wallet summary with error', () => {
|
||||||
|
const action = {
|
||||||
|
type: LOAD_WALLET_SUMMARY_ERROR,
|
||||||
|
payload: {
|
||||||
|
error: 'Something went wrong',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const expectedState = {
|
||||||
|
total: 0,
|
||||||
|
shielded: 0,
|
||||||
|
transparent: 0,
|
||||||
|
error: action.payload.error,
|
||||||
|
isLoading: false,
|
||||||
|
dollarValue: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||||
|
});
|
||||||
|
});
|
18
app/app.js
|
@ -13,13 +13,15 @@ const store = configureStore({});
|
||||||
|
|
||||||
export default () => (
|
export default () => (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Provider store={store}>
|
<Fragment>
|
||||||
<ConnectedRouter history={history}>
|
<GlobalStyle />
|
||||||
<Fragment>
|
<Provider store={store}>
|
||||||
<GlobalStyle />
|
<ConnectedRouter history={history}>
|
||||||
<Router />
|
<Fragment>
|
||||||
</Fragment>
|
<Router />
|
||||||
</ConnectedRouter>
|
</Fragment>
|
||||||
</Provider>
|
</ConnectedRouter>
|
||||||
|
</Provider>
|
||||||
|
</Fragment>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.891 10.875"><defs><style>.a{fill:#3b3b3f;stroke:#3b3b3f;stroke-width:0.5px;}</style></defs><g transform="translate(0.283 0.286)"><g transform="translate(-0.001 0.004)"><path class="a" d="M2.269,5.239c1.185,1.183,2.35,2.345,3.513,3.508a.938.938,0,1,1-1.307,1.342c-.786-.777-1.564-1.561-2.346-2.342q-.89-.89-1.779-1.78A.943.943,0,0,1,.345,4.5Q2.4,2.446,4.449.395A.938.938,0,0,1,5.441.115a.891.891,0,0,1,.638.7.941.941,0,0,1-.307.917q-1.654,1.649-3.3,3.3C2.408,5.091,2.349,5.156,2.269,5.239Z" transform="translate(0.001 -0.067)"/><path class="a" d="M297.98,5.214c-1.2-1.2-2.36-2.357-3.522-3.512a1.015,1.015,0,0,1-.336-.674.944.944,0,0,1,.536-.9.932.932,0,0,1,1.071.195c.592.582,1.177,1.173,1.765,1.76l2.344,2.344a.959.959,0,0,1,.01,1.534q-2.027,2.028-4.054,4.057a.948.948,0,0,1-1.053.283.933.933,0,0,1-.307-1.576q1.655-1.664,3.319-3.319C297.815,5.341,297.888,5.29,297.98,5.214Z" transform="translate(-277.88 -0.034)"/><path class="a" d="M142.53,9.378a3.646,3.646,0,0,1,.149-.453q1.983-4.169,3.976-8.332a.939.939,0,1,1,1.7.8q-1.995,4.19-4,8.375a.93.93,0,0,1-1.1.533A1,1,0,0,1,142.53,9.378Z" transform="translate(-134.629 -0.004)"/></g></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.891 10.875"><defs><style>.a{fill:#f4b728;stroke:#3b3b3f;stroke-width:0.5px;}</style></defs><g transform="translate(0.283 0.286)"><g transform="translate(-0.001 0.004)"><path class="a" d="M2.269,5.239c1.185,1.183,2.35,2.345,3.513,3.508a.938.938,0,1,1-1.307,1.342c-.786-.777-1.564-1.561-2.346-2.342q-.89-.89-1.779-1.78A.943.943,0,0,1,.345,4.5Q2.4,2.446,4.449.395A.938.938,0,0,1,5.441.115a.891.891,0,0,1,.638.7.941.941,0,0,1-.307.917q-1.654,1.649-3.3,3.3C2.408,5.091,2.349,5.156,2.269,5.239Z" transform="translate(0.001 -0.067)"/><path class="a" d="M297.98,5.214c-1.2-1.2-2.36-2.357-3.522-3.512a1.015,1.015,0,0,1-.336-.674.944.944,0,0,1,.536-.9.932.932,0,0,1,1.071.195c.592.582,1.177,1.173,1.765,1.76l2.344,2.344a.959.959,0,0,1,.01,1.534q-2.027,2.028-4.054,4.057a.948.948,0,0,1-1.053.283.933.933,0,0,1-.307-1.576q1.655-1.664,3.319-3.319C297.815,5.341,297.888,5.29,297.98,5.214Z" transform="translate(-277.88 -0.034)"/><path class="a" d="M142.53,9.378a3.646,3.646,0,0,1,.149-.453q1.983-4.169,3.976-8.332a.939.939,0,1,1,1.7.8q-1.995,4.19-4,8.375a.93.93,0,0,1-1.1.533A1,1,0,0,1,142.53,9.378Z" transform="translate(-134.629 -0.004)"/></g></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 19.091"><defs><style>.a{fill:#3b3b3f;}</style></defs><rect class="a" width="9.091" height="9.091" rx="2"/><rect class="a" width="20" height="8.182" rx="2" transform="translate(0 10.909)"/><rect class="a" width="9.091" height="9.091" rx="2" transform="translate(10.909)"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 19.091"><defs><style>.a{fill:#f4b728;}</style></defs><rect class="a" width="9.091" height="9.091" rx="2"/><rect class="a" width="20" height="8.182" rx="2" transform="translate(0 10.909)"/><rect class="a" width="9.091" height="9.091" rx="2" transform="translate(10.909)"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||||
|
<svg enable-background="new 0 0 256 256" height="256px" id="Layer_1" version="1.1" viewBox="0 0 256 256" width="256px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<circle fill="white" cx="57.6" cy="128" r="20"/>
|
||||||
|
<circle fill="white" cx="128" cy="128" r="20"/>
|
||||||
|
<circle fill="white" cx="198.4" cy="128" r="20"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 502 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.854"><defs><style>.a{fill:#3b3b3f;}</style></defs><g transform="translate(0)"><path class="a" d="M6.612,39.045h0a.9.9,0,0,1-.9.9h-3.9v9.638H5.959c.5,0,.675.176.675.682,0,.768,0,1.536,0,2.3,0,.424.191.619.617.62q2.387,0,4.774,0c.436,0,.627-.2.628-.638,0-.784,0-1.568,0-2.352,0-.429.186-.616.616-.616q1.94,0,3.88,0h.3V39.956H13.569a.9.9,0,0,1-.9-.9v-.022a.9.9,0,0,1,.9-.9c1.227,0,3.415,0,4.657,0a.948.948,0,0,1,1.053,1.066q0,7.972,0,15.944A1.048,1.048,0,0,1,18.23,56.2H1.041A.931.931,0,0,1,0,55.152q0-8.007,0-16.015a.912.912,0,0,1,.951-1c1.293-.006,3.529,0,4.769.006A.9.9,0,0,1,6.612,39.045Z" transform="translate(0.002 -36.345)"/><path class="a" d="M112.724,5.946l.016-.016a1.125,1.125,0,0,1,1.6.005l1.14,1.154.06-.024V1.222A1.221,1.221,0,0,1,116.756,0h0a1.222,1.222,0,0,1,1.222,1.222V7.057l1.143-1.144a1.125,1.125,0,0,1,1.6,0l.04.041a1.125,1.125,0,0,1,0,1.586l-3.4,3.4a.86.86,0,0,1-1.216,0l-3.409-3.41A1.125,1.125,0,0,1,112.724,5.946Z" transform="translate(-107.108)"/></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.854"><defs><style>.a{fill:#f4b728;}</style></defs><g transform="translate(0)"><path class="a" d="M6.612,39.045h0a.9.9,0,0,1-.9.9h-3.9v9.638H5.959c.5,0,.675.176.675.682,0,.768,0,1.536,0,2.3,0,.424.191.619.617.62q2.387,0,4.774,0c.436,0,.627-.2.628-.638,0-.784,0-1.568,0-2.352,0-.429.186-.616.616-.616q1.94,0,3.88,0h.3V39.956H13.569a.9.9,0,0,1-.9-.9v-.022a.9.9,0,0,1,.9-.9c1.227,0,3.415,0,4.657,0a.948.948,0,0,1,1.053,1.066q0,7.972,0,15.944A1.048,1.048,0,0,1,18.23,56.2H1.041A.931.931,0,0,1,0,55.152q0-8.007,0-16.015a.912.912,0,0,1,.951-1c1.293-.006,3.529,0,4.769.006A.9.9,0,0,1,6.612,39.045Z" transform="translate(0.002 -36.345)"/><path class="a" d="M112.724,5.946l.016-.016a1.125,1.125,0,0,1,1.6.005l1.14,1.154.06-.024V1.222A1.221,1.221,0,0,1,116.756,0h0a1.222,1.222,0,0,1,1.222,1.222V7.057l1.143-1.144a1.125,1.125,0,0,1,1.6,0l.04.041a1.125,1.125,0,0,1,0,1.586l-3.4,3.4a.86.86,0,0,1-1.216,0l-3.409-3.41A1.125,1.125,0,0,1,112.724,5.946Z" transform="translate(-107.108)"/></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.283"><defs><style>.a{fill:#3b3b3f;}</style></defs><g transform="translate(0 0)"><path class="a" d="M7.454,19.286c-.055-.021-.111-.038-.163-.063a.657.657,0,0,1-.4-.613c-.005-1.236,0-2.472,0-3.708a.213.213,0,0,1,.049-.12q1.437-1.766,2.878-3.528l6.229-7.635a.277.277,0,0,0,.051-.085.5.5,0,0,0-.042.029L9.744,9.019q-2.5,2.166-5,4.333a.12.12,0,0,1-.148.015Q2.513,12.512.432,11.661a.683.683,0,0,1-.086-1.232L3.87,8.4,18.218.118a.666.666,0,0,1,.751,0,.663.663,0,0,1,.294.717q-.356,2.122-.708,4.245Q18.2,7.241,17.838,9.4q-.428,2.568-.858,5.136-.23,1.38-.458,2.763a.686.686,0,0,1-.969.55q-2.369-.966-4.734-1.937a.138.138,0,0,0-.193.048q-1.254,1.535-2.511,3.066a.778.778,0,0,1-.424.264C7.612,19.286,7.533,19.286,7.454,19.286Z" transform="translate(0.003 -0.005)"/></g></svg>
|
After Width: | Height: | Size: 826 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.283"><defs><style>.a{fill:#f4b728;}</style></defs><g transform="translate(0 0)"><path class="a" d="M7.454,19.286c-.055-.021-.111-.038-.163-.063a.657.657,0,0,1-.4-.613c-.005-1.236,0-2.472,0-3.708a.213.213,0,0,1,.049-.12q1.437-1.766,2.878-3.528l6.229-7.635a.277.277,0,0,0,.051-.085.5.5,0,0,0-.042.029L9.744,9.019q-2.5,2.166-5,4.333a.12.12,0,0,1-.148.015Q2.513,12.512.432,11.661a.683.683,0,0,1-.086-1.232L3.87,8.4,18.218.118a.666.666,0,0,1,.751,0,.663.663,0,0,1,.294.717q-.356,2.122-.708,4.245Q18.2,7.241,17.838,9.4q-.428,2.568-.858,5.136-.23,1.38-.458,2.763a.686.686,0,0,1-.969.55q-2.369-.966-4.734-1.937a.138.138,0,0,0-.193.048q-1.254,1.535-2.511,3.066a.778.778,0,0,1-.424.264C7.612,19.286,7.533,19.286,7.454,19.286Z" transform="translate(0.003 -0.005)"/></g></svg>
|
After Width: | Height: | Size: 826 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.327 20.328"><defs><style>.a{fill:#3b3b3f;fill-rule:evenodd;}</style></defs><path class="a" d="M20.311,9.58a.868.868,0,0,0-.725-.865c-.067-.017-.134-.017-.2-.029-.58-.1-1.159-.2-1.763-.312-.218-.571-.434-1.136-.658-1.713.1-.134.186-.265.283-.39.253-.32.5-.65.772-.953A1.017,1.017,0,0,0,18,3.707c-.146-.143-.271-.306-.408-.457-.577-.638-.859-.679-1.588-.23-.484.3-.961.6-1.471.921A8.953,8.953,0,0,0,12,2.771c-.047-.245-.093-.463-.131-.685-.07-.425-.137-.851-.213-1.276a.874.874,0,0,0-.827-.778,6.587,6.587,0,0,0-1.291,0A.861.861,0,0,0,8.708.8c-.041.248-.067.5-.131.737a6.365,6.365,0,0,1-.242,1.194c-.749.315-1.436.6-2.153.9-.131-.087-.283-.184-.431-.285-.373-.262-.734-.539-1.113-.787A.881.881,0,0,0,3.4,2.658c-.268.245-.527.5-.772.772a.863.863,0,0,0-.09,1.229c.236.361.5.7.746,1.058.125.184.312.338.323.51-.312.752-.6,1.439-.891,2.141-.111.023-.218.047-.329.064-.536.093-1.078.172-1.611.277a.855.855,0,0,0-.76.836,12.027,12.027,0,0,0,0,1.3.828.828,0,0,0,.647.787c.151.047.312.052.466.087a11.385,11.385,0,0,1,1.626.329c.23.609.446,1.177.667,1.765-.189.236-.37.466-.554.7-.227.283-.466.559-.679.854A.888.888,0,0,0,2.2,16.493a10.568,10.568,0,0,0,.816.915.842.842,0,0,0,1.116.157c.181-.1.35-.21.524-.318l1.2-.743c.437.224.83.452,1.241.635s.842.32,1.261.478c.038.186.073.341.1.5.087.492.157.988.262,1.477a.809.809,0,0,0,.848.728c.431.017.865.015,1.3-.006a.781.781,0,0,0,.749-.591c.061-.192.079-.4.125-.6a13.164,13.164,0,0,1,.28-1.492l2.141-.88c.192.131.4.268.6.411.335.236.661.484,1,.708a.863.863,0,0,0,1.232-.082,10.682,10.682,0,0,0,.772-.769.9.9,0,0,0,.093-1.238c-.277-.417-.58-.813-.865-1.224-.1-.146-.184-.306-.245-.408.3-.728.583-1.4.9-2.15.274-.05.565-.1.854-.154.335-.058.676-.1,1.008-.166a.891.891,0,0,0,.813-.935C20.329,10.352,20.332,9.964,20.311,9.58ZM10.188,14a3.779,3.779,0,0,1-3.8-3.825,3.812,3.812,0,1,1,7.624.023A3.769,3.769,0,0,1,10.188,14Z" transform="translate(0 -0.001)"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.327 20.328"><defs><style>.a{fill:#f4b728;fill-rule:evenodd;}</style></defs><path class="a" d="M20.311,9.58a.868.868,0,0,0-.725-.865c-.067-.017-.134-.017-.2-.029-.58-.1-1.159-.2-1.763-.312-.218-.571-.434-1.136-.658-1.713.1-.134.186-.265.283-.39.253-.32.5-.65.772-.953A1.017,1.017,0,0,0,18,3.707c-.146-.143-.271-.306-.408-.457-.577-.638-.859-.679-1.588-.23-.484.3-.961.6-1.471.921A8.953,8.953,0,0,0,12,2.771c-.047-.245-.093-.463-.131-.685-.07-.425-.137-.851-.213-1.276a.874.874,0,0,0-.827-.778,6.587,6.587,0,0,0-1.291,0A.861.861,0,0,0,8.708.8c-.041.248-.067.5-.131.737a6.365,6.365,0,0,1-.242,1.194c-.749.315-1.436.6-2.153.9-.131-.087-.283-.184-.431-.285-.373-.262-.734-.539-1.113-.787A.881.881,0,0,0,3.4,2.658c-.268.245-.527.5-.772.772a.863.863,0,0,0-.09,1.229c.236.361.5.7.746,1.058.125.184.312.338.323.51-.312.752-.6,1.439-.891,2.141-.111.023-.218.047-.329.064-.536.093-1.078.172-1.611.277a.855.855,0,0,0-.76.836,12.027,12.027,0,0,0,0,1.3.828.828,0,0,0,.647.787c.151.047.312.052.466.087a11.385,11.385,0,0,1,1.626.329c.23.609.446,1.177.667,1.765-.189.236-.37.466-.554.7-.227.283-.466.559-.679.854A.888.888,0,0,0,2.2,16.493a10.568,10.568,0,0,0,.816.915.842.842,0,0,0,1.116.157c.181-.1.35-.21.524-.318l1.2-.743c.437.224.83.452,1.241.635s.842.32,1.261.478c.038.186.073.341.1.5.087.492.157.988.262,1.477a.809.809,0,0,0,.848.728c.431.017.865.015,1.3-.006a.781.781,0,0,0,.749-.591c.061-.192.079-.4.125-.6a13.164,13.164,0,0,1,.28-1.492l2.141-.88c.192.131.4.268.6.411.335.236.661.484,1,.708a.863.863,0,0,0,1.232-.082,10.682,10.682,0,0,0,.772-.769.9.9,0,0,0,.093-1.238c-.277-.417-.58-.813-.865-1.224-.1-.146-.184-.306-.245-.408.3-.728.583-1.4.9-2.15.274-.05.565-.1.854-.154.335-.058.676-.1,1.008-.166a.891.891,0,0,0,.813-.935C20.329,10.352,20.332,9.964,20.311,9.58ZM10.188,14a3.779,3.779,0,0,1-3.8-3.825,3.812,3.812,0,1,1,7.624.023A3.769,3.769,0,0,1,10.188,14Z" transform="translate(0 -0.001)"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.342 18.853"><defs><style>.a{fill:#6aeac0;}</style></defs><g transform="translate(34.55 55.562) rotate(180)"><g transform="translate(22.05 41.044)"><path class="a" d="M2.822,2.39h.817l3.889-.011c.69,0,1.381,0,2.071,0a.836.836,0,0,1,.1,1.668,1.591,1.591,0,0,1-.187.007q-4.277,0-8.55,0a1.1,1.1,0,0,1-.511-.093A.811.811,0,0,1,.228,2.67Q1.426,1.444,2.642.233A.8.8,0,0,1,3.777.24a.8.8,0,0,1,.015,1.127c-.295.31-.6.612-.907.918a.4.4,0,0,1-.1.056A.407.407,0,0,1,2.822,2.39Z" transform="translate(6.505 0) rotate(90)"/><path class="a" d="M2.822,1.664h.817l3.889.011c.69,0,1.381,0,2.071,0A.836.836,0,0,0,9.7.011,1.591,1.591,0,0,0,9.517,0Q5.24,0,.967,0A1.1,1.1,0,0,0,.455.093.811.811,0,0,0,.228,1.385q1.2,1.226,2.415,2.437A.808.808,0,0,0,3.792,2.687c-.295-.31-.6-.612-.907-.918a.4.4,0,0,0-.1-.056A.407.407,0,0,0,2.822,1.664Z" transform="translate(4.054 0) rotate(90)"/></g><g transform="translate(16.208 36.709)"><g transform="translate(0 0)"><path class="a" d="M9.568,0c.192.018.384.033.572.054a8.642,8.642,0,0,1,3.97,1.44,9.218,9.218,0,0,1,4.013,5.978,9.494,9.494,0,0,1-.923,6.481.923.923,0,0,0-.047.1.812.812,0,0,1-.919.5.847.847,0,0,1-.666-.85.325.325,0,0,1,.029-.13c.159-.38.322-.76.478-1.143a7.694,7.694,0,0,0,.59-3.456,7.687,7.687,0,0,0-2.4-5.236,7.093,7.093,0,0,0-4.071-1.979A7.2,7.2,0,0,0,4.249,3.579a7.447,7.447,0,0,0-2.5,4.614,7.65,7.65,0,0,0,1.925,6.492,7.156,7.156,0,0,0,4.429,2.377,7.222,7.222,0,0,0,5.453-1.39.387.387,0,0,0,.058-.051.8.8,0,0,1,.9-.17.871.871,0,0,1,.5.8.711.711,0,0,1-.275.572,8.448,8.448,0,0,1-4.48,1.954,8.725,8.725,0,0,1-6.846-2A9.249,9.249,0,0,1,.182,11.337a9.4,9.4,0,0,1-.127-3A9.343,9.343,0,0,1,2.36,3.108,8.962,8.962,0,0,1,7.234.214a8.521,8.521,0,0,1,1.419-.2A1.044,1.044,0,0,0,8.758,0C9.029,0,9.3,0,9.568,0Z" transform="translate(0.01)"/></g></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.342 18.853"><defs><style>.a{fill:#ff6c6c;}</style></defs><g transform="translate(-16.208 -36.709)"><g transform="translate(22.05 41.044)"><path class="a" d="M2.822,2.39h.817l3.889-.011c.69,0,1.381,0,2.071,0a.836.836,0,0,1,.1,1.668,1.591,1.591,0,0,1-.187.007q-4.277,0-8.55,0a1.1,1.1,0,0,1-.511-.093A.811.811,0,0,1,.228,2.67Q1.426,1.444,2.642.233A.8.8,0,0,1,3.777.24a.8.8,0,0,1,.015,1.127c-.295.31-.6.612-.907.918a.4.4,0,0,1-.1.056A.407.407,0,0,1,2.822,2.39Z" transform="translate(6.505 0) rotate(90)"/><path class="a" d="M2.822,1.664h.817l3.889.011c.69,0,1.381,0,2.071,0A.836.836,0,0,0,9.7.011,1.591,1.591,0,0,0,9.517,0Q5.24,0,.967,0A1.1,1.1,0,0,0,.455.093.811.811,0,0,0,.228,1.385q1.2,1.226,2.415,2.437A.808.808,0,0,0,3.792,2.687c-.295-.31-.6-.612-.907-.918a.4.4,0,0,0-.1-.056A.407.407,0,0,0,2.822,1.664Z" transform="translate(4.054 0) rotate(90)"/></g><g transform="translate(16.208 36.709)"><g transform="translate(0 0)"><path class="a" d="M9.568,0c.192.018.384.033.572.054a8.642,8.642,0,0,1,3.97,1.44,9.218,9.218,0,0,1,4.013,5.978,9.494,9.494,0,0,1-.923,6.481.923.923,0,0,0-.047.1.812.812,0,0,1-.919.5.847.847,0,0,1-.666-.85.325.325,0,0,1,.029-.13c.159-.38.322-.76.478-1.143a7.694,7.694,0,0,0,.59-3.456,7.687,7.687,0,0,0-2.4-5.236,7.093,7.093,0,0,0-4.071-1.979A7.2,7.2,0,0,0,4.249,3.579a7.447,7.447,0,0,0-2.5,4.614,7.65,7.65,0,0,0,1.925,6.492,7.156,7.156,0,0,0,4.429,2.377,7.222,7.222,0,0,0,5.453-1.39.387.387,0,0,0,.058-.051.8.8,0,0,1,.9-.17.871.871,0,0,1,.5.8.711.711,0,0,1-.275.572,8.448,8.448,0,0,1-4.48,1.954,8.725,8.725,0,0,1-6.846-2A9.249,9.249,0,0,1,.182,11.337a9.4,9.4,0,0,1-.127-3A9.343,9.343,0,0,1,2.36,3.108,8.962,8.962,0,0,1,7.234.214a8.521,8.521,0,0,1,1.419-.2A1.044,1.044,0,0,0,8.758,0C9.029,0,9.3,0,9.568,0Z" transform="translate(0.01)"/></g></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 21.4"><defs><style>.a{fill:#3b3b3f;}</style></defs><g transform="translate(0)"><path class="a" d="M10.862,0c.218.021.435.037.649.062A9.81,9.81,0,0,1,16.017,1.7a10.464,10.464,0,0,1,4.555,6.786,10.777,10.777,0,0,1-1.047,7.356,1.048,1.048,0,0,0-.053.119.921.921,0,0,1-1.043.567.961.961,0,0,1-.756-.965.369.369,0,0,1,.033-.148c.181-.431.366-.863.542-1.3a8.733,8.733,0,0,0,.67-3.923,8.725,8.725,0,0,0-2.723-5.944A8.051,8.051,0,0,0,11.573,2,8.176,8.176,0,0,0,4.824,4.062,8.452,8.452,0,0,0,1.99,9.3a8.683,8.683,0,0,0,2.185,7.369,8.123,8.123,0,0,0,5.028,2.7,8.2,8.2,0,0,0,6.19-1.577.44.44,0,0,0,.066-.058.909.909,0,0,1,1.027-.193.988.988,0,0,1,.571.908.807.807,0,0,1-.312.649,9.59,9.59,0,0,1-5.085,2.218,9.9,9.9,0,0,1-7.771-2.271,10.5,10.5,0,0,1-3.68-6.174A10.675,10.675,0,0,1,.064,9.46,10.6,10.6,0,0,1,2.68,3.528,10.173,10.173,0,0,1,8.213.242,9.672,9.672,0,0,1,9.823.021,1.185,1.185,0,0,0,9.942,0C10.25,0,10.554,0,10.862,0Z" transform="translate(0.01)"/><path class="a" d="M14.3,17.106h.9l4.28-.012c.76,0,1.52,0,2.28,0a.92.92,0,0,1,.115,1.836,1.751,1.751,0,0,1-.205.008q-4.707,0-9.41,0a1.214,1.214,0,0,1-.563-.1.893.893,0,0,1-.251-1.421q1.318-1.349,2.658-2.682a.889.889,0,0,1,1.265,1.249c-.324.341-.661.674-1,1.01a.436.436,0,0,1-.115.062A.448.448,0,0,1,14.3,17.106Z" transform="translate(-6.589 -8.53)"/><path class="a" d="M19.609,29.83h-.14q-3.654.006-7.3.008a.932.932,0,0,1-.928-.641A.915.915,0,0,1,12.1,28c1.109-.008,2.218,0,3.331,0h6.334a.908.908,0,0,1,.949.858.881.881,0,0,1-.283.694c-.32.32-.641.645-.961.969l-1.631,1.643A.892.892,0,0,1,18.7,32.3a.877.877,0,0,1-.164-1.339c.32-.341.657-.67.99-1a1.143,1.143,0,0,1,.111-.086A.094.094,0,0,1,19.609,29.83Z" transform="translate(-6.592 -16.498)"/></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 21.4"><defs><style>.a{fill:#f4b728;}</style></defs><path class="a" d="M10.862,0c.218.021.435.037.649.062A9.81,9.81,0,0,1,16.017,1.7a10.464,10.464,0,0,1,4.555,6.786,10.777,10.777,0,0,1-1.047,7.356,1.048,1.048,0,0,0-.053.119.921.921,0,0,1-1.043.567.961.961,0,0,1-.756-.965.369.369,0,0,1,.033-.148c.181-.431.366-.863.542-1.3a8.733,8.733,0,0,0,.67-3.923,8.725,8.725,0,0,0-2.723-5.944A8.051,8.051,0,0,0,11.573,2,8.176,8.176,0,0,0,4.824,4.062,8.452,8.452,0,0,0,1.99,9.3a8.683,8.683,0,0,0,2.185,7.369,8.123,8.123,0,0,0,5.028,2.7,8.2,8.2,0,0,0,6.19-1.577.44.44,0,0,0,.066-.058.909.909,0,0,1,1.027-.193.988.988,0,0,1,.571.908.807.807,0,0,1-.312.649,9.59,9.59,0,0,1-5.085,2.218,9.9,9.9,0,0,1-7.771-2.271,10.5,10.5,0,0,1-3.68-6.174A10.675,10.675,0,0,1,.064,9.46,10.6,10.6,0,0,1,2.68,3.528,10.173,10.173,0,0,1,8.213.242,9.672,9.672,0,0,1,9.823.021,1.185,1.185,0,0,0,9.942,0C10.25,0,10.554,0,10.862,0Z" transform="translate(0.01)"/><path class="a" d="M14.3,17.106h.9l4.28-.012c.76,0,1.52,0,2.28,0a.92.92,0,0,1,.115,1.836,1.751,1.751,0,0,1-.205.008q-4.707,0-9.41,0a1.214,1.214,0,0,1-.563-.1.893.893,0,0,1-.251-1.421q1.318-1.349,2.658-2.682a.889.889,0,0,1,1.265,1.249c-.324.341-.661.674-1,1.01a.436.436,0,0,1-.115.062A.448.448,0,0,1,14.3,17.106Z" transform="translate(-6.589 -8.53)"/><path class="a" d="M19.609,29.83h-.14q-3.654.006-7.3.008a.932.932,0,0,1-.928-.641A.915.915,0,0,1,12.1,28c1.109-.008,2.218,0,3.331,0h6.334a.908.908,0,0,1,.949.858.881.881,0,0,1-.283.694c-.32.32-.641.645-.961.969l-1.631,1.643A.892.892,0,0,1,18.7,32.3a.877.877,0,0,1-.164-1.339c.32-.341.657-.67.99-1a1.143,1.143,0,0,1,.111-.086A.094.094,0,0,1,19.609,29.83Z" transform="translate(-6.592 -16.498)"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 24 KiB |
|
@ -4,6 +4,7 @@ import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
/* eslint-disable import/no-extraneous-dependencies */
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
/* eslint-disable max-len */
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
import { darken } from 'polished';
|
import { darken } from 'polished';
|
||||||
|
|
||||||
|
@ -13,8 +14,14 @@ const defaultStyles = `
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
props => props.theme.fontFamily
|
props => props.theme.fontFamily
|
||||||
};
|
};
|
||||||
font-weight: bold;
|
font-weight: ${
|
||||||
font-size: 0.9em;
|
// $FlowFixMe
|
||||||
|
props => props.theme.fontWeight.bold
|
||||||
|
};
|
||||||
|
font-size: ${
|
||||||
|
// $FlowFixMe
|
||||||
|
props => `${props.theme.fontSize.text}em`
|
||||||
|
};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import type { Node } from 'react';
|
||||||
|
|
||||||
|
const Flex = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: ${props => props.alignItems};
|
||||||
|
justify-content: ${props => props.justifyContent};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
alignItems?: string,
|
||||||
|
justifyContent?: string,
|
||||||
|
className?: string,
|
||||||
|
children: Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ColumnComponent = ({ children, ...props }: Props) => (
|
||||||
|
<Flex {...props}>{React.Children.map(children, ch => ch)}</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
ColumnComponent.defaultProps = {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
className: '',
|
||||||
|
};
|
|
@ -0,0 +1,109 @@
|
||||||
|
// @flow
|
||||||
|
import React, { type Node, Component } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
// $FlowFixMe
|
||||||
|
import { darken } from 'polished';
|
||||||
|
import Popover from 'react-popover';
|
||||||
|
import ClickOutside from 'react-click-outside';
|
||||||
|
|
||||||
|
import { TextComponent } from './text';
|
||||||
|
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
const MenuWrapper = styled.div`
|
||||||
|
background-image: ${props => `linear-gradient(to right, ${darken(
|
||||||
|
0.05,
|
||||||
|
props.theme.colors.activeItem,
|
||||||
|
)}, ${props.theme.colors.activeItem})`};
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: -10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MenuItem = styled.button`
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
border-bottom-color: ${props => props.theme.colors.text};
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
padding: 15px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PopoverWithStyle = styled(Popover)`
|
||||||
|
& > .Popover-tip {
|
||||||
|
fill: ${props => props.theme.colors.activeItem};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
renderTrigger: (toggleVisibility: () => void, isOpen: boolean) => Node,
|
||||||
|
options: Array<{ label: string, onClick: () => void }>,
|
||||||
|
label?: string | null,
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
isOpen: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DropdownComponent extends Component<Props, State> {
|
||||||
|
state = {
|
||||||
|
isOpen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
label: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isOpen } = this.state;
|
||||||
|
const { label, options, renderTrigger } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PopoverWithStyle
|
||||||
|
isOpen={isOpen}
|
||||||
|
preferPlace='below'
|
||||||
|
enterExitTransitionDurationMs={0}
|
||||||
|
body={[
|
||||||
|
<ClickOutside
|
||||||
|
onClickOutside={() => this.setState(() => ({ isOpen: false }))}
|
||||||
|
>
|
||||||
|
<MenuWrapper>
|
||||||
|
{label && (
|
||||||
|
<MenuItem disabled>
|
||||||
|
<TextComponent value={label} isBold />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{options.map(({ label: optionLabel, onClick }) => (
|
||||||
|
<MenuItem onClick={onClick}>
|
||||||
|
<TextComponent value={optionLabel} />
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</MenuWrapper>
|
||||||
|
</ClickOutside>,
|
||||||
|
]}
|
||||||
|
tipSize={7}
|
||||||
|
>
|
||||||
|
{renderTrigger(
|
||||||
|
() => this.setState(state => ({ isOpen: !state.isOpen })),
|
||||||
|
isOpen,
|
||||||
|
)}
|
||||||
|
</PopoverWithStyle>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
name: DropDown
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Playground, PropsTable } from 'docz'
|
||||||
|
|
||||||
|
import { DropdownComponent } from './dropdown.js'
|
||||||
|
import { DoczWrapper } from '../theme.js'
|
||||||
|
|
||||||
|
# DropDown
|
||||||
|
|
||||||
|
<PropsTable of={DropdownComponent} />
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
<DoczWrapper>
|
||||||
|
{() => (
|
||||||
|
<div style={{ height: '500px' }}>
|
||||||
|
<DropdownComponent
|
||||||
|
label="Addresses"
|
||||||
|
options={[
|
||||||
|
{ label: 'asbh1yeasbdh23848asdasd', onClick: console.log },
|
||||||
|
{ label: 'urtyruhjr374hbfdjdhuh', onClick: console.log },
|
||||||
|
]}
|
||||||
|
renderTrigger={toggleVisibility => <button onClick={toggleVisibility}>Opa</button>}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DoczWrapper>
|
||||||
|
</Playground>
|
|
@ -0,0 +1,68 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { ZcashLogo } from './zcash-logo';
|
||||||
|
import { TextComponent } from './text';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
height: ${props => props.theme.headerHeight};
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: ${props => props.theme.colors.background};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LogoWrapper = styled.div`
|
||||||
|
height: ${props => props.theme.headerHeight};
|
||||||
|
width: ${props => props.theme.sidebarWidth};
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
${props => props.theme.colors.sidebarLogoGradientBegin},
|
||||||
|
${props => props.theme.colors.sidebarLogoGradientEnd}
|
||||||
|
);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TitleWrapper = styled.div`
|
||||||
|
width: ${props => `calc(100% - ${props.theme.sidebarWidth})`};
|
||||||
|
height: ${props => props.theme.headerHeight};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: ${props => props.theme.layoutPaddingLeft};
|
||||||
|
padding-right: ${props => props.theme.layoutPaddingRight};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled(TextComponent)`
|
||||||
|
font-size: ${props => `${props.theme.fontSize.title}em`};
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Divider = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
background-color: ${props => props.theme.colors.text};
|
||||||
|
height: 1px;
|
||||||
|
opacity: 0.1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HeaderComponent = ({ title }: Props) => (
|
||||||
|
<Wrapper>
|
||||||
|
<LogoWrapper>
|
||||||
|
<ZcashLogo />
|
||||||
|
</LogoWrapper>
|
||||||
|
<TitleWrapper>
|
||||||
|
<Title value={title} />
|
||||||
|
<Divider />
|
||||||
|
</TitleWrapper>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
|
@ -36,8 +36,12 @@ type Props = {
|
||||||
|
|
||||||
export const InputComponent = ({ inputType, onChange, ...props }: Props) => {
|
export const InputComponent = ({ inputType, onChange, ...props }: Props) => {
|
||||||
const inputTypes = {
|
const inputTypes = {
|
||||||
input: () => <Input onChange={evt => onChange(evt.target.value)} {...props} />,
|
input: () => (
|
||||||
textarea: () => <Textarea onChange={evt => onChange(evt.target.value)} {...props} />,
|
<Input onChange={evt => onChange(evt.target.value)} {...props} />
|
||||||
|
),
|
||||||
|
textarea: () => (
|
||||||
|
<Textarea onChange={evt => onChange(evt.target.value)} {...props} />
|
||||||
|
),
|
||||||
dropdown: () => null,
|
dropdown: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,22 @@ import { DoczWrapper } from '../theme.js'
|
||||||
## Text Input
|
## Text Input
|
||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<DoczWrapper>{() => <InputComponent inputType="input" value="Hello World!" onChange={console.log} />}</DoczWrapper>
|
<DoczWrapper>
|
||||||
|
{() => <InputComponent inputType="input" value="Hello World!" onChange={console.log} />}
|
||||||
|
</DoczWrapper>
|
||||||
</Playground>
|
</Playground>
|
||||||
|
|
||||||
## Textarea
|
## Textarea
|
||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<DoczWrapper>
|
<DoczWrapper>
|
||||||
{() => <InputComponent inputType="textarea" value="I'm ZCash Electron Wallet" onChange={console.log} rows={10} />}
|
{() => (
|
||||||
|
<InputComponent
|
||||||
|
inputType="textarea"
|
||||||
|
value="I'm Zcash Electron Wallet"
|
||||||
|
onChange={console.log}
|
||||||
|
rows={10}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DoczWrapper>
|
</DoczWrapper>
|
||||||
</Playground>
|
</Playground>
|
|
@ -1,17 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const Layout = styled.div`
|
const Layout = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
position: absolute;
|
width: ${props => `calc(100% - ${props.theme.sidebarWidth})`};
|
||||||
width: calc(100vw - 200px);
|
height: ${props => `calc(100vh - ${props.theme.headerHeight})`};
|
||||||
left: 200px;
|
background-color: ${props => props.theme.colors.background};
|
||||||
top: 0;
|
padding-left: ${props => props.theme.layoutPaddingLeft};
|
||||||
height: 100vh;
|
padding-right: ${props => props.theme.layoutPaddingRight};
|
||||||
background: ${props => props.theme.colors.secondary};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
|
@ -8,7 +8,9 @@ type Props = {
|
||||||
size?: number,
|
size?: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QRCode = ({ value, size }: Props) => <QR value={value} size={size} />;
|
export const QRCode = ({ value, size }: Props) => (
|
||||||
|
<QR value={value} size={size} />
|
||||||
|
);
|
||||||
|
|
||||||
QRCode.defaultProps = {
|
QRCode.defaultProps = {
|
||||||
size: 128,
|
size: 128,
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import type { Node } from 'react';
|
||||||
|
|
||||||
|
const Flex = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: ${props => props.alignItems};
|
||||||
|
justify-content: ${props => props.justifyContent};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
alignItems?: string,
|
||||||
|
justifyContent?: string,
|
||||||
|
className?: string,
|
||||||
|
children: Node,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RowComponent = ({ children, ...props }: Props) => (
|
||||||
|
<Flex {...props}>{React.Children.map(children, ch => ch)}</Flex>
|
||||||
|
);
|
||||||
|
|
||||||
|
RowComponent.defaultProps = {
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
className: '',
|
||||||
|
};
|
|
@ -2,46 +2,76 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, type Location } from 'react-router-dom';
|
||||||
import { MENU_OPTIONS } from '../constants/sidebar';
|
import { MENU_OPTIONS } from '../constants/sidebar';
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
const Wrapper = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 200px;
|
width: ${props => props.theme.sidebarWidth};
|
||||||
position: absolute;
|
height: ${props => `calc(100vh - ${props.theme.headerHeight})`};
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100vh;
|
|
||||||
font-family: ${props => props.theme.fontFamily}
|
font-family: ${props => props.theme.fontFamily}
|
||||||
background-color: ${props => props.theme.colors.sidebarBg};
|
background-color: ${props => props.theme.colors.sidebarBg};
|
||||||
padding: 20px;
|
padding-top: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
color: ${props => props.theme.colors.sidebarItem};
|
color: ${props => (props.isActive
|
||||||
font-size: 16px;
|
? props.theme.colors.sidebarItemActive
|
||||||
|
: props.theme.colors.sidebarItem)};
|
||||||
|
font-size: ${props => `${props.theme.fontSize.text}em`};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 700;
|
font-weight: ${props => (props.isActive
|
||||||
padding: 5px 0;
|
? props.theme.fontWeight.bold
|
||||||
|
: props.theme.fontWeight.default)};
|
||||||
|
padding: 0 20px;
|
||||||
|
height: 35px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 12.5px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
outline: none;
|
||||||
|
border-right: ${props => (props.isActive
|
||||||
|
? `1px solid ${props.theme.colors.sidebarItemActive}`
|
||||||
|
: 'none')};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${/* eslint-disable-next-line max-len */
|
||||||
|
props => (props.isActive
|
||||||
|
? props.theme.colors.sidebarItemActive
|
||||||
|
: props.theme.colors.sidebarHoveredItemLabel)};
|
||||||
|
background-color: ${props => props.theme.colors.sidebarHoveredItem};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Icon = styled.img`
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type MenuItem = {
|
type MenuItem = {
|
||||||
route: string,
|
route: string,
|
||||||
label: string,
|
label: string,
|
||||||
|
icon: (isActive: boolean) => string,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
options?: MenuItem[],
|
options?: MenuItem[],
|
||||||
|
location: Location,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarComponent = ({ options }: Props) => (
|
export const SidebarComponent = ({ options, location }: Props) => (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
{(options || []).map(item => (
|
{(options || []).map((item) => {
|
||||||
<StyledLink key={item.route} to={item.route}>
|
const isActive = location.pathname === item.route;
|
||||||
{item.label}
|
return (
|
||||||
</StyledLink>
|
<StyledLink isActive={isActive} key={item.route} to={item.route}>
|
||||||
))}
|
<Icon src={item.icon(isActive)} alt={`Sidebar Icon ${item.route}`} />
|
||||||
|
{item.label}
|
||||||
|
</StyledLink>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
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};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string,
|
||||||
|
isBold?: boolean,
|
||||||
|
color?: string,
|
||||||
|
className?: string,
|
||||||
|
size?: string | number,
|
||||||
|
align?: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextComponent = ({
|
||||||
|
value,
|
||||||
|
isBold,
|
||||||
|
color,
|
||||||
|
className,
|
||||||
|
size,
|
||||||
|
align,
|
||||||
|
}: Props) => (
|
||||||
|
<Text
|
||||||
|
className={className}
|
||||||
|
isBold={isBold}
|
||||||
|
color={color}
|
||||||
|
size={`${String(size)}em`}
|
||||||
|
align={align}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
|
||||||
|
TextComponent.defaultProps = {
|
||||||
|
className: '',
|
||||||
|
isBold: false,
|
||||||
|
color: theme.colors.text,
|
||||||
|
size: theme.fontSize.text,
|
||||||
|
align: 'left',
|
||||||
|
};
|
|
@ -1,68 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import type { TodoType } from '../types/todo';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
updateTodo: Function,
|
|
||||||
todo: TodoType,
|
|
||||||
cancelUpdateTodo: Function,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
value: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class TodoEditInput extends Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
value: props.todo.text || '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit = (event: SyntheticInputEvent<HTMLInputElement>, id: string) => {
|
|
||||||
const { value } = this.state;
|
|
||||||
const { updateTodo } = this.props;
|
|
||||||
const trimValue = value.trim();
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (trimValue !== '') {
|
|
||||||
updateTodo(id, trimValue);
|
|
||||||
this.setState({ value: '' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCancel = (id: string) => {
|
|
||||||
const { cancelUpdateTodo } = this.props;
|
|
||||||
cancelUpdateTodo(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInputChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
|
||||||
const {
|
|
||||||
target: { value },
|
|
||||||
} = event;
|
|
||||||
this.setState({ value });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { value } = this.state;
|
|
||||||
const { todo } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='todo-item__view todo-item__view--edit'>
|
|
||||||
<form className='todo-item__input' onSubmit={e => this.handleSubmit(e, todo.id)}>
|
|
||||||
<input value={value} onChange={this.handleInputChange} className='todo-item__input-field' autoFocus />
|
|
||||||
<button type='submit' className='todo-item__input-button'>
|
|
||||||
Update
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<button type='button' className='todo-item__input-cancel' onClick={() => this.handleCancel(todo.id)}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
addTodo: Function,
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
value: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class TodoInput extends Component<Props, State> {
|
|
||||||
state = {
|
|
||||||
value: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubmit = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
|
||||||
const { value } = this.state;
|
|
||||||
const { addTodo } = this.props;
|
|
||||||
const trimValue = value.trim();
|
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (trimValue !== '') {
|
|
||||||
addTodo(trimValue);
|
|
||||||
this.setState({ value: '' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInputChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
|
||||||
const {
|
|
||||||
target: { value },
|
|
||||||
} = event;
|
|
||||||
this.setState({ value });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { value } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form className='todo__input' onSubmit={this.handleSubmit}>
|
|
||||||
<input value={value} onChange={this.handleInputChange} className='todo__input-field' />
|
|
||||||
<button type='submit' className='todo__input-button'>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { PureComponent } from 'react';
|
|
||||||
import type { TodoType } from '../types/todo';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
todo: TodoType,
|
|
||||||
deleteTodo: Function,
|
|
||||||
toggleEdit: Function,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class TodoListItem extends PureComponent<Props> {
|
|
||||||
handleDelete = (id: string) => {
|
|
||||||
if (!id) return;
|
|
||||||
|
|
||||||
const { deleteTodo } = this.props;
|
|
||||||
deleteTodo(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEditToggle = (id: string) => {
|
|
||||||
if (!id) return;
|
|
||||||
|
|
||||||
const { toggleEdit } = this.props;
|
|
||||||
toggleEdit(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { todo } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='todo-item__view todo-item__view--view'>
|
|
||||||
<span className='todo-item__text'>{todo.text}</span>
|
|
||||||
<div className='todo-item__buttons'>
|
|
||||||
<button type='button' onClick={() => this.handleEditToggle(todo.id)} className='todo-item__button'>
|
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 528.899 528.899'>
|
|
||||||
<path
|
|
||||||
className='todo-item__svg'
|
|
||||||
d='M328.883 89.125l107.59 107.589-272.34 272.34L56.604 361.465l272.279-272.34zm189.23-25.948l-47.981-47.981c-18.543-18.543-48.653-18.543-67.259 0l-45.961 45.961 107.59 107.59 53.611-53.611c14.382-14.383 14.382-37.577 0-51.959zM.3 512.69c-1.958 8.812 5.998 16.708 14.811 14.565l119.891-29.069L27.473 390.597.3 512.69z'
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button type='button' onClick={() => this.handleDelete(todo.id)} className='todo-item__button'>
|
|
||||||
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 512 512'>
|
|
||||||
<path
|
|
||||||
className='todo-item__svg'
|
|
||||||
fill='#1D1D1B'
|
|
||||||
d='M459.232 60.687h-71.955c-1.121-17.642-15.631-31.657-33.553-31.657H161.669c-17.921 0-32.441 14.015-33.553 31.657H64.579c-18.647 0-33.767 15.12-33.767 33.768v8.442c0 18.648 15.12 33.768 33.767 33.768h21.04v342.113c0 13.784 11.179 24.963 24.963 24.963h308.996c13.784 0 24.964-11.179 24.964-24.963V136.665h14.691c18.663 0 33.768-15.12 33.768-33.768v-8.442c-.001-18.648-15.105-33.768-33.769-33.768zM196.674 443.725c0 12.58-10.197 22.803-22.802 22.803-12.598 0-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.205-22.802 22.803-22.802 12.605 0 22.802 10.206 22.802 22.802v284.9zm91.213 0c0 12.58-10.205 22.803-22.803 22.803s-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.205-22.802 22.803-22.802s22.803 10.206 22.803 22.802v284.9zm91.212 0c0 12.58-10.205 22.803-22.803 22.803-12.613 0-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.189-22.802 22.803-22.802 12.598 0 22.803 10.206 22.803 22.802v284.9z'
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React, { PureComponent } from 'react';
|
|
||||||
import TodoEditInput from './todo-edit-input';
|
|
||||||
import TodoListItem from './todo-list-item';
|
|
||||||
import type { TodoType } from '../types/todo';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
todos: Array<TodoType>,
|
|
||||||
deleteTodo: Function,
|
|
||||||
toggleEdit: Function,
|
|
||||||
updateTodo: Function,
|
|
||||||
cancelUpdateTodo: Function,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class TodoList extends PureComponent<Props> {
|
|
||||||
renderTodoView = (todo: TodoType) => {
|
|
||||||
const { deleteTodo, toggleEdit } = this.props;
|
|
||||||
|
|
||||||
return <TodoListItem todo={todo} deleteTodo={deleteTodo} toggleEdit={toggleEdit} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderEditView = (todo: TodoType) => {
|
|
||||||
const { updateTodo, cancelUpdateTodo } = this.props;
|
|
||||||
|
|
||||||
return <TodoEditInput todo={todo} updateTodo={updateTodo} cancelUpdateTodo={cancelUpdateTodo} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
renderList = () => {
|
|
||||||
const { todos } = this.props;
|
|
||||||
const sortTodosByTime = todos.sort((a, b) => b.createdAt - a.createdAt);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className='todo__list'>
|
|
||||||
{sortTodosByTime.map(todo => (
|
|
||||||
<li key={todo.id} className='todo__list-item todo-item'>
|
|
||||||
{todo.editing ? this.renderEditView(todo) : this.renderTodoView(todo)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderEmptyState = () => <p className='todo__list todo__list--empty'>No todos right now</p>;
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { todos } = this.props;
|
|
||||||
const hasTodos = todos.length;
|
|
||||||
|
|
||||||
return hasTodos ? this.renderList() : this.renderEmptyState();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { TransactionItemComponent, type Transaction } from './transaction-item';
|
||||||
|
import { TextComponent } from './text';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
margin-top: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransactionsWrapper = styled.div`
|
||||||
|
border-radius: 7.5px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Day = styled(TextComponent)`
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: ${props => props.theme.colors.transactionsDate};
|
||||||
|
font-size: ${props => `${props.theme.fontSize.text * 0.9}em`};
|
||||||
|
font-weight: ${props => props.theme.fontWeight.bold};
|
||||||
|
margin-bottom: 5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Divider = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background-color: ${props => props.theme.colors.inactiveItem};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
transactionsDate: string,
|
||||||
|
transactions: Transaction[],
|
||||||
|
zecPrice: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TransactionDailyComponent = ({
|
||||||
|
transactionsDate,
|
||||||
|
transactions,
|
||||||
|
zecPrice,
|
||||||
|
}: Props) => (
|
||||||
|
<Wrapper>
|
||||||
|
<Day value={transactionsDate} />
|
||||||
|
<TransactionsWrapper>
|
||||||
|
{transactions.map(({
|
||||||
|
date, type, address, amount,
|
||||||
|
}, idx) => (
|
||||||
|
<div>
|
||||||
|
<TransactionItemComponent
|
||||||
|
type={type}
|
||||||
|
date={date}
|
||||||
|
address={address || ''}
|
||||||
|
amount={amount}
|
||||||
|
zecPrice={zecPrice}
|
||||||
|
/>
|
||||||
|
{idx < transactions.length - 1 && <Divider />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</TransactionsWrapper>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
|
@ -0,0 +1,38 @@
|
||||||
|
---
|
||||||
|
name: Transaction Daily
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Playground, PropsTable } from 'docz'
|
||||||
|
|
||||||
|
import { TransactionDailyComponent } from './transaction-daily.js'
|
||||||
|
import { DoczWrapper } from '../theme.js'
|
||||||
|
|
||||||
|
# Transaction Item
|
||||||
|
|
||||||
|
<PropsTable of={TransactionDailyComponent} />
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
<DoczWrapper>
|
||||||
|
{() => (
|
||||||
|
<TransactionDailyComponent
|
||||||
|
transactionsDate={new Date().toISOString()}
|
||||||
|
transactions={[
|
||||||
|
{
|
||||||
|
type: 'received',
|
||||||
|
address: '123456789123456789123456789123456789',
|
||||||
|
amount: 1.7891,
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'sent',
|
||||||
|
address: '123456789123456789123456789123456789',
|
||||||
|
amount: 0.8458,
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DoczWrapper>
|
||||||
|
</Playground>
|
|
@ -0,0 +1,103 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import dateFns from 'date-fns';
|
||||||
|
|
||||||
|
import SentIcon from '../assets/images/transaction_sent_icon.svg';
|
||||||
|
import ReceivedIcon from '../assets/images/transaction_received_icon.svg';
|
||||||
|
|
||||||
|
import { RowComponent } from './row';
|
||||||
|
import { ColumnComponent } from './column';
|
||||||
|
import { TextComponent } from './text';
|
||||||
|
|
||||||
|
import theme from '../theme';
|
||||||
|
|
||||||
|
import formatNumber from '../utils/formatNumber';
|
||||||
|
import truncateAddress from '../utils/truncateAddress';
|
||||||
|
|
||||||
|
const Wrapper = styled(RowComponent)`
|
||||||
|
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||||
|
padding: 15px 17px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Icon = styled.img`
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransactionTypeLabel = styled(TextComponent)`
|
||||||
|
color: ${props => (props.isReceived
|
||||||
|
? props.theme.colors.transactionReceived
|
||||||
|
: props.theme.colors.transactionSent)};
|
||||||
|
text-transform: capitalize;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransactionTime = styled(TextComponent)`
|
||||||
|
color: ${props => props.theme.colors.inactiveItem};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransactionColumn = styled(ColumnComponent)`
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 80px;
|
||||||
|
min-width: 60px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type Transaction = {
|
||||||
|
type: 'send' | 'receive',
|
||||||
|
date: string,
|
||||||
|
address: string,
|
||||||
|
amount: number,
|
||||||
|
zecPrice: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TransactionItemComponent = ({
|
||||||
|
type,
|
||||||
|
date,
|
||||||
|
address,
|
||||||
|
amount,
|
||||||
|
zecPrice,
|
||||||
|
}: Transaction) => {
|
||||||
|
const isReceived = type === 'receive';
|
||||||
|
const transactionTime = dateFns.format(new Date(date), 'HH:mm A');
|
||||||
|
const transactionValueInZec = formatNumber({
|
||||||
|
value: amount,
|
||||||
|
append: `${isReceived ? '+' : '-'}ZEC `,
|
||||||
|
});
|
||||||
|
const transactionValueInUsd = formatNumber({
|
||||||
|
value: amount * zecPrice,
|
||||||
|
append: `${isReceived ? '+' : '-'}USD $`,
|
||||||
|
});
|
||||||
|
const transactionAddress = truncateAddress(address);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper alignItems='center' justifyContent='space-between'>
|
||||||
|
<RowComponent alignItems='center'>
|
||||||
|
<RowComponent alignItems='center'>
|
||||||
|
<Icon
|
||||||
|
src={isReceived ? ReceivedIcon : SentIcon}
|
||||||
|
alt='Transaction Type Icon'
|
||||||
|
/>
|
||||||
|
<TransactionColumn>
|
||||||
|
<TransactionTypeLabel isReceived={isReceived} value={type} />
|
||||||
|
<TransactionTime value={transactionTime} />
|
||||||
|
</TransactionColumn>
|
||||||
|
</RowComponent>
|
||||||
|
<TextComponent value={transactionAddress} align='left' />
|
||||||
|
</RowComponent>
|
||||||
|
<ColumnComponent alignItems='flex-end'>
|
||||||
|
<TextComponent
|
||||||
|
value={transactionValueInZec}
|
||||||
|
color={
|
||||||
|
isReceived
|
||||||
|
? theme.colors.transactionReceived
|
||||||
|
: theme.colors.transactionSent
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TextComponent
|
||||||
|
value={transactionValueInUsd}
|
||||||
|
color={theme.colors.inactiveItem}
|
||||||
|
/>
|
||||||
|
</ColumnComponent>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
name: Transaction Item
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Playground, PropsTable } from 'docz'
|
||||||
|
|
||||||
|
import { TransactionItemComponent } from './transaction-item.js'
|
||||||
|
import { DoczWrapper } from '../theme.js'
|
||||||
|
|
||||||
|
# Transaction Item
|
||||||
|
|
||||||
|
<PropsTable of={TransactionItemComponent} />
|
||||||
|
|
||||||
|
## Sent
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
<DoczWrapper>
|
||||||
|
{() => (
|
||||||
|
<TransactionItemComponent
|
||||||
|
type="sent"
|
||||||
|
address="123456789123456789123456789123456789"
|
||||||
|
amount={0.8652}
|
||||||
|
date={new Date().toISOString()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DoczWrapper>
|
||||||
|
</Playground>
|
||||||
|
|
||||||
|
## Received
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
<DoczWrapper>
|
||||||
|
{() => (
|
||||||
|
<TransactionItemComponent
|
||||||
|
type="received"
|
||||||
|
address="123456789123456789123456789123456789"
|
||||||
|
amount={1.7891}
|
||||||
|
date={new Date().toISOString()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</DoczWrapper>
|
||||||
|
</Playground>
|
|
@ -0,0 +1,146 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { TextComponent } from './text';
|
||||||
|
import { RowComponent } from './row';
|
||||||
|
import { DropdownComponent } from './dropdown';
|
||||||
|
import MenuIcon from '../assets/images/menu_icon.svg';
|
||||||
|
|
||||||
|
import formatNumber from '../utils/formatNumber';
|
||||||
|
|
||||||
|
import theme from '../theme';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 37px 45px;
|
||||||
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AllAddresses = styled(TextComponent)`
|
||||||
|
margin-bottom: 2.5px;
|
||||||
|
font-size: ${props => `${props.theme.fontSize.text}em`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ValueBox = styled.div`
|
||||||
|
margin-bottom: 15px;
|
||||||
|
margin-right: 25px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Label = styled(TextComponent)`
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-left: -7.5px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const USDValue = styled(TextComponent)`
|
||||||
|
opacity: 0.5;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ShieldedValue = styled(Label)`
|
||||||
|
color: ${props => props.theme.colors.activeItem};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SeeMoreButton = styled.button`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
outline: none;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 100%;
|
||||||
|
border-width: 1px;
|
||||||
|
border-color: ${props => (props.isOpen
|
||||||
|
? props.theme.colors.activeItem
|
||||||
|
: props.theme.colors.inactiveItem)};
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: ${props => props.theme.colors.activeItem};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SeeMoreIcon = styled.img`
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
total: number,
|
||||||
|
shielded: number,
|
||||||
|
transparent: number,
|
||||||
|
zecPrice: number,
|
||||||
|
addresses: string[],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WalletSummaryComponent = ({
|
||||||
|
total,
|
||||||
|
shielded,
|
||||||
|
transparent,
|
||||||
|
zecPrice,
|
||||||
|
addresses,
|
||||||
|
}: Props) => (
|
||||||
|
<Wrapper>
|
||||||
|
<DropdownComponent
|
||||||
|
label='All Addresses'
|
||||||
|
renderTrigger={(toggleVisibility, isOpen) => (
|
||||||
|
<SeeMoreButton onClick={toggleVisibility} isOpen={isOpen}>
|
||||||
|
<SeeMoreIcon src={MenuIcon} alt='Menu Icon' />
|
||||||
|
</SeeMoreButton>
|
||||||
|
)}
|
||||||
|
options={addresses.map(addr => ({ label: addr, onClick: x => x }))}
|
||||||
|
/>
|
||||||
|
<AllAddresses value='ALL ADDRESSES' isBold />
|
||||||
|
<ValueBox>
|
||||||
|
<TextComponent
|
||||||
|
size={theme.fontSize.zecValueBase * 2.5}
|
||||||
|
value={`ZEC ${formatNumber({ value: total })}`}
|
||||||
|
isBold
|
||||||
|
/>
|
||||||
|
<USDValue
|
||||||
|
value={`USD $${formatNumber({ value: total * zecPrice })}`}
|
||||||
|
size={theme.fontSize.zecValueBase * 2}
|
||||||
|
/>
|
||||||
|
</ValueBox>
|
||||||
|
<RowComponent>
|
||||||
|
<ValueBox>
|
||||||
|
<ShieldedValue
|
||||||
|
value='● SHIELDED'
|
||||||
|
isBold
|
||||||
|
size={theme.fontSize.text * 0.8}
|
||||||
|
/>
|
||||||
|
<TextComponent
|
||||||
|
value={`ZEC ${formatNumber({ value: shielded })}`}
|
||||||
|
isBold
|
||||||
|
size={theme.fontSize.zecValueBase}
|
||||||
|
/>
|
||||||
|
<USDValue
|
||||||
|
value={`USD $${formatNumber({ value: shielded * zecPrice })}`}
|
||||||
|
/>
|
||||||
|
</ValueBox>
|
||||||
|
<ValueBox>
|
||||||
|
<Label
|
||||||
|
value='● TRANSPARENT'
|
||||||
|
isBold
|
||||||
|
size={theme.fontSize.text * 0.8}
|
||||||
|
/>
|
||||||
|
<TextComponent
|
||||||
|
value={`ZEC ${formatNumber({ value: transparent })}`}
|
||||||
|
isBold
|
||||||
|
size={theme.fontSize.zecValueBase}
|
||||||
|
/>
|
||||||
|
<USDValue
|
||||||
|
value={`USD $${formatNumber({ value: transparent * zecPrice })}`}
|
||||||
|
/>
|
||||||
|
</ValueBox>
|
||||||
|
</RowComponent>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
name: Wallet Summary
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Playground, PropsTable } from 'docz'
|
||||||
|
|
||||||
|
import { WalletSummaryComponent } from './wallet-summary.js'
|
||||||
|
import { DoczWrapper } from '../theme.js'
|
||||||
|
|
||||||
|
# Wallet Summary
|
||||||
|
|
||||||
|
<PropsTable of={WalletSummaryComponent} />
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
<DoczWrapper>
|
||||||
|
{() => (
|
||||||
|
<div style={{ width: '700px' }}>
|
||||||
|
<WalletSummaryComponent
|
||||||
|
total={5000}
|
||||||
|
shielded={2500}
|
||||||
|
transparent={2500}
|
||||||
|
dollarValue={56}
|
||||||
|
addresses={['12345678asdaas9', '98asdasd765asd4sad321']}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DoczWrapper>
|
||||||
|
</Playground>
|
|
@ -0,0 +1,56 @@
|
||||||
|
// @flow
|
||||||
|
import React, { type ComponentType, Component } from 'react';
|
||||||
|
|
||||||
|
import rpc from '../../services/api';
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
isRunning: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {};
|
||||||
|
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
export const withDaemonStatusCheck = <PassedProps: {}>(
|
||||||
|
WrappedComponent: ComponentType<PassedProps>,
|
||||||
|
): ComponentType<$Diff<PassedProps, Props>> => class extends Component<PassedProps, State> {
|
||||||
|
timer: ?IntervalID = null;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isRunning: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.runTest();
|
||||||
|
|
||||||
|
this.timer = setInterval(this.runTest, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runTest = () => {
|
||||||
|
rpc.getinfo().then((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.setState(() => ({ isRunning: true }));
|
||||||
|
if (this.timer) {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
this.timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isRunning } = this.state;
|
||||||
|
|
||||||
|
if (isRunning) {
|
||||||
|
return <WrappedComponent {...this.props} {...this.state} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Daemon is starting...';
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ZcashLogo = () => (
|
||||||
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='-75 -10 175 175'>
|
||||||
|
<defs>
|
||||||
|
<style>{'.a{ fill:#040508; }'}</style>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
className='a'
|
||||||
|
d='M541.425,662.318v4.555h-7.678v5.678H545.5l-11.751,16v4.261h7.678v4.665h4.563v-4.665h7.577v-5.666H541.788l11.777-16v-4.273h-7.577v-4.555Z'
|
||||||
|
transform='translate(-533.747 -662.318)'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
|
@ -1,7 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
export const DASHBOARD_ROUTE = '/';
|
export const DASHBOARD_ROUTE = '/';
|
||||||
|
export const CONSOLE_ROUTE = '/console';
|
||||||
export const SEND_ROUTE = '/send';
|
export const SEND_ROUTE = '/send';
|
||||||
export const RECEIVE_ROUTE = '/receive';
|
export const RECEIVE_ROUTE = '/receive';
|
||||||
|
export const TRANSACTIONS_ROUTE = '/transactions';
|
||||||
export const SETTINGS_ROUTE = '/settings';
|
export const SETTINGS_ROUTE = '/settings';
|
||||||
export const CONSOLE_ROUTE = '/console';
|
|
||||||
|
|
|
@ -1,28 +1,57 @@
|
||||||
// @flow
|
// @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';
|
||||||
|
import ConsoleIconActive from '../assets/images/console_icon_active.svg';
|
||||||
|
import SendIcon from '../assets/images/send_icon.svg';
|
||||||
|
import SendIconActive from '../assets/images/send_icon_active.svg';
|
||||||
|
import ReceiveIcon from '../assets/images/receive_icon.svg';
|
||||||
|
import ReceiveIconActive from '../assets/images/receive_icon_active.svg';
|
||||||
|
import TransactionsIcon from '../assets/images/transactions_icon.svg';
|
||||||
|
import TransactionsIconActive from '../assets/images/transactions_icon_active.svg';
|
||||||
|
import SettingsIcon from '../assets/images/settings_icon.svg';
|
||||||
|
import SettingsIconActive from '../assets/images/settings_icon_active.svg';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DASHBOARD_ROUTE, SEND_ROUTE, RECEIVE_ROUTE, SETTINGS_ROUTE, CONSOLE_ROUTE,
|
DASHBOARD_ROUTE,
|
||||||
|
SEND_ROUTE,
|
||||||
|
RECEIVE_ROUTE,
|
||||||
|
SETTINGS_ROUTE,
|
||||||
|
CONSOLE_ROUTE,
|
||||||
|
TRANSACTIONS_ROUTE,
|
||||||
} from './routes';
|
} from './routes';
|
||||||
|
|
||||||
export const MENU_OPTIONS = [
|
export const MENU_OPTIONS = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
route: DASHBOARD_ROUTE,
|
route: DASHBOARD_ROUTE,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
icon: (isActive: boolean) => isActive ? DashboardIconActive : DashboardIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Send',
|
label: 'Send',
|
||||||
route: SEND_ROUTE,
|
route: SEND_ROUTE,
|
||||||
|
icon: (isActive: boolean) => (isActive ? SendIconActive : SendIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Receive',
|
label: 'Receive',
|
||||||
route: RECEIVE_ROUTE,
|
route: RECEIVE_ROUTE,
|
||||||
|
icon: (isActive: boolean) => (isActive ? ReceiveIconActive : ReceiveIcon),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Console',
|
label: 'Transactions',
|
||||||
route: CONSOLE_ROUTE,
|
route: TRANSACTIONS_ROUTE,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
icon: (isActive: boolean) => isActive ? TransactionsIconActive : TransactionsIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
route: SETTINGS_ROUTE,
|
route: SETTINGS_ROUTE,
|
||||||
|
icon: (isActive: boolean) => (isActive ? SettingsIconActive : SettingsIcon),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Console',
|
||||||
|
route: CONSOLE_ROUTE,
|
||||||
|
icon: (isActive: boolean) => (isActive ? ConsoleIconActive : ConsoleIcon),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import eres from 'eres';
|
||||||
|
import flow from 'lodash.flow';
|
||||||
|
import groupBy from 'lodash.groupby';
|
||||||
|
import dateFns from 'date-fns';
|
||||||
|
import { DashboardView } from '../views/dashboard';
|
||||||
|
import rpc from '../../services/api';
|
||||||
|
import store from '../../config/electron-store';
|
||||||
|
import {
|
||||||
|
loadWalletSummary,
|
||||||
|
loadWalletSummarySuccess,
|
||||||
|
loadWalletSummaryError,
|
||||||
|
} from '../redux/modules/wallet';
|
||||||
|
|
||||||
|
import type { AppState } from '../types/app-state';
|
||||||
|
import type { Dispatch } from '../types/redux';
|
||||||
|
|
||||||
|
const mapStateToProps = ({ walletSummary }: AppState) => ({
|
||||||
|
total: walletSummary.total,
|
||||||
|
shielded: walletSummary.shielded,
|
||||||
|
transparent: walletSummary.transparent,
|
||||||
|
error: walletSummary.error,
|
||||||
|
isLoading: walletSummary.isLoading,
|
||||||
|
zecPrice: walletSummary.zecPrice,
|
||||||
|
addresses: walletSummary.addresses,
|
||||||
|
transactions: walletSummary.transactions,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||||
|
getSummary: async () => {
|
||||||
|
dispatch(loadWalletSummary());
|
||||||
|
|
||||||
|
const [err, walletSummary] = await eres(rpc.z_gettotalbalance());
|
||||||
|
|
||||||
|
if (err) return dispatch(loadWalletSummaryError({ error: err.message }));
|
||||||
|
|
||||||
|
const [addressesErr, addresses] = await eres(rpc.z_listaddresses());
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (addressesErr) return dispatch(loadWalletSummaryError({ error: addressesErr.message }));
|
||||||
|
|
||||||
|
const [transactionsErr, transactions = []] = await eres(
|
||||||
|
rpc.listtransactions(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (transactionsErr) {
|
||||||
|
return dispatch(
|
||||||
|
loadWalletSummaryError({ error: transactionsErr.message }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
loadWalletSummarySuccess({
|
||||||
|
transparent: walletSummary.transparent,
|
||||||
|
total: walletSummary.total,
|
||||||
|
shielded: walletSummary.private,
|
||||||
|
addresses,
|
||||||
|
transactions: flow([
|
||||||
|
arr => arr.map(transaction => ({
|
||||||
|
type: transaction.category,
|
||||||
|
date: new Date(transaction.time * 1000).toISOString(),
|
||||||
|
address: transaction.address,
|
||||||
|
amount: Math.abs(transaction.amount),
|
||||||
|
})),
|
||||||
|
arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')),
|
||||||
|
])(transactions),
|
||||||
|
zecPrice: store.get('ZEC_DOLLAR_PRICE'),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DashboardContainer = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(DashboardView);
|
|
@ -1,17 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { SidebarComponent } from '../components/sidebar';
|
import { SidebarComponent } from '../components/sidebar';
|
||||||
|
|
||||||
const mapStateToProps = (state: Object) => ({
|
export const SidebarContainer = SidebarComponent;
|
||||||
todos: state.todos,
|
|
||||||
});
|
|
||||||
|
|
||||||
// const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
// addTodo: text => dispatch(addTodo(text)),
|
|
||||||
// });
|
|
||||||
|
|
||||||
export const SidebarContainer = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
// mapDispatchToProps,
|
|
||||||
)(SidebarComponent);
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import TodoView from '../views/todo';
|
|
||||||
import {
|
|
||||||
addTodo, deleteTodo, toggleEdit, updateTodo, cancelUpdateTodo,
|
|
||||||
} from '../redux/modules/todo';
|
|
||||||
|
|
||||||
import type { AppState } from '../types/app-state';
|
|
||||||
import type { Dispatch } from '../types/redux';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => ({
|
|
||||||
todos: state.todos,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
|
||||||
addTodo: text => dispatch(addTodo(text)),
|
|
||||||
deleteTodo: id => dispatch(deleteTodo(id)),
|
|
||||||
toggleEdit: id => dispatch(toggleEdit(id)),
|
|
||||||
updateTodo: (id, text) => dispatch(updateTodo(id, text)),
|
|
||||||
cancelUpdateTodo: id => dispatch(cancelUpdateTodo(id)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(TodoView);
|
|
|
@ -9,7 +9,9 @@ import { createRootReducer } from './modules/reducer';
|
||||||
|
|
||||||
export const history = createBrowserHistory();
|
export const history = createBrowserHistory();
|
||||||
|
|
||||||
const shouldEnableDevTools = (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'staging') && window.devToolsExtension;
|
const shouldEnableDevTools = (process.env.NODE_ENV !== 'production'
|
||||||
|
|| process.env.NODE_ENV !== 'staging')
|
||||||
|
&& window.devToolsExtension;
|
||||||
|
|
||||||
export const configureStore = (initialState: Object) => {
|
export const configureStore = (initialState: Object) => {
|
||||||
const middleware = applyMiddleware(thunk, routerMiddleware(history));
|
const middleware = applyMiddleware(thunk, routerMiddleware(history));
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { combineReducers } from 'redux';
|
||||||
import { connectRouter } from 'connected-react-router';
|
import { connectRouter } from 'connected-react-router';
|
||||||
import type { RouterHistory } from 'react-router-dom';
|
import type { RouterHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import todoReducer from './todo';
|
import wallet from './wallet';
|
||||||
|
|
||||||
export const createRootReducer = (history: RouterHistory) => combineReducers({
|
export const createRootReducer = (history: RouterHistory) => combineReducers({
|
||||||
todos: todoReducer,
|
walletSummary: wallet,
|
||||||
router: connectRouter(history),
|
router: connectRouter(history),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import UUID from 'uuid/v4';
|
|
||||||
|
|
||||||
import { getTimestamp } from '../../utils/timestamp';
|
|
||||||
|
|
||||||
import type { Action } from '../../types/redux';
|
|
||||||
import type { TodoType } from '../../types/todo';
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
export const ADD_TODO = 'ADD_TODO';
|
|
||||||
export const DELETE_TODO = 'DELETE_TODO';
|
|
||||||
export const UPDATE_TODO = 'UPDATE_TODO';
|
|
||||||
export const TOGGLE_EDIT_TODO = 'TOGGLE_EDIT_TODO';
|
|
||||||
export const CANCEL_UPDATE_TODO = 'CANCEL_UPDATE_TODO';
|
|
||||||
|
|
||||||
// Actions Creators
|
|
||||||
export const addTodo = (text: string) => ({
|
|
||||||
type: ADD_TODO,
|
|
||||||
payload: {
|
|
||||||
text,
|
|
||||||
id: UUID(),
|
|
||||||
editing: false,
|
|
||||||
createdAt: getTimestamp(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const cancelUpdateTodo = (id: string) => ({
|
|
||||||
type: CANCEL_UPDATE_TODO,
|
|
||||||
payload: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const deleteTodo = (id: string) => ({
|
|
||||||
type: DELETE_TODO,
|
|
||||||
payload: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const toggleEdit = (id: string) => ({
|
|
||||||
type: TOGGLE_EDIT_TODO,
|
|
||||||
payload: { id },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateTodo = (id: string, text: string) => ({
|
|
||||||
type: UPDATE_TODO,
|
|
||||||
payload: {
|
|
||||||
text,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initial State
|
|
||||||
const initialState = [];
|
|
||||||
|
|
||||||
// Reducers
|
|
||||||
export default (state: Array<TodoType> = initialState, action: Action): Array<TodoType> => {
|
|
||||||
switch (action.type) {
|
|
||||||
case ADD_TODO:
|
|
||||||
return [...state, action.payload];
|
|
||||||
case DELETE_TODO:
|
|
||||||
return state.filter((todo: TodoType) => todo.id !== action.payload.id);
|
|
||||||
case TOGGLE_EDIT_TODO: {
|
|
||||||
const { id } = action.payload;
|
|
||||||
return state.map(todo => (todo.id === id ? { ...todo, editing: true } : todo));
|
|
||||||
}
|
|
||||||
case UPDATE_TODO: {
|
|
||||||
const { id, text } = action.payload;
|
|
||||||
return state.map(todo => (todo.id === id ? { ...todo, editing: false, text } : todo));
|
|
||||||
}
|
|
||||||
case CANCEL_UPDATE_TODO: {
|
|
||||||
const { id } = action.payload;
|
|
||||||
return state.map(todo => (todo.id === id ? { ...todo, editing: false } : todo));
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// SideEffects
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
// @flow
|
||||||
|
import type { Action } from '../../types/redux';
|
||||||
|
import type { Transaction } from '../../components/transaction-item';
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
export const LOAD_WALLET_SUMMARY = 'LOAD_WALLET_SUMMARY';
|
||||||
|
export const LOAD_WALLET_SUMMARY_SUCCESS = 'LOAD_WALLET_SUMMARY_SUCCESS';
|
||||||
|
export const LOAD_WALLET_SUMMARY_ERROR = 'LOAD_WALLET_SUMMARY_ERROR';
|
||||||
|
|
||||||
|
// Actions Creators
|
||||||
|
export const loadWalletSummary = () => ({
|
||||||
|
type: LOAD_WALLET_SUMMARY,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const loadWalletSummarySuccess = ({
|
||||||
|
total,
|
||||||
|
shielded,
|
||||||
|
transparent,
|
||||||
|
addresses,
|
||||||
|
transactions,
|
||||||
|
zecPrice,
|
||||||
|
}: {
|
||||||
|
total: number,
|
||||||
|
shielded: number,
|
||||||
|
transparent: number,
|
||||||
|
addresses: string[],
|
||||||
|
transactions: { [day: string]: Transaction[] },
|
||||||
|
zecPrice: number,
|
||||||
|
}) => ({
|
||||||
|
type: LOAD_WALLET_SUMMARY_SUCCESS,
|
||||||
|
payload: {
|
||||||
|
total,
|
||||||
|
shielded,
|
||||||
|
transparent,
|
||||||
|
addresses,
|
||||||
|
transactions,
|
||||||
|
zecPrice,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const loadWalletSummaryError = ({ error }: { error: string }) => ({
|
||||||
|
type: LOAD_WALLET_SUMMARY_ERROR,
|
||||||
|
payload: { error },
|
||||||
|
});
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
total: number,
|
||||||
|
shielded: number,
|
||||||
|
transparent: number,
|
||||||
|
error: string | null,
|
||||||
|
isLoading: boolean,
|
||||||
|
zecPrice: number,
|
||||||
|
addresses: [],
|
||||||
|
transactions: { [day: string]: Transaction[] },
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
total: 0,
|
||||||
|
shielded: 0,
|
||||||
|
transparent: 0,
|
||||||
|
error: null,
|
||||||
|
isLoading: false,
|
||||||
|
zecPrice: 0,
|
||||||
|
addresses: [],
|
||||||
|
transactions: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (state: State = initialState, action: Action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case LOAD_WALLET_SUMMARY:
|
||||||
|
return { ...state, isLoading: true };
|
||||||
|
case LOAD_WALLET_SUMMARY_SUCCESS:
|
||||||
|
// TODO: Get zec in dollars
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.payload,
|
||||||
|
isLoading: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
case LOAD_WALLET_SUMMARY_ERROR:
|
||||||
|
return { ...state, isLoading: false, error: action.payload.error };
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,18 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { RouterComponent } from './router';
|
import { RouterComponent } from './router';
|
||||||
import type { AppState } from '../types/app-state';
|
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => ({
|
export const Router = compose(withRouter)(RouterComponent);
|
||||||
todos: state.todos,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Router = compose(
|
|
||||||
withRouter,
|
|
||||||
connect(
|
|
||||||
mapStateToProps,
|
|
||||||
null,
|
|
||||||
),
|
|
||||||
)(RouterComponent);
|
|
||||||
|
|
|
@ -1,37 +1,68 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch, type Location } from 'react-router-dom';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { ScrollTopComponent } from './scroll-top';
|
import { ScrollTopComponent } from './scroll-top';
|
||||||
import { SidebarContainer } from '../containers/sidebar';
|
import { SidebarContainer } from '../containers/sidebar';
|
||||||
import { DashboardView } from '../views/dashboard';
|
import { DashboardContainer } from '../containers/dashboard';
|
||||||
import { SendView } from '../views/send';
|
import { SendView } from '../views/send';
|
||||||
import { ReceiveView } from '../views/receive';
|
import { ReceiveView } from '../views/receive';
|
||||||
import { SettingsView } from '../views/settings';
|
import { SettingsView } from '../views/settings';
|
||||||
import { NotFoundView } from '../views/not-found';
|
import { NotFoundView } from '../views/not-found';
|
||||||
import { ConsoleView } from '../views/console';
|
import { ConsoleView } from '../views/console';
|
||||||
import { LayoutComponent } from '../components/layout';
|
import { LayoutComponent } from '../components/layout';
|
||||||
|
import { HeaderComponent } from '../components/header';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DASHBOARD_ROUTE, SEND_ROUTE, RECEIVE_ROUTE, SETTINGS_ROUTE, CONSOLE_ROUTE,
|
DASHBOARD_ROUTE,
|
||||||
|
SEND_ROUTE,
|
||||||
|
RECEIVE_ROUTE,
|
||||||
|
SETTINGS_ROUTE,
|
||||||
|
CONSOLE_ROUTE,
|
||||||
} from '../constants/routes';
|
} from '../constants/routes';
|
||||||
|
|
||||||
export const RouterComponent = () => (
|
const FullWrapper = styled.div`
|
||||||
<Fragment>
|
display: flex;
|
||||||
<SidebarContainer />
|
flex-direction: column;
|
||||||
<ScrollTopComponent>
|
width: 100%;
|
||||||
{/* $FlowFixMe */}
|
`;
|
||||||
<LayoutComponent>
|
|
||||||
<Switch>
|
const ContentWrapper = styled.div`
|
||||||
<Route exact path={DASHBOARD_ROUTE} component={DashboardView} />
|
display: flex;
|
||||||
<Route path={SEND_ROUTE} component={SendView} />
|
flex-direction: row;
|
||||||
<Route path={RECEIVE_ROUTE} component={ReceiveView} />
|
width: 100vw;
|
||||||
<Route path={SETTINGS_ROUTE} component={SettingsView} />
|
`;
|
||||||
<Route path={CONSOLE_ROUTE} component={ConsoleView} />
|
|
||||||
<Route component={NotFoundView} />
|
const getTitle = (path: string) => {
|
||||||
</Switch>
|
if (path === '/') return 'Dashboard';
|
||||||
</LayoutComponent>
|
|
||||||
</ScrollTopComponent>
|
return path.replace('/', '');
|
||||||
</Fragment>
|
};
|
||||||
|
|
||||||
|
export const RouterComponent = ({ location }: { location: Location }) => (
|
||||||
|
<FullWrapper>
|
||||||
|
<HeaderComponent title={getTitle(location.pathname)} />
|
||||||
|
<ContentWrapper>
|
||||||
|
<SidebarContainer location={location} />
|
||||||
|
<ScrollTopComponent>
|
||||||
|
{/* $FlowFixMe */}
|
||||||
|
<LayoutComponent>
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={DASHBOARD_ROUTE}
|
||||||
|
component={DashboardContainer}
|
||||||
|
/>
|
||||||
|
<Route path={SEND_ROUTE} component={SendView} />
|
||||||
|
<Route path={RECEIVE_ROUTE} component={ReceiveView} />
|
||||||
|
<Route path={SETTINGS_ROUTE} component={SettingsView} />
|
||||||
|
<Route path={CONSOLE_ROUTE} component={ConsoleView} />
|
||||||
|
<Route component={NotFoundView} />
|
||||||
|
</Switch>
|
||||||
|
</LayoutComponent>
|
||||||
|
</ScrollTopComponent>
|
||||||
|
</ContentWrapper>
|
||||||
|
</FullWrapper>
|
||||||
);
|
);
|
||||||
|
|
67
app/theme.js
|
@ -1,5 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import theme from 'styled-theming';
|
import theme from 'styled-theming';
|
||||||
import { ThemeProvider, createGlobalStyle } from 'styled-components';
|
import { ThemeProvider, createGlobalStyle } from 'styled-components';
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
|
@ -9,12 +9,32 @@ import { DARK } from './constants/themes';
|
||||||
|
|
||||||
const darkOne = '#7B00DD';
|
const darkOne = '#7B00DD';
|
||||||
const lightOne = '#ffffff';
|
const lightOne = '#ffffff';
|
||||||
const brandOne = '#624cda';
|
const brandOne = '#000';
|
||||||
const brandTwo = '#a6ede2';
|
const brandTwo = '#3B3B3F';
|
||||||
|
const activeItem = '#F5CB00';
|
||||||
|
const text = '#FFF';
|
||||||
|
const cardBackgroundColor = '#000';
|
||||||
|
const sidebarLogoGradientBegin = '#F4B728';
|
||||||
|
const sidebarLogoGradientEnd = '#FFE240';
|
||||||
|
const sidebarHoveredItem = '#1C1C1C';
|
||||||
|
const sidebarHoveredItemLabel = '#969696';
|
||||||
|
const background = '#212124';
|
||||||
|
const transactionSent = '#FF6C6C';
|
||||||
|
const transactionReceived = '#6AEAC0';
|
||||||
|
const transactionsDate = '#777777';
|
||||||
|
|
||||||
const appTheme = {
|
const appTheme = {
|
||||||
mode: DARK,
|
mode: DARK,
|
||||||
fontFamily: 'PT Sans',
|
fontFamily: 'PT Sans',
|
||||||
|
fontWeight: {
|
||||||
|
bold: 700,
|
||||||
|
default: 400,
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
title: 1.25,
|
||||||
|
text: 0.84375,
|
||||||
|
zecValueBase: 1.125,
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: theme('mode', {
|
primary: theme('mode', {
|
||||||
light: lightOne,
|
light: lightOne,
|
||||||
|
@ -26,18 +46,43 @@ const appTheme = {
|
||||||
}),
|
}),
|
||||||
sidebarBg: brandOne,
|
sidebarBg: brandOne,
|
||||||
sidebarItem: brandTwo,
|
sidebarItem: brandTwo,
|
||||||
sidebarItemActive: lightOne,
|
sidebarItemActive: activeItem,
|
||||||
},
|
sidebarHoveredItem,
|
||||||
size: {
|
sidebarHoveredItemLabel,
|
||||||
title: 18,
|
cardBackgroundColor,
|
||||||
paragraph: 12,
|
text,
|
||||||
|
activeItem,
|
||||||
|
inactiveItem: brandTwo,
|
||||||
|
sidebarLogoGradientBegin,
|
||||||
|
sidebarLogoGradientEnd,
|
||||||
|
background,
|
||||||
|
transactionSent,
|
||||||
|
transactionReceived,
|
||||||
|
transactionsDate,
|
||||||
},
|
},
|
||||||
|
sidebarWidth: '200px',
|
||||||
|
headerHeight: '60px',
|
||||||
|
layoutPaddingLeft: '50px',
|
||||||
|
layoutPaddingRight: '45px',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GlobalStyle = createGlobalStyle`
|
||||||
|
${normalize()}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
/* eslint-disable react/prop-types */
|
/* eslint-disable react/prop-types */
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
export const DoczWrapper = ({ children }) => <ThemeProvider theme={appTheme}>{children()}</ThemeProvider>;
|
export const DoczWrapper = ({ children }) => (
|
||||||
|
<ThemeProvider theme={appTheme}>
|
||||||
export const GlobalStyle = createGlobalStyle`${normalize()}`;
|
<Fragment>
|
||||||
|
<GlobalStyle />
|
||||||
|
{children()}
|
||||||
|
</Fragment>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
|
||||||
export default appTheme;
|
export default appTheme;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type { TodoType } from './todo';
|
import type { State as WalletSummaryState } from '../redux/modules/wallet';
|
||||||
|
|
||||||
export type AppState = {
|
export type AppState = {
|
||||||
todos: Array<TodoType>,
|
walletSummary: WalletSummaryState,
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export default ({ value, append = '' }: { value: number, append?: string }) => `${append}${(value || 0).toLocaleString('de-DE')}`;
|
|
@ -0,0 +1,6 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export default (address: string = '') => `${address.substr(0, 20)}...${address.substr(
|
||||||
|
address.length - 10,
|
||||||
|
address.length,
|
||||||
|
)}`;
|
|
@ -1,39 +1,54 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
/* eslint-disable-next-line import/no-extraneous-dependencies */
|
|
||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import generateRandomString from '../utils/generate-random-string';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = {};
|
type Props = {};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
log: string | null,
|
log: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ConsoleView extends Component<Props, State> {
|
export class ConsoleView extends Component<Props, State> {
|
||||||
|
scrollView = React.createRef();
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
log: null,
|
log: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
ipcRenderer.on('zcashd-log', (event, message) => {
|
ipcRenderer.on('zcashd-log', (event, message) => {
|
||||||
this.setState(() => ({
|
this.setState(state => ({
|
||||||
log: message,
|
log: `${state.log}\n${message}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (this.scrollView && this.scrollView.current) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
this.scrollView.current.scrollTop = this.scrollView.current.scrollHeight;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { log } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='dashboard'>
|
<Wrapper ref={this.scrollView}>
|
||||||
{this.state.log
|
{log
|
||||||
&& this.state.log.split('\n').map(item => (
|
&& log.split('\n').map(item => (
|
||||||
<Fragment key={`${item.slice(0, 10)}`}>
|
<Fragment key={generateRandomString()}>
|
||||||
{item}
|
{item}
|
||||||
<br />
|
<br />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,73 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export const DashboardView = () => <div className='dashboard'>dashboard</div>;
|
import { WalletSummaryComponent } from '../components/wallet-summary';
|
||||||
|
import { TransactionDailyComponent } from '../components/transaction-daily';
|
||||||
|
import { withDaemonStatusCheck } from '../components/with-daemon-status-check';
|
||||||
|
|
||||||
|
import type { Transaction } from '../components/transaction-item';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
getSummary: () => void,
|
||||||
|
total: number,
|
||||||
|
shielded: number,
|
||||||
|
transparent: number,
|
||||||
|
error: string | null,
|
||||||
|
isLoading: boolean,
|
||||||
|
zecPrice: number,
|
||||||
|
addresses: string[],
|
||||||
|
transactions: { [day: string]: Transaction[] },
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Dashboard extends React.Component<Props> {
|
||||||
|
componentDidMount() {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
this.props.getSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
total,
|
||||||
|
shielded,
|
||||||
|
transparent,
|
||||||
|
zecPrice,
|
||||||
|
addresses,
|
||||||
|
transactions,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const days = Object.keys(transactions);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='dashboard'>
|
||||||
|
{isLoading ? (
|
||||||
|
'Loading'
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<WalletSummaryComponent
|
||||||
|
total={total}
|
||||||
|
shielded={shielded}
|
||||||
|
transparent={transparent}
|
||||||
|
zecPrice={zecPrice}
|
||||||
|
addresses={addresses}
|
||||||
|
/>
|
||||||
|
{days.map(day => (
|
||||||
|
<TransactionDailyComponent
|
||||||
|
transactionsDate={day}
|
||||||
|
transactions={transactions[day]}
|
||||||
|
zecPrice={zecPrice}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DashboardView = withDaemonStatusCheck(Dashboard);
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import TodoInput from '../components/todo-input';
|
|
||||||
import TodoList from '../components/todo-list';
|
|
||||||
|
|
||||||
import type { TodoType } from '../types/todo';
|
|
||||||
|
|
||||||
import checklist from '../assets/images/checklist.svg';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
addTodo: Function,
|
|
||||||
deleteTodo: Function,
|
|
||||||
toggleEdit: Function,
|
|
||||||
todos: Array<TodoType>,
|
|
||||||
updateTodo: Function,
|
|
||||||
cancelUpdateTodo: Function,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (props: Props) => {
|
|
||||||
const {
|
|
||||||
addTodo, todos, deleteTodo, toggleEdit, updateTodo, cancelUpdateTodo,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='todo'>
|
|
||||||
<div className='todo__heading'>
|
|
||||||
<img src={checklist} alt='Testing File Loader' className='todo__image' />
|
|
||||||
<h1 className='todo__header'>Todo List App</h1>
|
|
||||||
</div>
|
|
||||||
<TodoInput addTodo={addTodo} />
|
|
||||||
<TodoList
|
|
||||||
todos={todos}
|
|
||||||
deleteTodo={deleteTodo}
|
|
||||||
toggleEdit={toggleEdit}
|
|
||||||
updateTodo={updateTodo}
|
|
||||||
cancelUpdateTodo={cancelUpdateTodo}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -19,16 +19,36 @@ import log from './logger';
|
||||||
|
|
||||||
const queue = new Queue({ concurrency: 1, autoStart: false });
|
const queue = new Queue({ concurrency: 1, autoStart: false });
|
||||||
|
|
||||||
const httpClient = got.extend({ baseUrl: 'https://z.cash/downloads/', retry: 3, useElectronNet: true });
|
const httpClient = got.extend({
|
||||||
|
baseUrl: 'https://z.cash/downloads/',
|
||||||
|
retry: 3,
|
||||||
|
useElectronNet: true,
|
||||||
|
});
|
||||||
|
|
||||||
const FILES: Array<{ name: string, hash: string }> = [
|
const FILES: Array<{ name: string, hash: string }> = [
|
||||||
{ name: 'sprout-proving.key', hash: '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7' },
|
{
|
||||||
{ name: 'sprout-verifying.key', hash: '4bd498dae0aacfd8e98dc306338d017d9c08dd0918ead18172bd0aec2fc5df82' },
|
name: 'sprout-proving.key',
|
||||||
{ name: 'sapling-spend.params', hash: '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13' },
|
hash: '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7',
|
||||||
{ name: 'sapling-output.params', hash: '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4' },
|
},
|
||||||
{ name: 'sprout-groth16.params', hash: 'b685d700c60328498fbde589c8c7c484c722b788b265b72af448a5bf0ee55b50' },
|
{
|
||||||
|
name: 'sprout-verifying.key',
|
||||||
|
hash: '4bd498dae0aacfd8e98dc306338d017d9c08dd0918ead18172bd0aec2fc5df82',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sapling-spend.params',
|
||||||
|
hash: '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sapling-output.params',
|
||||||
|
hash: '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sprout-groth16.params',
|
||||||
|
hash: 'b685d700c60328498fbde589c8c7c484c722b788b265b72af448a5bf0ee55b50',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
const checkSha256 = (pathToFile: string, expectedHash: string) => new Promise((resolve, reject) => {
|
const checkSha256 = (pathToFile: string, expectedHash: string) => new Promise((resolve, reject) => {
|
||||||
fs.readFile(pathToFile, (err, file) => {
|
fs.readFile(pathToFile, (err, file) => {
|
||||||
if (err) return reject(new Error(err));
|
if (err) return reject(new Error(err));
|
||||||
|
@ -39,6 +59,7 @@ const checkSha256 = (pathToFile: string, expectedHash: string) => new Promise((r
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
const downloadFile = ({ file, pathToSave }): Promise<*> => new Promise((resolve, reject) => {
|
const downloadFile = ({ file, pathToSave }): Promise<*> => new Promise((resolve, reject) => {
|
||||||
log(`Downloading ${file.name}...`);
|
log(`Downloading ${file.name}...`);
|
||||||
|
|
||||||
|
@ -50,7 +71,9 @@ const downloadFile = ({ file, pathToSave }): Promise<*> => new Promise((resolve,
|
||||||
log(`SHA256 validation for file ${file.name} succeeded!`);
|
log(`SHA256 validation for file ${file.name} succeeded!`);
|
||||||
resolve(file.name);
|
resolve(file.name);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`SHA256 validation failed for file: ${file.name}`));
|
reject(
|
||||||
|
new Error(`SHA256 validation failed for file: ${file.name}`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -61,7 +84,9 @@ const downloadFile = ({ file, pathToSave }): Promise<*> => new Promise((resolve,
|
||||||
let missingDownloadParam = false;
|
let missingDownloadParam = false;
|
||||||
|
|
||||||
export default (): Promise<*> => new Promise((resolve, reject) => {
|
export default (): Promise<*> => new Promise((resolve, reject) => {
|
||||||
const firstRunProcess = cp.spawn(path.join(getBinariesPath(), 'win', 'first-run.bat'));
|
const firstRunProcess = cp.spawn(
|
||||||
|
path.join(getBinariesPath(), 'win', 'first-run.bat'),
|
||||||
|
);
|
||||||
firstRunProcess.stdout.on('data', data => log(data.toString()));
|
firstRunProcess.stdout.on('data', data => log(data.toString()));
|
||||||
firstRunProcess.stderr.on('data', data => reject(data.toString()));
|
firstRunProcess.stderr.on('data', data => reject(data.toString()));
|
||||||
|
|
||||||
|
@ -70,20 +95,33 @@ export default (): Promise<*> => new Promise((resolve, reject) => {
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
FILES.map(async (file) => {
|
FILES.map(async (file) => {
|
||||||
const pathToSave = path.join(app.getPath('userData'), '..', 'ZcashParams', file.name);
|
const pathToSave = path.join(
|
||||||
|
app.getPath('userData'),
|
||||||
|
'..',
|
||||||
|
'ZcashParams',
|
||||||
|
file.name,
|
||||||
|
);
|
||||||
|
|
||||||
const [cannotAccess] = await eres(util.promisify(fs.access)(pathToSave, fs.constants.F_OK));
|
const [cannotAccess] = await eres(
|
||||||
|
util.promisify(fs.access)(pathToSave, fs.constants.F_OK),
|
||||||
|
);
|
||||||
|
|
||||||
if (cannotAccess) {
|
if (cannotAccess) {
|
||||||
missingDownloadParam = true;
|
missingDownloadParam = true;
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
queue.add(() => downloadFile({ file, pathToSave }).then(() => log(`Download ${file.name} finished!`)));
|
queue.add(() => downloadFile({ file, pathToSave }).then(() => log(`Download ${file.name} finished!`)));
|
||||||
} else {
|
} else {
|
||||||
const isValid = await checkSha256(pathToSave, file.hash);
|
const isValid = await checkSha256(pathToSave, file.hash);
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
log(`${file.name} already is in ${pathToSave}...`);
|
log(`${file.name} already is in ${pathToSave}...`);
|
||||||
} else {
|
} else {
|
||||||
log(`File: ${file.name} failed in the SHASUM validation, downloading again...`);
|
log(
|
||||||
|
`File: ${
|
||||||
|
file.name
|
||||||
|
} failed in the SHASUM validation, downloading again...`,
|
||||||
|
);
|
||||||
queue.add(() => {
|
queue.add(() => {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
downloadFile({ file, pathToSave }).then(() => log(`Download ${file.name} finished!`));
|
downloadFile({ file, pathToSave }).then(() => log(`Download ${file.name} finished!`));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,8 @@ import path from 'path';
|
||||||
/* eslint-disable-next-line import/no-extraneous-dependencies */
|
/* eslint-disable-next-line import/no-extraneous-dependencies */
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
// $FlowFixMe
|
/* eslint-disable operator-linebreak */
|
||||||
export default () => (isDev ? path.join(__dirname, '..', '..', './bin') : path.join(process.resourcesPath, 'bin'));
|
export default () => (isDev
|
||||||
|
? path.join(__dirname, '..', '..', './bin')
|
||||||
|
: // $FlowFixMe
|
||||||
|
path.join(process.resourcesPath, 'bin'));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
/* eslint-disable-next-line no-console */
|
/* eslint-disable-next-line no-console */
|
||||||
export default (...message: Array<*>) => console.log('[ZCash Daemon]', ...message);
|
export default (...message: Array<*>) => console.log('[Zcash Daemon]', ...message);
|
||||||
|
|
|
@ -8,5 +8,7 @@ import runUnixFetchParams from './fetch-unix-params';
|
||||||
export default (): Promise<*> => {
|
export default (): Promise<*> => {
|
||||||
log('Fetching params');
|
log('Fetching params');
|
||||||
|
|
||||||
return os.platform() === 'win32' ? fetchWindowsParams() : runUnixFetchParams();
|
return os.platform() === 'win32'
|
||||||
|
? fetchWindowsParams()
|
||||||
|
: runUnixFetchParams();
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,7 +16,7 @@ import getDaemonName from './get-daemon-name';
|
||||||
import fetchParams from './run-fetch-params';
|
import fetchParams from './run-fetch-params';
|
||||||
import log from './logger';
|
import log from './logger';
|
||||||
import store from '../electron-store';
|
import store from '../electron-store';
|
||||||
import generateRandomString from './generate-random-string';
|
import generateRandomString from '../../app/utils/generate-random-string';
|
||||||
|
|
||||||
const getDaemonOptions = ({ username, password }) => {
|
const getDaemonOptions = ({ username, password }) => {
|
||||||
/*
|
/*
|
||||||
|
@ -35,13 +35,20 @@ const getDaemonOptions = ({ username, password }) => {
|
||||||
`-rpcuser=${username}`,
|
`-rpcuser=${username}`,
|
||||||
`-rpcpassword=${password}`,
|
`-rpcpassword=${password}`,
|
||||||
];
|
];
|
||||||
return isDev ? defaultOptions.concat(['-testnet', '-addnode=testnet.z.cash']) : defaultOptions;
|
return isDev
|
||||||
|
? defaultOptions.concat(['-testnet', '-addnode=testnet.z.cash'])
|
||||||
|
: defaultOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolved = false;
|
let resolved = false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
const runDaemon: () => Promise<?ChildProcess> = () => new Promise(async (resolve, reject) => {
|
const runDaemon: () => Promise<?ChildProcess> = () => new Promise(async (resolve, reject) => {
|
||||||
const processName = path.join(getBinariesPath(), getOsFolder(), getDaemonName());
|
const processName = path.join(
|
||||||
|
getBinariesPath(),
|
||||||
|
getOsFolder(),
|
||||||
|
getDaemonName(),
|
||||||
|
);
|
||||||
|
|
||||||
const [err] = await eres(fetchParams());
|
const [err] = await eres(fetchParams());
|
||||||
|
|
||||||
|
@ -76,9 +83,13 @@ const runDaemon: () => Promise<?ChildProcess> = () => new Promise(async (resolve
|
||||||
store.set('rpcpassword', rpcCredentials.password);
|
store.set('rpcpassword', rpcCredentials.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
const childProcess = cp.spawn(processName, getDaemonOptions(rpcCredentials), {
|
const childProcess = cp.spawn(
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
processName,
|
||||||
});
|
getDaemonOptions(rpcCredentials),
|
||||||
|
{
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
childProcess.stdout.on('data', (data) => {
|
childProcess.stdout.on('data', (data) => {
|
||||||
if (mainWindow) mainWindow.webContents.send('zcashd-log', data.toString());
|
if (mainWindow) mainWindow.webContents.send('zcashd-log', data.toString());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import '@babel/polyfill';
|
import '@babel/polyfill';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
|
@ -13,6 +14,10 @@ import eres from 'eres';
|
||||||
import { registerDebugShortcut } from '../utils/debug-shortcut';
|
import { registerDebugShortcut } from '../utils/debug-shortcut';
|
||||||
import runDaemon from './daemon/zcashd-child-process';
|
import runDaemon from './daemon/zcashd-child-process';
|
||||||
import zcashLog from './daemon/logger';
|
import zcashLog from './daemon/logger';
|
||||||
|
import getZecPrice from '../services/zec-price';
|
||||||
|
import store from './electron-store';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
let mainWindow: BrowserWindowType;
|
let mainWindow: BrowserWindowType;
|
||||||
let updateAvailable: boolean = false;
|
let updateAvailable: boolean = false;
|
||||||
|
@ -37,9 +42,9 @@ const createWindow = () => {
|
||||||
|
|
||||||
autoUpdater.on('download-progress', progress => showStatus(
|
autoUpdater.on('download-progress', progress => showStatus(
|
||||||
/* eslint-disable-next-line max-len */
|
/* eslint-disable-next-line max-len */
|
||||||
`Download speed: ${progress.bytesPerSecond} - Downloaded ${progress.percent}% (${progress.transferred}/${
|
`Download speed: ${progress.bytesPerSecond} - Downloaded ${
|
||||||
progress.total
|
progress.percent
|
||||||
})`,
|
}% (${progress.transferred}/${progress.total})`,
|
||||||
));
|
));
|
||||||
autoUpdater.on('update-downloaded', () => {
|
autoUpdater.on('update-downloaded', () => {
|
||||||
updateAvailable = true;
|
updateAvailable = true;
|
||||||
|
@ -58,10 +63,18 @@ const createWindow = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
getZecPrice().then((obj) => {
|
||||||
|
store.set('ZEC_DOLLAR_PRICE', obj.USD);
|
||||||
|
});
|
||||||
|
|
||||||
mainWindow.setVisibleOnAllWorkspaces(true);
|
mainWindow.setVisibleOnAllWorkspaces(true);
|
||||||
registerDebugShortcut(app, mainWindow);
|
registerDebugShortcut(app, mainWindow);
|
||||||
|
|
||||||
mainWindow.loadURL(isDev ? 'http://0.0.0.0:8080/' : `file://${path.join(__dirname, '../build/index.html')}`);
|
mainWindow.loadURL(
|
||||||
|
isDev
|
||||||
|
? 'http://0.0.0.0:8080/'
|
||||||
|
: `file://${path.join(__dirname, '../build/index.html')}`,
|
||||||
|
);
|
||||||
|
|
||||||
exports.app = app;
|
exports.app = app;
|
||||||
exports.mainWindow = mainWindow;
|
exports.mainWindow = mainWindow;
|
||||||
|
@ -75,7 +88,7 @@ app.on('ready', async () => {
|
||||||
if (err || !proc) return zcashLog(err);
|
if (err || !proc) return zcashLog(err);
|
||||||
|
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
zcashLog(`ZCash Daemon running. PID: ${proc.pid}`);
|
zcashLog(`Zcash Daemon running. PID: ${proc.pid}`);
|
||||||
|
|
||||||
zcashDaemon = proc;
|
zcashDaemon = proc;
|
||||||
});
|
});
|
||||||
|
@ -87,7 +100,7 @@ app.on('window-all-closed', () => {
|
||||||
});
|
});
|
||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
if (zcashDaemon) {
|
if (zcashDaemon) {
|
||||||
zcashLog('Graceful shutdown ZCash Daemon, this may take a few seconds.');
|
zcashLog('Graceful shutdown Zcash Daemon, this may take a few seconds.');
|
||||||
zcashDaemon.kill('SIGINT');
|
zcashDaemon.kill('SIGINT');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,6 @@
|
||||||
"css-loader": "^1.0.1",
|
"css-loader": "^1.0.1",
|
||||||
"docz": "^0.12.13",
|
"docz": "^0.12.13",
|
||||||
"docz-plugin-css": "^0.11.0",
|
"docz-plugin-css": "^0.11.0",
|
||||||
"electron": "^3.0.10",
|
|
||||||
"electron-builder": "^20.36.2",
|
"electron-builder": "^20.36.2",
|
||||||
"electron-icon-maker": "^0.0.4",
|
"electron-icon-maker": "^0.0.4",
|
||||||
"electron-is-dev": "^1.0.1",
|
"electron-is-dev": "^1.0.1",
|
||||||
|
@ -88,15 +87,22 @@
|
||||||
"@babel/register": "^7.0.0",
|
"@babel/register": "^7.0.0",
|
||||||
"autoprefixer": "^9.3.1",
|
"autoprefixer": "^9.3.1",
|
||||||
"connected-react-router": "^5.0.1",
|
"connected-react-router": "^5.0.1",
|
||||||
|
"date-fns": "^1.30.1",
|
||||||
|
"dotenv": "^6.2.0",
|
||||||
|
"electron": "^3.0.10",
|
||||||
"electron-store": "^2.0.0",
|
"electron-store": "^2.0.0",
|
||||||
"eres": "^1.0.1",
|
"eres": "^1.0.1",
|
||||||
"got": "^9.3.2",
|
"got": "^9.3.2",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
|
"lodash.flow": "^3.5.0",
|
||||||
|
"lodash.groupby": "^4.6.0",
|
||||||
"p-queue": "^3.0.0",
|
"p-queue": "^3.0.0",
|
||||||
"process-exists": "^3.1.0",
|
"process-exists": "^3.1.0",
|
||||||
"qrcode.react": "^0.8.0",
|
"qrcode.react": "^0.8.0",
|
||||||
"react": "^16.6.0",
|
"react": "^16.6.0",
|
||||||
|
"react-click-outside": "tj/react-click-outside",
|
||||||
"react-dom": "^16.6.0",
|
"react-dom": "^16.6.0",
|
||||||
|
"react-popover": "^0.5.10",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
export const METHODS = [
|
export const METHODS = [
|
||||||
'getbestblockhash',
|
'getbestblockhash',
|
||||||
|
@ -745,8 +746,8 @@ export type APIMethods = {
|
||||||
z_exportwallet: (filename: string) => Promise<string>,
|
z_exportwallet: (filename: string) => Promise<string>,
|
||||||
z_getbalance: (address: string, minconf?: number) => Promise<number>,
|
z_getbalance: (address: string, minconf?: number) => Promise<number>,
|
||||||
z_getnewaddress: (type: string) => Promise<string>,
|
z_getnewaddress: (type: string) => Promise<string>,
|
||||||
z_getoperationresult: (operationid: string) => Promise<Object[]>,
|
z_getoperationresult: (operationid?: string[]) => Promise<Object[]>,
|
||||||
z_getoperationstatus: (operationid: string) => Promise<Object[]>,
|
z_getoperationstatus: (operationid?: string[]) => Promise<Object[]>,
|
||||||
z_gettotalbalance: (
|
z_gettotalbalance: (
|
||||||
minconf?: number,
|
minconf?: number,
|
||||||
includeWatchonly?: boolean,
|
includeWatchonly?: boolean,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// @flow
|
||||||
|
import { net } from 'electron';
|
||||||
|
|
||||||
|
type Payload = {
|
||||||
|
[currency: string]: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
WARNING:
|
||||||
|
Just a super fast way to get the zec price
|
||||||
|
*/
|
||||||
|
export default (currencies: string[] = ['USD']): Promise<Payload> => new Promise((resolve, reject) => {
|
||||||
|
const ENDPOINT = `https://min-api.cryptocompare.com/data/price?fsym=ZEC&tsyms=${currencies.join(
|
||||||
|
',',
|
||||||
|
)}&api_key=${String(process.env.ZEC_PRICE_API_KEY)}`;
|
||||||
|
|
||||||
|
const request = net.request(ENDPOINT);
|
||||||
|
request.on('response', (response) => {
|
||||||
|
let data = '';
|
||||||
|
/* eslint-disable-next-line no-return-assign */
|
||||||
|
response.on('data', chunk => (data += chunk));
|
||||||
|
response.on('end', () => {
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
request.end();
|
||||||
|
});
|
60
yarn.lock
|
@ -3898,6 +3898,13 @@ css-value@~0.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea"
|
resolved "https://registry.yarnpkg.com/css-value/-/css-value-0.0.1.tgz#5efd6c2eea5ea1fd6b6ac57ec0427b18452424ea"
|
||||||
integrity sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=
|
integrity sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=
|
||||||
|
|
||||||
|
css-vendor@^0.3.1:
|
||||||
|
version "0.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-0.3.8.tgz#6421cfd3034ce664fe7673972fd0119fc28941fa"
|
||||||
|
integrity sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=
|
||||||
|
dependencies:
|
||||||
|
is-in-browser "^1.0.2"
|
||||||
|
|
||||||
css-what@2.1, css-what@^2.1.2:
|
css-what@2.1, css-what@^2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
|
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
|
||||||
|
@ -4073,6 +4080,11 @@ date-fns@^1.23.0:
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6"
|
||||||
integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
|
integrity sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==
|
||||||
|
|
||||||
|
date-fns@^1.30.1:
|
||||||
|
version "1.30.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||||
|
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
||||||
|
|
||||||
date-now@^0.1.4:
|
date-now@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||||
|
@ -4800,6 +4812,11 @@ dotenv@^6.0.0, dotenv@^6.1.0:
|
||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.1.0.tgz#9853b6ca98292acb7dec67a95018fa40bccff42c"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.1.0.tgz#9853b6ca98292acb7dec67a95018fa40bccff42c"
|
||||||
integrity sha512-/veDn2ztgRlB7gKmE3i9f6CmDIyXAy6d5nBq+whO9SLX+Zs1sXEgFLPi+aSuWqUuusMfbi84fT8j34fs1HaYUw==
|
integrity sha512-/veDn2ztgRlB7gKmE3i9f6CmDIyXAy6d5nBq+whO9SLX+Zs1sXEgFLPi+aSuWqUuusMfbi84fT8j34fs1HaYUw==
|
||||||
|
|
||||||
|
dotenv@^6.2.0:
|
||||||
|
version "6.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
|
||||||
|
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
|
||||||
|
|
||||||
duplexer2@~0.1.4:
|
duplexer2@~0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
||||||
|
@ -7542,6 +7559,11 @@ is-hexadecimal@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
|
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835"
|
||||||
integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==
|
integrity sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==
|
||||||
|
|
||||||
|
is-in-browser@^1.0.2:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
|
||||||
|
integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
|
||||||
|
|
||||||
is-installed-globally@^0.1.0:
|
is-installed-globally@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
|
resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
|
||||||
|
@ -8827,6 +8849,13 @@ lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||||
|
|
||||||
|
lodash.debounce@^3.0.0:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-3.1.1.tgz#812211c378a94cc29d5aa4e3346cf0bfce3a7df5"
|
||||||
|
integrity sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=
|
||||||
|
dependencies:
|
||||||
|
lodash._getnative "^3.0.0"
|
||||||
|
|
||||||
lodash.debounce@^4.0.8:
|
lodash.debounce@^4.0.8:
|
||||||
version "4.0.8"
|
version "4.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
@ -8847,11 +8876,21 @@ lodash.flattendepth@^4.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.flattendepth/-/lodash.flattendepth-4.7.0.tgz#b4d2d14fc7d9c53deb96642eb616fff22a60932f"
|
resolved "https://registry.yarnpkg.com/lodash.flattendepth/-/lodash.flattendepth-4.7.0.tgz#b4d2d14fc7d9c53deb96642eb616fff22a60932f"
|
||||||
integrity sha1-tNLRT8fZxT3rlmQuthb/8ipgky8=
|
integrity sha1-tNLRT8fZxT3rlmQuthb/8ipgky8=
|
||||||
|
|
||||||
|
lodash.flow@^3.5.0:
|
||||||
|
version "3.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
|
||||||
|
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
|
||||||
|
|
||||||
lodash.get@^4.4.2:
|
lodash.get@^4.4.2:
|
||||||
version "4.4.2"
|
version "4.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||||
|
|
||||||
|
lodash.groupby@^4.6.0:
|
||||||
|
version "4.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
|
||||||
|
integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=
|
||||||
|
|
||||||
lodash.isarguments@^3.0.0:
|
lodash.isarguments@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||||
|
@ -8906,6 +8945,13 @@ lodash.tail@^4.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
|
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
|
||||||
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
|
integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
|
||||||
|
|
||||||
|
lodash.throttle@^3.0.3:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-3.0.4.tgz#bc4f471fb328e4d6fdc6df2b3d3caf113f0f89c9"
|
||||||
|
integrity sha1-vE9HH7Mo5Nb9xt8rPTyvET8Pick=
|
||||||
|
dependencies:
|
||||||
|
lodash.debounce "^3.0.0"
|
||||||
|
|
||||||
lodash.throttle@^4.1.1:
|
lodash.throttle@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
|
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
|
||||||
|
@ -11470,6 +11516,10 @@ re-resizable@^4.10.0:
|
||||||
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-4.10.0.tgz#e5a4d5e0c79e9c94a1d68fda77aee91526eaff3e"
|
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-4.10.0.tgz#e5a4d5e0c79e9c94a1d68fda77aee91526eaff3e"
|
||||||
integrity sha512-g5Q5IswKX7LM+MtYFnuzaQrTEGr/kpserqGV8V6HYkjwbV60XnJv00VlKugLHEwlQ5pgrV08spm0TjyyYVbWmQ==
|
integrity sha512-g5Q5IswKX7LM+MtYFnuzaQrTEGr/kpserqGV8V6HYkjwbV60XnJv00VlKugLHEwlQ5pgrV08spm0TjyyYVbWmQ==
|
||||||
|
|
||||||
|
react-click-outside@tj/react-click-outside:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://codeload.github.com/tj/react-click-outside/tar.gz/a833ddc5be47490307f9fcc6ed09d8c353108510"
|
||||||
|
|
||||||
react-codemirror2@^5.1.0:
|
react-codemirror2@^5.1.0:
|
||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-5.1.0.tgz#62de4460178adea40eb52eabf7491669bf3794b8"
|
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-5.1.0.tgz#62de4460178adea40eb52eabf7491669bf3794b8"
|
||||||
|
@ -11664,6 +11714,16 @@ react-perfect-scrollbar@^1.4.2:
|
||||||
perfect-scrollbar "^1.4.0"
|
perfect-scrollbar "^1.4.0"
|
||||||
prop-types "^15.6.1"
|
prop-types "^15.6.1"
|
||||||
|
|
||||||
|
react-popover@^0.5.10:
|
||||||
|
version "0.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-popover/-/react-popover-0.5.10.tgz#40d5e854300a96722ffc1620e49d840cb1ad5db6"
|
||||||
|
integrity sha512-5SYDTfncywSH00I70oHd4gFRUR8V0rJ4sRADSI/P6G0RVXp9jUgaWloJ0Bk+SFnjpLPuipTKuzQNNd2CTs5Hrw==
|
||||||
|
dependencies:
|
||||||
|
css-vendor "^0.3.1"
|
||||||
|
debug "^2.6.8"
|
||||||
|
lodash.throttle "^3.0.3"
|
||||||
|
prop-types "^15.5.10"
|
||||||
|
|
||||||
react-powerplug@^1.0.0:
|
react-powerplug@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-powerplug/-/react-powerplug-1.0.0.tgz#f9c10a761ece115661b8fd10920c4e573ea95909"
|
resolved "https://registry.yarnpkg.com/react-powerplug/-/react-powerplug-1.0.0.tgz#f9c10a761ece115661b8fd10920c4e573ea95909"
|
||||||
|
|