From 13fd3c3f55980ff18744788c110dffc98d82d8cf Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 20:41:08 -0300 Subject: [PATCH 01/55] hotfix: update sidebar colors --- app/theme.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/theme.js b/app/theme.js index 88391d0..34d6625 100644 --- a/app/theme.js +++ b/app/theme.js @@ -9,11 +9,13 @@ import { DARK } from './constants/themes'; const darkOne = '#7B00DD'; const lightOne = '#ffffff'; -const brandOne = '#624cda'; -const brandTwo = '#a6ede2'; +const brandOne = '#000'; +const brandTwo = '#3B3B3F'; const activeItem = '#F5CB00'; const text = '#FFF'; const cardBackgroundColor = '#000'; +const sidebarLogoGradientBegin = '#F4B728'; +const sidebarLogoGradientEnd = '#FFE240'; const appTheme = { mode: DARK, @@ -29,10 +31,12 @@ const appTheme = { }), sidebarBg: brandOne, sidebarItem: brandTwo, - sidebarItemActive: lightOne, + sidebarItemActive: activeItem, cardBackgroundColor, text, activeItem, + sidebarLogoGradientBegin, + sidebarLogoGradientEnd, }, size: { title: 18, From 21ce7e2c37a8d8da5607bd101bcb31170aceac47 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 20:41:55 -0300 Subject: [PATCH 02/55] feature: add zcash-logo --- app/components/zcash-logo.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 app/components/zcash-logo.js diff --git a/app/components/zcash-logo.js b/app/components/zcash-logo.js new file mode 100644 index 0000000..7dad32b --- /dev/null +++ b/app/components/zcash-logo.js @@ -0,0 +1,8 @@ +// @flow +import React from 'react'; + +export const ZCashLogo = () => ( + + + +); From c172e47c4de7af137a10c1c9a8064120f3dc1fc9 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 20:42:50 -0300 Subject: [PATCH 03/55] feature: add TRANSACTIONS_ROUTE --- app/constants/routes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/constants/routes.js b/app/constants/routes.js index 5120961..3b5d0ee 100644 --- a/app/constants/routes.js +++ b/app/constants/routes.js @@ -1,7 +1,8 @@ // @flow export const DASHBOARD_ROUTE = '/'; +export const CONSOLE_ROUTE = '/console'; export const SEND_ROUTE = '/send'; export const RECEIVE_ROUTE = '/receive'; +export const TRANSACTIONS_ROUTE = '/receive'; export const SETTINGS_ROUTE = '/settings'; -export const CONSOLE_ROUTE = '/console'; From 64f12d483d796b21b00f952be3f89207cca69ecd Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 20:43:09 -0300 Subject: [PATCH 04/55] feature: add Icons in sidebar items --- app/constants/sidebar.js | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/app/constants/sidebar.js b/app/constants/sidebar.js index d8d2988..9a3e8e1 100644 --- a/app/constants/sidebar.js +++ b/app/constants/sidebar.js @@ -1,28 +1,48 @@ // @flow +import React from 'react'; +import { FaThLarge, FaCode } from 'react-icons/fa'; +import { IoIosSend } from 'react-icons/io'; +import { TiDownload } from 'react-icons/ti'; +import { MdSettings, MdTransform } from 'react-icons/md'; 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'; export const MENU_OPTIONS = [ { label: 'Dashboard', route: DASHBOARD_ROUTE, - }, - { - label: 'Send', - route: SEND_ROUTE, - }, - { - label: 'Receive', - route: RECEIVE_ROUTE, + icon: , }, { label: 'Console', route: CONSOLE_ROUTE, + icon: , + }, + { + label: 'Send', + route: SEND_ROUTE, + icon: , + }, + { + label: 'Receive', + route: RECEIVE_ROUTE, + icon: , + }, + { + label: 'Transactions', + route: TRANSACTIONS_ROUTE, + icon: , }, { label: 'Settings', route: SETTINGS_ROUTE, + icon: , }, ]; From 913af43f42677577107d1cb2a3322f86d1e5ee3d Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 20:43:27 -0300 Subject: [PATCH 05/55] feature: update sidebar styles --- app/components/sidebar.js | 48 +++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 2dc953f..0acaf30 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -1,9 +1,10 @@ // @flow -import React from 'react'; +import React, { type Element } from 'react'; import styled from 'styled-components'; -import { Link } from 'react-router-dom'; +import { Link, withRouter, type Match } from 'react-router-dom'; import { MENU_OPTIONS } from '../constants/sidebar'; +import { ZCashLogo } from './zcash-logo'; const Wrapper = styled.div` display: flex; @@ -12,36 +13,63 @@ const Wrapper = styled.div` height: 100vh; font-family: ${props => props.theme.fontFamily} background-color: ${props => props.theme.colors.sidebarBg}; - padding: 20px; +`; + +const LogoWrapper = styled.div` + height: 60px; + width: 200px; + margin-bottom: 20px; + background-image: linear-gradient( + to right, + ${props => props.theme.colors.sidebarLogoGradientBegin}, + ${props => props.theme.colors.sidebarLogoGradientEnd} + ); `; const StyledLink = styled(Link)` - color: ${props => props.theme.colors.sidebarItem}; - font-size: 16px; + color: ${props => (props.isActive ? props.theme.colors.sidebarItemActive : props.theme.colors.sidebarItem)}; + font-size: 0.9em; text-decoration: none; - font-weight: 700; - padding: 5px 0; + font-weight: ${props => (props.isActive ? 700 : 400)}; + padding: 15px 20px; + display: flex; + align-items: center; + + &:hover { + color: ${props => props.theme.colors.sidebarItemActive}; + } `; type MenuItem = { route: string, label: string, + icon: Element<*>, }; type Props = { options?: MenuItem[], + match: Match, }; -export const SidebarComponent = ({ options }: Props) => ( +const Sidebar = ({ options, match }: Props) => ( + + + {(options || []).map(item => ( - + + {React.cloneElement(item.icon, { + style: { marginRight: '15px' }, + size: 20, + })} {item.label} ))} ); -SidebarComponent.defaultProps = { +export const SidebarComponent = withRouter(Sidebar); + +Sidebar.defaultProps = { options: MENU_OPTIONS, }; From efe32a52276764bd0f364953197b62ae2ed2b756 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:09:22 -0300 Subject: [PATCH 06/55] hotfix: fix route typo --- app/constants/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/constants/routes.js b/app/constants/routes.js index 3b5d0ee..8d72502 100644 --- a/app/constants/routes.js +++ b/app/constants/routes.js @@ -4,5 +4,5 @@ export const DASHBOARD_ROUTE = '/'; export const CONSOLE_ROUTE = '/console'; export const SEND_ROUTE = '/send'; export const RECEIVE_ROUTE = '/receive'; -export const TRANSACTIONS_ROUTE = '/receive'; +export const TRANSACTIONS_ROUTE = '/transactions'; export const SETTINGS_ROUTE = '/settings'; From a39dd135f6816e009b038238067aed2a45e5e955 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:15:38 -0300 Subject: [PATCH 07/55] hotfix: pass location down in sidebar --- __tests__/components/Sidebar.test.js | 2 +- app/components/sidebar.js | 12 +++++------- app/router/router.js | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/__tests__/components/Sidebar.test.js b/__tests__/components/Sidebar.test.js index d8636ae..76975bd 100644 --- a/__tests__/components/Sidebar.test.js +++ b/__tests__/components/Sidebar.test.js @@ -13,7 +13,7 @@ describe('', () => { // $FlowFixMe const { asFragment } = render( - + , ); diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 0acaf30..351e44d 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -2,7 +2,7 @@ import React, { type Element } from 'react'; import styled from 'styled-components'; -import { Link, withRouter, type Match } from 'react-router-dom'; +import { Link, type Location } from 'react-router-dom'; import { MENU_OPTIONS } from '../constants/sidebar'; import { ZCashLogo } from './zcash-logo'; @@ -48,16 +48,16 @@ type MenuItem = { type Props = { options?: MenuItem[], - match: Match, + location: Location, }; -const Sidebar = ({ options, match }: Props) => ( +export const SidebarComponent = ({ options, location }: Props) => ( {(options || []).map(item => ( - + {React.cloneElement(item.icon, { style: { marginRight: '15px' }, size: 20, @@ -68,8 +68,6 @@ const Sidebar = ({ options, match }: Props) => ( ); -export const SidebarComponent = withRouter(Sidebar); - -Sidebar.defaultProps = { +SidebarComponent.defaultProps = { options: MENU_OPTIONS, }; diff --git a/app/router/router.js b/app/router/router.js index 626c179..c50d910 100644 --- a/app/router/router.js +++ b/app/router/router.js @@ -1,7 +1,7 @@ // @flow 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'; @@ -24,9 +24,9 @@ const Wrapper = styled.div` width: 100vw; `; -export const RouterComponent = () => ( +export const RouterComponent = ({ location }: { location: Location }) => ( - + {/* $FlowFixMe */} From 9522f9d2455bfed8a7e6f89cc799a3f6f4f61c1d Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:34:38 -0300 Subject: [PATCH 08/55] feature: add background color --- app/theme.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/theme.js b/app/theme.js index 34d6625..cf7a681 100644 --- a/app/theme.js +++ b/app/theme.js @@ -16,6 +16,7 @@ const text = '#FFF'; const cardBackgroundColor = '#000'; const sidebarLogoGradientBegin = '#F4B728'; const sidebarLogoGradientEnd = '#FFE240'; +const background = '#212124'; const appTheme = { mode: DARK, @@ -37,6 +38,7 @@ const appTheme = { activeItem, sidebarLogoGradientBegin, sidebarLogoGradientEnd, + background, }, size: { title: 18, From efdcf3c4f16b2488cb2e5debe04981c67146a1b7 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:35:04 -0300 Subject: [PATCH 09/55] hotfix: add padding horizontal in Layout --- app/components/layout.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/layout.js b/app/components/layout.js index 2dc0ba7..df52c53 100644 --- a/app/components/layout.js +++ b/app/components/layout.js @@ -8,7 +8,9 @@ const Layout = styled.div` flex-direction: column; width: 100%; height: 100vh; - background: ${props => props.theme.colors.secondary}; + background-color: ${props => props.theme.colors.background}; + padding-left: 35px; + padding-right: 45px; `; type Props = { From 25a5a4d801d3e79e576b7fc2e44f6ca66ea8d829 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:35:26 -0300 Subject: [PATCH 10/55] hotfix: remove padding from WalletSummary --- app/components/wallet-summary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/wallet-summary.js b/app/components/wallet-summary.js index bc216df..1d2fc4e 100644 --- a/app/components/wallet-summary.js +++ b/app/components/wallet-summary.js @@ -15,7 +15,7 @@ const Wrapper = styled.div` background-color: ${props => props.theme.colors.cardBackgroundColor}; border-radius: 5px; padding: 37px 45px; - margin: 20px; + margin-top: 20px; position: relative; `; From 127c3e17f888b1ae380b7ede790247f521d69b86 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:45:52 -0300 Subject: [PATCH 11/55] hotfix: remove outline from sidebar --- app/components/sidebar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 351e44d..5a6a453 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -34,6 +34,7 @@ const StyledLink = styled(Link)` padding: 15px 20px; display: flex; align-items: center; + outline: none; &:hover { color: ${props => props.theme.colors.sidebarItemActive}; From 6bfd33c58ee8e08fa03e1a0db7843b5ab5af0da3 Mon Sep 17 00:00:00 2001 From: George Lima Date: Tue, 11 Dec 2018 21:46:26 -0300 Subject: [PATCH 12/55] feature: add dashboard styles --- app/views/dashboard.js | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/app/views/dashboard.js b/app/views/dashboard.js index 89fdd6a..be2105a 100644 --- a/app/views/dashboard.js +++ b/app/views/dashboard.js @@ -1,9 +1,24 @@ // @flow -import React from 'react'; +import React, { Fragment } from 'react'; +import styled from 'styled-components'; import { WalletSummaryComponent } from '../components/wallet-summary'; import { withDaemonStatusCheck } from '../components/with-daemon-status-check'; +import { TextComponent } from '../components/text'; + +const Title = styled(TextComponent)` + font-size: 1.5em; + margin-top: 10px; + margin-bottom: 10px; +`; + +const Divider = styled.div` + width: 100%; + background-color: ${props => props.theme.colors.text}; + height: 1px; + opacity: 0.1; +`; type Props = { getSummary: () => void, @@ -31,13 +46,17 @@ export class Dashboard extends React.Component { {this.props.isLoading ? ( 'Loading' ) : ( - + + + <Divider /> + <WalletSummaryComponent + total={this.props.total} + shielded={this.props.shielded} + transparent={this.props.transparent} + dollarValue={this.props.dollarValue} + addresses={this.props.addresses} + /> + </Fragment> )} </div> ); From 16ad414a856ea2123c34052b7f523338b6214549 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 14:35:31 -0300 Subject: [PATCH 13/55] hotfix: test filename case --- .../actions/{WalletSummary.test.js => wallet-summary.test.js} | 0 __tests__/components/{Sidebar.test.js => sidebar.test.js} | 0 .../reducers/{WalletSummary.test.js => wallet-summary.test.js} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename __tests__/actions/{WalletSummary.test.js => wallet-summary.test.js} (100%) rename __tests__/components/{Sidebar.test.js => sidebar.test.js} (100%) rename __tests__/reducers/{WalletSummary.test.js => wallet-summary.test.js} (100%) diff --git a/__tests__/actions/WalletSummary.test.js b/__tests__/actions/wallet-summary.test.js similarity index 100% rename from __tests__/actions/WalletSummary.test.js rename to __tests__/actions/wallet-summary.test.js diff --git a/__tests__/components/Sidebar.test.js b/__tests__/components/sidebar.test.js similarity index 100% rename from __tests__/components/Sidebar.test.js rename to __tests__/components/sidebar.test.js diff --git a/__tests__/reducers/WalletSummary.test.js b/__tests__/reducers/wallet-summary.test.js similarity index 100% rename from __tests__/reducers/WalletSummary.test.js rename to __tests__/reducers/wallet-summary.test.js From fa3e734d7829cc2b04d988b547a892520b372e9a Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 15:59:13 -0300 Subject: [PATCH 14/55] lint: fix react/destructuring-assignment rule --- .eslintrc | 3 +- app/components/dropdown.js | 15 ++++--- app/components/with-daemon-status-check.js | 4 +- app/views/console.js | 6 ++- app/views/dashboard.js | 46 ++++++++-------------- 5 files changed, 33 insertions(+), 41 deletions(-) diff --git a/.eslintrc b/.eslintrc index 390923c..afeb5e9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -43,7 +43,6 @@ "ignoreTrailingComments": true } ], - "consistent-return": 0, - "react/destructuring-assignment": 0 + "consistent-return": 0 } } diff --git a/app/components/dropdown.js b/app/components/dropdown.js index 6d6d42f..8772135 100644 --- a/app/components/dropdown.js +++ b/app/components/dropdown.js @@ -68,22 +68,25 @@ export class DropdownComponent extends Component<Props, State> { }; render() { + const { isOpen } = this.state; + const { label, options, renderTrigger } = this.props; + return ( <PopoverWithStyle - isOpen={this.state.isOpen} + isOpen={isOpen} preferPlace='below' enterExitTransitionDurationMs={0} body={[ <ClickOutside onClickOutside={() => this.setState(() => ({ isOpen: false }))}> <MenuWrapper> - {this.props.label && ( + {label && ( <MenuItem disabled> - <TextComponent value={this.props.label} isBold /> + <TextComponent value={label} isBold /> </MenuItem> )} - {this.props.options.map(({ label, onClick }) => ( + {options.map(({ label: optionLabel, onClick }) => ( <MenuItem onClick={onClick}> - <TextComponent value={label} /> + <TextComponent value={optionLabel} /> </MenuItem> ))} </MenuWrapper> @@ -91,7 +94,7 @@ export class DropdownComponent extends Component<Props, State> { ]} tipSize={7} > - {this.props.renderTrigger(() => this.setState(state => ({ isOpen: !state.isOpen })), this.state.isOpen)} + {renderTrigger(() => this.setState(state => ({ isOpen: !state.isOpen })), isOpen)} </PopoverWithStyle> ); } diff --git a/app/components/with-daemon-status-check.js b/app/components/with-daemon-status-check.js index 7275db3..b2d5735 100644 --- a/app/components/with-daemon-status-check.js +++ b/app/components/with-daemon-status-check.js @@ -44,7 +44,9 @@ export const withDaemonStatusCheck = <PassedProps: {}>( }; render() { - if (this.state.isRunning) { + const { isRunning } = this.state; + + if (isRunning) { return <WrappedComponent {...this.props} {...this.state} />; } diff --git a/app/views/console.js b/app/views/console.js index 7b53a4d..bf0d67b 100644 --- a/app/views/console.js +++ b/app/views/console.js @@ -24,10 +24,12 @@ export class ConsoleView extends Component<Props, State> { } render() { + const { log } = this.state; + return ( <div className='dashboard'> - {this.state.log - && this.state.log.split('\n').map(item => ( + {log + && log.split('\n').map(item => ( <Fragment key={`${item.slice(0, 10)}`}> {item} <br /> diff --git a/app/views/dashboard.js b/app/views/dashboard.js index be2105a..39ce162 100644 --- a/app/views/dashboard.js +++ b/app/views/dashboard.js @@ -1,24 +1,9 @@ // @flow -import React, { Fragment } from 'react'; -import styled from 'styled-components'; +import React from 'react'; import { WalletSummaryComponent } from '../components/wallet-summary'; import { withDaemonStatusCheck } from '../components/with-daemon-status-check'; -import { TextComponent } from '../components/text'; - -const Title = styled(TextComponent)` - font-size: 1.5em; - margin-top: 10px; - margin-bottom: 10px; -`; - -const Divider = styled.div` - width: 100%; - background-color: ${props => props.theme.colors.text}; - height: 1px; - opacity: 0.1; -`; type Props = { getSummary: () => void, @@ -33,30 +18,31 @@ type Props = { export class Dashboard extends React.Component<Props> { componentDidMount() { + /* eslint-disable-next-line */ this.props.getSummary(); } render() { - if (this.props.error) { - return this.props.error; + const { + error, isLoading, total, shielded, transparent, dollarValue, addresses, + } = this.props; + + if (error) { + return error; } return ( <div className='dashboard'> - {this.props.isLoading ? ( + {isLoading ? ( 'Loading' ) : ( - <Fragment> - <Title value='Dashboard' /> - <Divider /> - <WalletSummaryComponent - total={this.props.total} - shielded={this.props.shielded} - transparent={this.props.transparent} - dollarValue={this.props.dollarValue} - addresses={this.props.addresses} - /> - </Fragment> + <WalletSummaryComponent + total={total} + shielded={shielded} + transparent={transparent} + dollarValue={dollarValue} + addresses={addresses} + /> )} </div> ); From 496d54c3e8f226a2eb290ab19b6054e0abb3145c Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:00:15 -0300 Subject: [PATCH 15/55] hotfix: remove react-icons lib --- package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/package.json b/package.json index 97adb48..61c1325 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "react": "^16.6.0", "react-click-outside": "tj/react-click-outside", "react-dom": "^16.6.0", - "react-icons": "^3.2.2", "react-popover": "^0.5.10", "react-redux": "^5.0.7", "react-router-dom": "^4.2.2", diff --git a/yarn.lock b/yarn.lock index f113ea4..853000c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11653,11 +11653,6 @@ react-hot-loader@4.3.6: react-lifecycles-compat "^3.0.4" shallowequal "^1.0.2" -react-icons@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.2.2.tgz#7b1dc795004a119a35175e62ecef91e5c9c90962" - integrity sha512-0DiXTcECUNoMvze2zcFwYYrHAFgQ2rBM0+HIHfaECKRfE1NZYwfuUSbwyg9s1uVPuy7o/KqQKSwBGMWQFvdrhQ== - react-imported-component@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/react-imported-component/-/react-imported-component-5.2.3.tgz#7771b72d2814c5ba2839489e2cdc5d033637c045" From 521896156799b1bfbcf5bc91f69457443d50d3c1 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:01:06 -0300 Subject: [PATCH 16/55] feature: add sidebar assets --- app/assets/images/console_icon.svg | 1 + app/assets/images/console_icon_active.svg | 1 + app/assets/images/dashboard_icon.svg | 1 + app/assets/images/dashboard_icon_active.svg | 1 + app/assets/images/menu_icon.svg | 7 +++++++ app/assets/images/receive_icon.svg | 1 + app/assets/images/receive_icon_active.svg | 1 + app/assets/images/send_icon.svg | 1 + app/assets/images/send_icon_active.svg | 1 + app/assets/images/settings_icon.svg | 1 + app/assets/images/settings_icon_active.svg | 1 + app/assets/images/transactions_icon.svg | 1 + app/assets/images/transactions_icon_active.svg | 1 + app/assets/images/zcash-icon.png | Bin 0 -> 24849 bytes 14 files changed, 19 insertions(+) create mode 100644 app/assets/images/console_icon.svg create mode 100644 app/assets/images/console_icon_active.svg create mode 100644 app/assets/images/dashboard_icon.svg create mode 100644 app/assets/images/dashboard_icon_active.svg create mode 100644 app/assets/images/menu_icon.svg create mode 100644 app/assets/images/receive_icon.svg create mode 100644 app/assets/images/receive_icon_active.svg create mode 100644 app/assets/images/send_icon.svg create mode 100644 app/assets/images/send_icon_active.svg create mode 100644 app/assets/images/settings_icon.svg create mode 100644 app/assets/images/settings_icon_active.svg create mode 100644 app/assets/images/transactions_icon.svg create mode 100644 app/assets/images/transactions_icon_active.svg create mode 100644 app/assets/images/zcash-icon.png diff --git a/app/assets/images/console_icon.svg b/app/assets/images/console_icon.svg new file mode 100644 index 0000000..b079435 --- /dev/null +++ b/app/assets/images/console_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/console_icon_active.svg b/app/assets/images/console_icon_active.svg new file mode 100644 index 0000000..66106e9 --- /dev/null +++ b/app/assets/images/console_icon_active.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/dashboard_icon.svg b/app/assets/images/dashboard_icon.svg new file mode 100644 index 0000000..12bd57c --- /dev/null +++ b/app/assets/images/dashboard_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/dashboard_icon_active.svg b/app/assets/images/dashboard_icon_active.svg new file mode 100644 index 0000000..acb3945 --- /dev/null +++ b/app/assets/images/dashboard_icon_active.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/menu_icon.svg b/app/assets/images/menu_icon.svg new file mode 100644 index 0000000..2dbc39f --- /dev/null +++ b/app/assets/images/menu_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/receive_icon.svg b/app/assets/images/receive_icon.svg new file mode 100644 index 0000000..5576d8f --- /dev/null +++ b/app/assets/images/receive_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/receive_icon_active.svg b/app/assets/images/receive_icon_active.svg new file mode 100644 index 0000000..4ec6f51 --- /dev/null +++ b/app/assets/images/receive_icon_active.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/send_icon.svg b/app/assets/images/send_icon.svg new file mode 100644 index 0000000..1c0a162 --- /dev/null +++ b/app/assets/images/send_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/send_icon_active.svg b/app/assets/images/send_icon_active.svg new file mode 100644 index 0000000..58a759d --- /dev/null +++ b/app/assets/images/send_icon_active.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/settings_icon.svg b/app/assets/images/settings_icon.svg new file mode 100644 index 0000000..0503b15 --- /dev/null +++ b/app/assets/images/settings_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/settings_icon_active.svg b/app/assets/images/settings_icon_active.svg new file mode 100644 index 0000000..88802f3 --- /dev/null +++ b/app/assets/images/settings_icon_active.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/transactions_icon.svg b/app/assets/images/transactions_icon.svg new file mode 100644 index 0000000..becaac7 --- /dev/null +++ b/app/assets/images/transactions_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/transactions_icon_active.svg b/app/assets/images/transactions_icon_active.svg new file mode 100644 index 0000000..0913d8b --- /dev/null +++ b/app/assets/images/transactions_icon_active.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/zcash-icon.png b/app/assets/images/zcash-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..060bd0030c3e31c4da52d88817db34f81c09577f GIT binary patch literal 24849 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 MV@spT)2<2s1Elg|C;$Ke literal 0 HcmV?d00001 From 1cc3c103d02f02b5619d92fba21591b26c8dc27e Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:01:30 -0300 Subject: [PATCH 17/55] feature: add default font style props --- app/theme.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/theme.js b/app/theme.js index cf7a681..10e3bd9 100644 --- a/app/theme.js +++ b/app/theme.js @@ -21,6 +21,15 @@ const background = '#212124'; const appTheme = { mode: DARK, fontFamily: 'PT Sans', + fontWeight: { + bold: 700, + default: 400, + }, + fontSize: { + title: 1.25, + text: 0.9375, + zecValueBase: 1.125, + }, colors: { primary: theme('mode', { light: lightOne, @@ -36,14 +45,15 @@ const appTheme = { cardBackgroundColor, text, activeItem, + inactiveItem: brandTwo, sidebarLogoGradientBegin, sidebarLogoGradientEnd, background, }, - size: { - title: 18, - paragraph: 12, - }, + sidebarWidth: '200px', + headerHeight: '60px', + layoutPaddingLeft: '35px', + layoutPaddingRight: '45px', }; export const GlobalStyle = createGlobalStyle` From c812b33fabde1cf9b10dc7e55a7f70d18c6fd537 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:03:35 -0300 Subject: [PATCH 18/55] hotfix: update usage of style props in components --- app/components/button.js | 10 ++++++++-- app/components/layout.js | 9 ++++----- app/components/text.js | 8 ++++---- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/app/components/button.js b/app/components/button.js index 650bc7b..c77b368 100644 --- a/app/components/button.js +++ b/app/components/button.js @@ -13,8 +13,14 @@ const defaultStyles = ` // $FlowFixMe props => props.theme.fontFamily }; - font-weight: bold; - font-size: 0.9em; + font-weight: ${ + // $FlowFixMe + props => props.theme.fontWeight.bold +}; + font-size: ${ + // $FlowFixMe + props => `${props.theme.fontSize.text}em` +}; cursor: pointer; outline: none; min-width: 100px; diff --git a/app/components/layout.js b/app/components/layout.js index df52c53..47c0b28 100644 --- a/app/components/layout.js +++ b/app/components/layout.js @@ -1,16 +1,15 @@ // @flow - import React from 'react'; import styled from 'styled-components'; const Layout = styled.div` display: flex; flex-direction: column; - width: 100%; - height: 100vh; + width: ${props => `calc(100% - ${props.theme.sidebarWidth})`}; + height: ${props => `calc(100vh - ${props.theme.headerHeight})`}; background-color: ${props => props.theme.colors.background}; - padding-left: 35px; - padding-right: 45px; + padding-left: ${props => props.theme.layoutPaddingLeft}; + padding-right: ${props => props.theme.layoutPaddingRight}; `; type Props = { diff --git a/app/components/text.js b/app/components/text.js index 645c553..96bd929 100644 --- a/app/components/text.js +++ b/app/components/text.js @@ -10,7 +10,7 @@ const Text = styled.p` color: ${props => props.color || props.theme.colors.text}; margin: 0; padding: 0; - font-weight: ${props => (props.isBold ? 'bold' : '400')}; + font-weight: ${props => (props.isBold ? props.theme.fontWeight.bold : props.theme.fontWeight.default)}; `; type Props = { @@ -18,13 +18,13 @@ type Props = { isBold?: boolean, color?: string, className?: string, - size?: string, + size?: string | number, }; export const TextComponent = ({ value, isBold, color, className, size, }: Props) => ( - <Text className={className} isBold={isBold} color={color} size={size}> + <Text className={className} isBold={isBold} color={color} size={`${String(size)}em`}> {value} </Text> ); @@ -33,5 +33,5 @@ TextComponent.defaultProps = { className: '', isBold: false, color: theme.colors.text, - size: '1em', + size: `${theme.fontSize.text}em`, }; From 922c62eb385604e3b26f8d6f9bbd0b23282428ae Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:04:04 -0300 Subject: [PATCH 19/55] feature: update icons in sidebar constants --- app/constants/sidebar.js | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/app/constants/sidebar.js b/app/constants/sidebar.js index 9a3e8e1..ea23fb1 100644 --- a/app/constants/sidebar.js +++ b/app/constants/sidebar.js @@ -1,9 +1,16 @@ // @flow -import React from 'react'; -import { FaThLarge, FaCode } from 'react-icons/fa'; -import { IoIosSend } from 'react-icons/io'; -import { TiDownload } from 'react-icons/ti'; -import { MdSettings, MdTransform } from 'react-icons/md'; +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 { DASHBOARD_ROUTE, @@ -18,31 +25,31 @@ export const MENU_OPTIONS = [ { label: 'Dashboard', route: DASHBOARD_ROUTE, - icon: <FaThLarge />, - }, - { - label: 'Console', - route: CONSOLE_ROUTE, - icon: <FaCode />, + icon: (isActive: boolean) => (isActive ? DashboardIconActive : DashboardIcon), }, { label: 'Send', route: SEND_ROUTE, - icon: <IoIosSend />, + icon: (isActive: boolean) => (isActive ? SendIconActive : SendIcon), }, { label: 'Receive', route: RECEIVE_ROUTE, - icon: <TiDownload />, + icon: (isActive: boolean) => (isActive ? ReceiveIconActive : ReceiveIcon), }, { label: 'Transactions', route: TRANSACTIONS_ROUTE, - icon: <MdTransform />, + icon: (isActive: boolean) => (isActive ? TransactionsIconActive : TransactionsIcon), }, { label: 'Settings', route: SETTINGS_ROUTE, - icon: <MdSettings />, + icon: (isActive: boolean) => (isActive ? SettingsIconActive : SettingsIcon), + }, + { + label: 'Console', + route: CONSOLE_ROUTE, + icon: (isActive: boolean) => (isActive ? ConsoleIconActive : ConsoleIcon), }, ]; From c0ace32d0b4af641a4894aa1abd3da8dfaea7ac3 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:04:44 -0300 Subject: [PATCH 20/55] feature: update fontSizes and remove react-icons in wallet summary --- app/components/wallet-summary.js | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/app/components/wallet-summary.js b/app/components/wallet-summary.js index 1d2fc4e..fb89b13 100644 --- a/app/components/wallet-summary.js +++ b/app/components/wallet-summary.js @@ -1,11 +1,11 @@ // @flow import React from 'react'; import styled from 'styled-components'; -import { IoIosMore } from 'react-icons/io'; import { TextComponent } from './text'; import { RowComponent } from './row'; import { DropdownComponent } from './dropdown'; +import MenuIcon from '../assets/images/menu_icon.svg'; import theme from '../theme'; @@ -21,7 +21,7 @@ const Wrapper = styled.div` const AllAddresses = styled(TextComponent)` margin-bottom: 2.5px; - font-size: 0.625em; + font-size: ${props => `${props.theme.fontSize.text}em`}; `; const ValueBox = styled.div` @@ -48,9 +48,10 @@ const SeeMoreButton = styled.button` 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.text)}; + border-color: ${props => (props.isOpen ? props.theme.colors.activeItem : props.theme.colors.inactiveItem)}; background-color: transparent; padding: 5px; cursor: pointer; @@ -63,6 +64,11 @@ const SeeMoreButton = styled.button` } `; +const SeeMoreIcon = styled.img` + width: 25px; + height: 25px; +`; + type Props = { total: number, shielded: number, @@ -81,26 +87,26 @@ export const WalletSummaryComponent = ({ label='All Addresses' renderTrigger={(toggleVisibility, isOpen) => ( <SeeMoreButton onClick={toggleVisibility} isOpen={isOpen}> - <IoIosMore color={theme.colors.text} size={15} /> + <SeeMoreIcon src={MenuIcon} alt='Menu Icon' /> </SeeMoreButton> )} options={addresses.map(addr => ({ label: addr, onClick: x => x }))} /> <AllAddresses value='ALL ADDRESSES' isBold /> <ValueBox> - <TextComponent size='2.625em' value={`ZEC ${formatNumber(total)}`} isBold /> - <USDValue value={`USD $${formatNumber(total * dollarValue)}`} size='1.750em' /> + <TextComponent size={theme.fontSize.zecValueBase * 2.5} value={`ZEC ${formatNumber(total)}`} isBold /> + <USDValue value={`USD $${formatNumber(total * dollarValue)}`} size={theme.fontSize.zecValueBase * 2} /> </ValueBox> <RowComponent> <ValueBox> - <ShieldedValue value='● SHIELDED' isBold size='0.625em' /> - <TextComponent value={`ZEC ${formatNumber(shielded)}`} isBold size='1.125em' /> - <USDValue value={`USD $${formatNumber(shielded * dollarValue)}`} size='0.750em' /> + <ShieldedValue value='● SHIELDED' isBold /> + <TextComponent value={`ZEC ${formatNumber(shielded)}`} isBold size={theme.fontSize.zecValueBase} /> + <USDValue value={`USD $${formatNumber(shielded * dollarValue)}`} /> </ValueBox> <ValueBox> - <Label value='● TRANSPARENT' isBold size='0.625em' /> - <TextComponent value={`ZEC ${formatNumber(transparent)}`} isBold size='1.125em' /> - <USDValue value={`USD $${formatNumber(transparent * dollarValue)}`} size='0.750em' /> + <Label value='● TRANSPARENT' isBold /> + <TextComponent value={`ZEC ${formatNumber(transparent)}`} isBold size={theme.fontSize.zecValueBase} /> + <USDValue value={`USD $${formatNumber(transparent * dollarValue)}`} /> </ValueBox> </RowComponent> </Wrapper> From 9228e053beacbd5580c9fe44bb08fc8c95f07d22 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:05:19 -0300 Subject: [PATCH 21/55] hotfix: sidebar icons --- app/components/sidebar.js | 94 +++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 38 deletions(-) diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 5a6a453..931037a 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -1,50 +1,50 @@ // @flow -import React, { type Element } from 'react'; +import React, { Component } from 'react'; import styled from 'styled-components'; import { Link, type Location } from 'react-router-dom'; import { MENU_OPTIONS } from '../constants/sidebar'; -import { ZCashLogo } from './zcash-logo'; const Wrapper = styled.div` display: flex; flex-direction: column; - width: 200px; - height: 100vh; + width: ${props => props.theme.sidebarWidth}; + height: ${props => `calc(100vh - ${props.theme.headerHeight})`}; font-family: ${props => props.theme.fontFamily} - background-color: ${props => props.theme.colors.sidebarBg}; -`; - -const LogoWrapper = styled.div` - height: 60px; - width: 200px; - margin-bottom: 20px; - background-image: linear-gradient( - to right, - ${props => props.theme.colors.sidebarLogoGradientBegin}, - ${props => props.theme.colors.sidebarLogoGradientEnd} - ); + background-color: ${props => props.theme.colors.sidebarBg}; + padding-top: 15px; `; const StyledLink = styled(Link)` color: ${props => (props.isActive ? props.theme.colors.sidebarItemActive : props.theme.colors.sidebarItem)}; - font-size: 0.9em; + font-size: ${props => `${props.theme.fontSize.text}em`}; text-decoration: none; - font-weight: ${props => (props.isActive ? 700 : 400)}; + font-weight: ${props => (props.isActive ? props.theme.fontWeight.bold : props.theme.fontWeight.default)}; padding: 15px 20px; display: flex; align-items: center; outline: none; + border-right: ${props => (props.isActive ? `1px solid ${props.theme.colors.sidebarItemActive}` : 'none')}; &:hover { color: ${props => props.theme.colors.sidebarItemActive}; + + svg { + color: ${props => props.theme.colors.sidebarItemActive}; + } } `; +const Icon = styled.img` + width: 20px; + height: 20px; + margin-right: 15px; +`; + type MenuItem = { route: string, label: string, - icon: Element<*>, + icon: (isActive: boolean) => string, }; type Props = { @@ -52,23 +52,41 @@ type Props = { location: Location, }; -export const SidebarComponent = ({ options, location }: Props) => ( - <Wrapper> - <LogoWrapper> - <ZCashLogo /> - </LogoWrapper> - {(options || []).map(item => ( - <StyledLink isActive={location.pathname === item.route} key={item.route} to={item.route}> - {React.cloneElement(item.icon, { - style: { marginRight: '15px' }, - size: 20, - })} - {item.label} - </StyledLink> - ))} - </Wrapper> -); - -SidebarComponent.defaultProps = { - options: MENU_OPTIONS, +type State = { + currentHovered: string | null, }; + +export class SidebarComponent extends Component<Props, State> { + static defaultProps = { + options: MENU_OPTIONS, + }; + + state = { + currentHovered: null, + }; + + render() { + const { options, location } = this.props; + const { currentHovered } = this.state; + + return ( + <Wrapper> + {(options || []).map((item) => { + const isActive = location.pathname === item.route; + return ( + <StyledLink + onMouseEnter={() => this.setState(() => ({ currentHovered: item.route }))} + onMouseLeave={() => this.setState(() => ({ currentHovered: null }))} + isActive={isActive} + key={item.route} + to={item.route} + > + <Icon src={item.icon(currentHovered === item.route || isActive)} alt={`Sidebar Icon ${item.route}`} /> + {item.label} + </StyledLink> + ); + })} + </Wrapper> + ); + } +} From 70e1385568dff63566c0a922c5cf604c1d576b7a Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:05:33 -0300 Subject: [PATCH 22/55] feature: add Header component --- app/components/header.js | 67 ++++++++++++++++++++++++++++++++++++++++ app/router/router.js | 44 ++++++++++++++++---------- 2 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 app/components/header.js diff --git a/app/components/header.js b/app/components/header.js new file mode 100644 index 0000000..55d211b --- /dev/null +++ b/app/components/header.js @@ -0,0 +1,67 @@ +// @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; +`; + +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> +); diff --git a/app/router/router.js b/app/router/router.js index c50d910..018fe0e 100644 --- a/app/router/router.js +++ b/app/router/router.js @@ -13,32 +13,42 @@ import { SettingsView } from '../views/settings'; import { NotFoundView } from '../views/not-found'; import { ConsoleView } from '../views/console'; import { LayoutComponent } from '../components/layout'; +import { HeaderComponent } from '../components/header'; import { DASHBOARD_ROUTE, SEND_ROUTE, RECEIVE_ROUTE, SETTINGS_ROUTE, CONSOLE_ROUTE, } from '../constants/routes'; -const Wrapper = styled.div` +const FullWrapper = styled.div` + display: flex; + flex-direction: column; + width: 100%; +`; + +const ContentWrapper = styled.div` display: flex; flex-direction: row; width: 100vw; `; export const RouterComponent = ({ location }: { location: Location }) => ( - <Wrapper> - <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> - </Wrapper> + <FullWrapper> + <HeaderComponent title='Dashboard' /> + <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> ); From 51048c79d958eac12ee6ab18b73368e706d02653 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:16:42 -0300 Subject: [PATCH 23/55] hotfix: increase layout padding left --- app/theme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/theme.js b/app/theme.js index 10e3bd9..57be468 100644 --- a/app/theme.js +++ b/app/theme.js @@ -52,7 +52,7 @@ const appTheme = { }, sidebarWidth: '200px', headerHeight: '60px', - layoutPaddingLeft: '35px', + layoutPaddingLeft: '50px', layoutPaddingRight: '45px', }; From 886708c0a4b875c62b8c8c839360f843280ded5f Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:23:31 -0300 Subject: [PATCH 24/55] hotfix: update zcash-logo path --- app/components/zcash-logo.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/components/zcash-logo.js b/app/components/zcash-logo.js index 7dad32b..628d9d9 100644 --- a/app/components/zcash-logo.js +++ b/app/components/zcash-logo.js @@ -2,7 +2,14 @@ import React from 'react'; export const ZCashLogo = () => ( - <svg xmlns='http://www.w3.org/2000/svg' viewBox='-575 150 1800 1800'> - <path d='M278.497 500.175h138.006v69.357h-84.927c1.416 19.818 2.12 38.221 3.539 58.036h-71.482v-57.325h-84.927c0-22.651-2.829-45.294 1.415-66.525 2.125-11.326 14.862-21.238 22.649-31.143 26.89-31.849 53.788-62.987 81.388-94.833 10.614-12.03 21.232-23.353 33.262-36.803H185.077v-69.356h78.556v-58.032h68.648v56.617h85.637c0 23.353 2.83 46.002-1.415 67.234-2.122 11.323-14.862 21.229-23.356 31.14-26.89 31.849-53.785 62.984-81.388 94.835-10.616 12.738-21.23 24.061-33.262 36.798z' /> + <svg xmlns='http://www.w3.org/2000/svg' viewBox='-64 -5 150 150'> + <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> ); From febbb087470a0d6bb7320eab1f439fe92ad071db Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:48:08 -0300 Subject: [PATCH 25/55] hotfix: fix sidebar on hover --- app/components/sidebar.js | 68 ++++++++++++++------------------------- app/theme.js | 4 +++ 2 files changed, 28 insertions(+), 44 deletions(-) diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 931037a..2ad9221 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -1,6 +1,6 @@ // @flow -import React, { Component } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Link, type Location } from 'react-router-dom'; import { MENU_OPTIONS } from '../constants/sidebar'; @@ -20,18 +20,19 @@ const StyledLink = styled(Link)` font-size: ${props => `${props.theme.fontSize.text}em`}; text-decoration: none; font-weight: ${props => (props.isActive ? props.theme.fontWeight.bold : props.theme.fontWeight.default)}; - padding: 15px 20px; + 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: ${props => props.theme.colors.sidebarItemActive}; - - svg { - color: ${props => props.theme.colors.sidebarItemActive}; - } + 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}; } `; @@ -52,41 +53,20 @@ type Props = { location: Location, }; -type State = { - currentHovered: string | null, +export const SidebarComponent = ({ options, location }: Props) => ( + <Wrapper> + {(options || []).map((item) => { + const isActive = location.pathname === item.route; + return ( + <StyledLink isActive={isActive} key={item.route} to={item.route}> + <Icon src={item.icon(isActive)} alt={`Sidebar Icon ${item.route}`} /> + {item.label} + </StyledLink> + ); + })} + </Wrapper> +); + +SidebarComponent.defaultProps = { + options: MENU_OPTIONS, }; - -export class SidebarComponent extends Component<Props, State> { - static defaultProps = { - options: MENU_OPTIONS, - }; - - state = { - currentHovered: null, - }; - - render() { - const { options, location } = this.props; - const { currentHovered } = this.state; - - return ( - <Wrapper> - {(options || []).map((item) => { - const isActive = location.pathname === item.route; - return ( - <StyledLink - onMouseEnter={() => this.setState(() => ({ currentHovered: item.route }))} - onMouseLeave={() => this.setState(() => ({ currentHovered: null }))} - isActive={isActive} - key={item.route} - to={item.route} - > - <Icon src={item.icon(currentHovered === item.route || isActive)} alt={`Sidebar Icon ${item.route}`} /> - {item.label} - </StyledLink> - ); - })} - </Wrapper> - ); - } -} diff --git a/app/theme.js b/app/theme.js index 57be468..1275738 100644 --- a/app/theme.js +++ b/app/theme.js @@ -16,6 +16,8 @@ const text = '#FFF'; const cardBackgroundColor = '#000'; const sidebarLogoGradientBegin = '#F4B728'; const sidebarLogoGradientEnd = '#FFE240'; +const sidebarHoveredItem = '#1C1C1C'; +const sidebarHoveredItemLabel = '#969696'; const background = '#212124'; const appTheme = { @@ -42,6 +44,8 @@ const appTheme = { sidebarBg: brandOne, sidebarItem: brandTwo, sidebarItemActive: activeItem, + sidebarHoveredItem, + sidebarHoveredItemLabel, cardBackgroundColor, text, activeItem, From 7f8d81aa20f994b01368a5026465819d8a766fd7 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:48:29 -0300 Subject: [PATCH 26/55] hotfix: decrease zcash-logo size --- app/components/zcash-logo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/zcash-logo.js b/app/components/zcash-logo.js index 628d9d9..d41b5d7 100644 --- a/app/components/zcash-logo.js +++ b/app/components/zcash-logo.js @@ -2,7 +2,7 @@ import React from 'react'; export const ZCashLogo = () => ( - <svg xmlns='http://www.w3.org/2000/svg' viewBox='-64 -5 150 150'> + <svg xmlns='http://www.w3.org/2000/svg' viewBox='-75 -10 175 175'> <defs> <style>{'.a{ fill:#040508; }'}</style> </defs> From 8595e4175a58022f9525f4f2ce56b702d44f30f4 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 16:54:34 -0300 Subject: [PATCH 27/55] hotfix: get header title from location --- app/components/header.js | 1 + app/router/router.js | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/components/header.js b/app/components/header.js index 55d211b..6b67bd1 100644 --- a/app/components/header.js +++ b/app/components/header.js @@ -41,6 +41,7 @@ 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` diff --git a/app/router/router.js b/app/router/router.js index 018fe0e..0534ffd 100644 --- a/app/router/router.js +++ b/app/router/router.js @@ -31,9 +31,15 @@ const ContentWrapper = styled.div` width: 100vw; `; +const getTitle = (path: string) => { + if (path === '/') return 'Dashboard'; + + return path.replace('/', ''); +}; + export const RouterComponent = ({ location }: { location: Location }) => ( <FullWrapper> - <HeaderComponent title='Dashboard' /> + <HeaderComponent title={getTitle(location.pathname)} /> <ContentWrapper> <SidebarContainer location={location} /> <ScrollTopComponent> From 7660174a06df5abbc1e00326a8758fcb33032ec2 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:10:30 -0300 Subject: [PATCH 28/55] hotfix: add normalize css in docz header --- doczrc.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doczrc.js b/doczrc.js index 6a61d60..8b48c23 100644 --- a/doczrc.js +++ b/doczrc.js @@ -12,6 +12,10 @@ export default { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=PT+Sans:400,700', }, + { + rel: 'stylesheet', + href: 'https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css', + }, ], }, }, From 095876e9d203c541d81b250c4e97351d9028373b Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:11:03 -0300 Subject: [PATCH 29/55] feature: add date-fns lib --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 61c1325..1c46678 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@babel/register": "^7.0.0", "autoprefixer": "^9.3.1", "connected-react-router": "^5.0.1", + "date-fns": "^1.30.1", "electron-store": "^2.0.0", "eres": "^1.0.1", "got": "^9.3.2", diff --git a/yarn.lock b/yarn.lock index 853000c..bb17efb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4080,6 +4080,11 @@ date-fns@^1.23.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" 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: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" From fa3c353f129a94c867801cfd791f98a461295977 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:11:28 -0300 Subject: [PATCH 30/55] feature: add transaction type colors --- app/theme.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/theme.js b/app/theme.js index 1275738..bfd72ab 100644 --- a/app/theme.js +++ b/app/theme.js @@ -19,6 +19,8 @@ const sidebarLogoGradientEnd = '#FFE240'; const sidebarHoveredItem = '#1C1C1C'; const sidebarHoveredItemLabel = '#969696'; const background = '#212124'; +const transactionSent = '#FF6C6C'; +const transactionReceived = '#6AEAC0'; const appTheme = { mode: DARK, @@ -29,7 +31,7 @@ const appTheme = { }, fontSize: { title: 1.25, - text: 0.9375, + text: 0.84375, zecValueBase: 1.125, }, colors: { @@ -53,6 +55,8 @@ const appTheme = { sidebarLogoGradientBegin, sidebarLogoGradientEnd, background, + transactionSent, + transactionReceived, }, sidebarWidth: '200px', headerHeight: '60px', From 055159f8f0471c06b90f57c25a238c008a8f2be3 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:11:43 -0300 Subject: [PATCH 31/55] chore: add transaction_icons --- app/assets/images/transaction_received_icon.svg | 1 + app/assets/images/transaction_sent_icon.svg | 1 + 2 files changed, 2 insertions(+) create mode 100644 app/assets/images/transaction_received_icon.svg create mode 100644 app/assets/images/transaction_sent_icon.svg diff --git a/app/assets/images/transaction_received_icon.svg b/app/assets/images/transaction_received_icon.svg new file mode 100644 index 0000000..e55a426 --- /dev/null +++ b/app/assets/images/transaction_received_icon.svg @@ -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> \ No newline at end of file diff --git a/app/assets/images/transaction_sent_icon.svg b/app/assets/images/transaction_sent_icon.svg new file mode 100644 index 0000000..990a3a5 --- /dev/null +++ b/app/assets/images/transaction_sent_icon.svg @@ -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> \ No newline at end of file From f30ec793e5b2ff559d6a4c996fa48fecfeda9e28 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:13:13 -0300 Subject: [PATCH 32/55] hotfix: create format number helper --- app/components/wallet-summary.js | 8 ++++---- app/components/wallet-summary.mdx | 8 +++++++- app/utils/formatNumber.js | 3 +++ 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 app/utils/formatNumber.js diff --git a/app/components/wallet-summary.js b/app/components/wallet-summary.js index fb89b13..f3a5ddf 100644 --- a/app/components/wallet-summary.js +++ b/app/components/wallet-summary.js @@ -7,6 +7,8 @@ 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` @@ -77,8 +79,6 @@ type Props = { addresses: string[], }; -const formatNumber = number => number.toLocaleString('de-DE'); - export const WalletSummaryComponent = ({ total, shielded, transparent, dollarValue, addresses, }: Props) => ( @@ -99,12 +99,12 @@ export const WalletSummaryComponent = ({ </ValueBox> <RowComponent> <ValueBox> - <ShieldedValue value='● SHIELDED' isBold /> + <ShieldedValue value='● SHIELDED' isBold size={theme.fontSize.text * 0.8} /> <TextComponent value={`ZEC ${formatNumber(shielded)}`} isBold size={theme.fontSize.zecValueBase} /> <USDValue value={`USD $${formatNumber(shielded * dollarValue)}`} /> </ValueBox> <ValueBox> - <Label value='● TRANSPARENT' isBold /> + <Label value='● TRANSPARENT' isBold size={theme.fontSize.text * 0.8} /> <TextComponent value={`ZEC ${formatNumber(transparent)}`} isBold size={theme.fontSize.zecValueBase} /> <USDValue value={`USD $${formatNumber(transparent * dollarValue)}`} /> </ValueBox> diff --git a/app/components/wallet-summary.mdx b/app/components/wallet-summary.mdx index d7d75b6..abe53de 100644 --- a/app/components/wallet-summary.mdx +++ b/app/components/wallet-summary.mdx @@ -17,7 +17,13 @@ import { DoczWrapper } from '../theme.js' <DoczWrapper> {() => ( <div style={{ width: '700px' }}> - <WalletSummaryComponent total={5000} shielded={2500} transparent={2500} dollarValue={56} /> + <WalletSummaryComponent + total={5000} + shielded={2500} + transparent={2500} + dollarValue={56} + addresses={['12345678asdaas9', '98asdasd765asd4sad321']} + /> </div> )} </DoczWrapper> diff --git a/app/utils/formatNumber.js b/app/utils/formatNumber.js new file mode 100644 index 0000000..bd85119 --- /dev/null +++ b/app/utils/formatNumber.js @@ -0,0 +1,3 @@ +// @flow + +export default (number: number, append?: string = '') => `${append}${number.toLocaleString('de-DE')}`; From 212e2b3b254b9e834e6945d84aed21660cffd87b Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:13:39 -0300 Subject: [PATCH 33/55] feature: add column component --- app/components/column.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/components/column.js diff --git a/app/components/column.js b/app/components/column.js new file mode 100644 index 0000000..ba58e4d --- /dev/null +++ b/app/components/column.js @@ -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: '', +}; From c0be9d5d8cf2dccf691c43bfd638dd1b6ba9d45f Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:14:12 -0300 Subject: [PATCH 34/55] hotfix: add classname in row component --- app/components/row.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/components/row.js b/app/components/row.js index 9fb68f3..83a483c 100644 --- a/app/components/row.js +++ b/app/components/row.js @@ -14,6 +14,7 @@ const Flex = styled.div` type Props = { alignItems?: string, justifyContent?: string, + className?: string, children: Node, }; @@ -24,4 +25,5 @@ export const RowComponent = ({ children, ...props }: Props) => ( RowComponent.defaultProps = { alignItems: 'flex-start', justifyContent: 'flex-start', + className: '', }; From 514b6835729953d28145949005dc7b0d70552079 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:14:41 -0300 Subject: [PATCH 35/55] hotfix: add align prop in text and fix fontSize --- app/components/text.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/components/text.js b/app/components/text.js index 96bd929..c8dac60 100644 --- a/app/components/text.js +++ b/app/components/text.js @@ -11,6 +11,7 @@ const Text = styled.p` margin: 0; padding: 0; font-weight: ${props => (props.isBold ? props.theme.fontWeight.bold : props.theme.fontWeight.default)}; + text-align: ${props => props.align}; `; type Props = { @@ -19,12 +20,13 @@ type Props = { color?: string, className?: string, size?: string | number, + align?: string, }; export const TextComponent = ({ - value, isBold, color, className, size, + value, isBold, color, className, size, align, }: Props) => ( - <Text className={className} isBold={isBold} color={color} size={`${String(size)}em`}> + <Text className={className} isBold={isBold} color={color} size={`${String(size)}em`} align={align}> {value} </Text> ); @@ -33,5 +35,6 @@ TextComponent.defaultProps = { className: '', isBold: false, color: theme.colors.text, - size: `${theme.fontSize.text}em`, + size: theme.fontSize.text, + align: 'left', }; From e43bf67d8712f07b2266f736a65c982816486d9d Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:14:58 -0300 Subject: [PATCH 36/55] feature: add TransactionItem component --- app/components/transaction-item.js | 77 +++++++++++++++++++++++++++++ app/components/transaction-item.mdx | 42 ++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 app/components/transaction-item.js create mode 100644 app/components/transaction-item.mdx diff --git a/app/components/transaction-item.js b/app/components/transaction-item.js new file mode 100644 index 0000000..405ed2d --- /dev/null +++ b/app/components/transaction-item.js @@ -0,0 +1,77 @@ +// @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'; + +const Wrapper = styled(RowComponent)` + background-color: ${props => props.theme.colors.cardBackgroundColor}; + padding: 15px 17px; + border-radius: 7.5px; +`; + +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 Column = styled(ColumnComponent)` + margin-left: 10px; +`; + +type Props = { + type: 'sent' | 'received', + date: string, + address: string, + amount: number, +}; + +/* eslint-disable-next-line max-len */ +const truncateAddress = (address: string) => `${address.substr(0, 20)}...${address.substr(address.length - 10, address.length)}`; + +export const TransactionItemComponent = ({ + type, date, address, amount, +}: Props) => { + const isReceived = type === 'received'; + return ( + <Wrapper alignItems='center' justifyContent='space-between'> + <RowComponent alignItems='center'> + <Icon src={isReceived ? ReceivedIcon : SentIcon} alt='Transaction Type Icon' /> + <Column> + <TransactionTypeLabel isReceived={isReceived} value={type} /> + <TransactionTime value={dateFns.format(new Date(date), 'HH:mm')} /> + </Column> + </RowComponent> + <TextComponent value={truncateAddress(address)} align='center' /> + <ColumnComponent> + <TextComponent + value={formatNumber(amount, `${isReceived ? '+' : '-'}ZEC `)} + color={isReceived ? theme.colors.transactionReceived : theme.colors.transactionSent} + /> + <TextComponent + value={formatNumber(amount, `${isReceived ? '+' : '-'}USD $`)} + color={theme.colors.inactiveItem} + /> + </ColumnComponent> + </Wrapper> + ); +}; diff --git a/app/components/transaction-item.mdx b/app/components/transaction-item.mdx new file mode 100644 index 0000000..ed0beb3 --- /dev/null +++ b/app/components/transaction-item.mdx @@ -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> From 5d6a0c23bf783ff5619afb33c12087e807fcc071 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Wed, 12 Dec 2018 18:19:45 -0300 Subject: [PATCH 37/55] hotfix: transaction address alignment --- app/components/transaction-item.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/components/transaction-item.js b/app/components/transaction-item.js index 405ed2d..428d786 100644 --- a/app/components/transaction-item.js +++ b/app/components/transaction-item.js @@ -34,8 +34,9 @@ const TransactionTime = styled(TextComponent)` color: ${props => props.theme.colors.inactiveItem}; `; -const Column = styled(ColumnComponent)` +const TransactionColumn = styled(ColumnComponent)` margin-left: 10px; + margin-right: 80px; `; type Props = { @@ -54,14 +55,16 @@ export const TransactionItemComponent = ({ const isReceived = type === 'received'; return ( <Wrapper alignItems='center' justifyContent='space-between'> - <RowComponent alignItems='center'> - <Icon src={isReceived ? ReceivedIcon : SentIcon} alt='Transaction Type Icon' /> - <Column> - <TransactionTypeLabel isReceived={isReceived} value={type} /> - <TransactionTime value={dateFns.format(new Date(date), 'HH:mm')} /> - </Column> + <RowComponent> + <RowComponent alignItems='center'> + <Icon src={isReceived ? ReceivedIcon : SentIcon} alt='Transaction Type Icon' /> + <TransactionColumn> + <TransactionTypeLabel isReceived={isReceived} value={type} /> + <TransactionTime value={dateFns.format(new Date(date), 'HH:mm')} /> + </TransactionColumn> + </RowComponent> + <TextComponent value={truncateAddress(address)} align='left' /> </RowComponent> - <TextComponent value={truncateAddress(address)} align='center' /> <ColumnComponent> <TextComponent value={formatNumber(amount, `${isReceived ? '+' : '-'}ZEC `)} From dd4ff196c8f6d78ed8ad1c7a840d13edcced5ded Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 14:18:40 -0300 Subject: [PATCH 38/55] feature: add transactionsDate color --- app/theme.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/theme.js b/app/theme.js index bfd72ab..dc9dcd8 100644 --- a/app/theme.js +++ b/app/theme.js @@ -21,6 +21,7 @@ const sidebarHoveredItemLabel = '#969696'; const background = '#212124'; const transactionSent = '#FF6C6C'; const transactionReceived = '#6AEAC0'; +const transactionsDate = '#777777'; const appTheme = { mode: DARK, @@ -57,6 +58,7 @@ const appTheme = { background, transactionSent, transactionReceived, + transactionsDate, }, sidebarWidth: '200px', headerHeight: '60px', From e1b8bfc35886476880e3d0af3ffe4834c844dad8 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 14:19:53 -0300 Subject: [PATCH 39/55] hotfix: remove border radius from transaction item --- app/components/transaction-item.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/transaction-item.js b/app/components/transaction-item.js index 428d786..caf03e9 100644 --- a/app/components/transaction-item.js +++ b/app/components/transaction-item.js @@ -16,8 +16,7 @@ import formatNumber from '../utils/formatNumber'; const Wrapper = styled(RowComponent)` background-color: ${props => props.theme.colors.cardBackgroundColor}; - padding: 15px 17px; - border-radius: 7.5px; + padding: 10px 17px; `; const Icon = styled.img` @@ -37,9 +36,10 @@ const TransactionTime = styled(TextComponent)` const TransactionColumn = styled(ColumnComponent)` margin-left: 10px; margin-right: 80px; + min-width: 60px; `; -type Props = { +export type Transaction = { type: 'sent' | 'received', date: string, address: string, @@ -51,11 +51,11 @@ const truncateAddress = (address: string) => `${address.substr(0, 20)}...${addre export const TransactionItemComponent = ({ type, date, address, amount, -}: Props) => { +}: Transaction) => { const isReceived = type === 'received'; return ( <Wrapper alignItems='center' justifyContent='space-between'> - <RowComponent> + <RowComponent alignItems='center'> <RowComponent alignItems='center'> <Icon src={isReceived ? ReceivedIcon : SentIcon} alt='Transaction Type Icon' /> <TransactionColumn> From b308ff724d20afcf8b55f3b511e85349d22f93df Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 14:21:18 -0300 Subject: [PATCH 40/55] feature: add TransactionDaily component --- app/components/transaction-daily.js | 51 ++++++++++++++++++++++++++++ app/components/transaction-daily.mdx | 38 +++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 app/components/transaction-daily.js create mode 100644 app/components/transaction-daily.mdx diff --git a/app/components/transaction-daily.js b/app/components/transaction-daily.js new file mode 100644 index 0000000..24f0ca1 --- /dev/null +++ b/app/components/transaction-daily.js @@ -0,0 +1,51 @@ +// @flow +import React, { Fragment } from 'react'; +import styled from 'styled-components'; +import dateFns from 'date-fns'; +import { TransactionItemComponent, type Transaction } from './transaction-item'; +import { TextComponent } from './text'; + +const Wrapper = 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[], +}; + +export const TransactionDailyComponent = ({ transactionsDate, transactions }: Props) => ( + <Fragment> + <Day value={dateFns.format(transactionsDate, 'MMM D, YYYY')} /> + <Wrapper> + {transactions.map(({ + date, type, address, amount, + }) => ( + <div> + <TransactionItemComponent type={type} date={date} address={address} amount={amount} /> + <Divider /> + </div> + ))} + </Wrapper> + </Fragment> +); diff --git a/app/components/transaction-daily.mdx b/app/components/transaction-daily.mdx new file mode 100644 index 0000000..f190de3 --- /dev/null +++ b/app/components/transaction-daily.mdx @@ -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> From 9a0ac73cd47d64381b9759ae1f721c4affeaabf0 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 16:36:46 -0300 Subject: [PATCH 41/55] type: fix z_getoperation* arguments --- services/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/utils.js b/services/utils.js index 356a15b..20e7e09 100644 --- a/services/utils.js +++ b/services/utils.js @@ -745,8 +745,8 @@ export type APIMethods = { z_exportwallet: (filename: string) => Promise<string>, z_getbalance: (address: string, minconf?: number) => Promise<number>, z_getnewaddress: (type: string) => Promise<string>, - z_getoperationresult: (operationid: string) => Promise<Object[]>, - z_getoperationstatus: (operationid: string) => Promise<Object[]>, + z_getoperationresult: (operationid?: string[]) => Promise<Object[]>, + z_getoperationstatus: (operationid?: string[]) => Promise<Object[]>, z_gettotalbalance: ( minconf?: number, includeWatchonly?: boolean, From 7e2618075f667f6af7e9f351d30968a8dc567be6 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:17:37 -0300 Subject: [PATCH 42/55] feature: add ramda lib --- package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 1c46678..53ae043 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "p-queue": "^3.0.0", "process-exists": "^3.1.0", "qrcode.react": "^0.8.0", + "ramda": "^0.26.1", "react": "^16.6.0", "react-click-outside": "tj/react-click-outside", "react-dom": "^16.6.0", diff --git a/yarn.lock b/yarn.lock index bb17efb..df1654d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11394,6 +11394,11 @@ raf@^3.4.0: dependencies: performance-now "^2.1.0" +ramda@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" + integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== + randomatic@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" From cbac64bbaead0a812d782c7ca67b4fdd28d7a552 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:18:08 -0300 Subject: [PATCH 43/55] hotfix: wallet-summary action test --- __tests__/actions/wallet-summary.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/__tests__/actions/wallet-summary.test.js b/__tests__/actions/wallet-summary.test.js index 1eadbb5..ffb9d8d 100644 --- a/__tests__/actions/wallet-summary.test.js +++ b/__tests__/actions/wallet-summary.test.js @@ -32,6 +32,7 @@ describe('WalletSummary Actions', () => { transparent: 5000, shielded: 5000, addresses: [], + transactions: {}, }; store.dispatch(loadWalletSummarySuccess(payload)); From eb366c1324d6bd5134ad6d84cac16eaf320a97b1 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:19:37 -0300 Subject: [PATCH 44/55] hotfix: use "send" and "receive" for transaction type --- app/components/transaction-item.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/components/transaction-item.js b/app/components/transaction-item.js index caf03e9..4d766d8 100644 --- a/app/components/transaction-item.js +++ b/app/components/transaction-item.js @@ -16,7 +16,7 @@ import formatNumber from '../utils/formatNumber'; const Wrapper = styled(RowComponent)` background-color: ${props => props.theme.colors.cardBackgroundColor}; - padding: 10px 17px; + padding: 15px 17px; `; const Icon = styled.img` @@ -40,7 +40,7 @@ const TransactionColumn = styled(ColumnComponent)` `; export type Transaction = { - type: 'sent' | 'received', + type: 'send' | 'receive', date: string, address: string, amount: number, @@ -52,7 +52,7 @@ const truncateAddress = (address: string) => `${address.substr(0, 20)}...${addre export const TransactionItemComponent = ({ type, date, address, amount, }: Transaction) => { - const isReceived = type === 'received'; + const isReceived = type === 'receive'; return ( <Wrapper alignItems='center' justifyContent='space-between'> <RowComponent alignItems='center'> @@ -60,18 +60,18 @@ export const TransactionItemComponent = ({ <Icon src={isReceived ? ReceivedIcon : SentIcon} alt='Transaction Type Icon' /> <TransactionColumn> <TransactionTypeLabel isReceived={isReceived} value={type} /> - <TransactionTime value={dateFns.format(new Date(date), 'HH:mm')} /> + <TransactionTime value={dateFns.format(new Date(date), 'HH:mm A')} /> </TransactionColumn> </RowComponent> <TextComponent value={truncateAddress(address)} align='left' /> </RowComponent> - <ColumnComponent> + <ColumnComponent alignItems='flex-end'> <TextComponent - value={formatNumber(amount, `${isReceived ? '+' : '-'}ZEC `)} + value={formatNumber({ value: amount, append: `${isReceived ? '+' : '-'}ZEC ` })} color={isReceived ? theme.colors.transactionReceived : theme.colors.transactionSent} /> <TextComponent - value={formatNumber(amount, `${isReceived ? '+' : '-'}USD $`)} + value={formatNumber({ value: amount, append: `${isReceived ? '+' : '-'}USD $` })} color={theme.colors.inactiveItem} /> </ColumnComponent> From 829b297fcf412f4dc6e4249de5ae5312ad1b3556 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:19:58 -0300 Subject: [PATCH 45/55] hotfix: transaction daily vertical padding --- app/components/transaction-daily.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/components/transaction-daily.js b/app/components/transaction-daily.js index 24f0ca1..40fb2f5 100644 --- a/app/components/transaction-daily.js +++ b/app/components/transaction-daily.js @@ -1,11 +1,14 @@ // @flow -import React, { Fragment } from 'react'; +import React from 'react'; import styled from 'styled-components'; -import dateFns from 'date-fns'; 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}; @@ -35,17 +38,17 @@ type Props = { }; export const TransactionDailyComponent = ({ transactionsDate, transactions }: Props) => ( - <Fragment> - <Day value={dateFns.format(transactionsDate, 'MMM D, YYYY')} /> - <Wrapper> + <Wrapper> + <Day value={transactionsDate} /> + <TransactionsWrapper> {transactions.map(({ date, type, address, amount, - }) => ( + }, idx) => ( <div> - <TransactionItemComponent type={type} date={date} address={address} amount={amount} /> - <Divider /> + <TransactionItemComponent type={type} date={date} address={address || ''} amount={amount} /> + {idx < transactions.length - 1 && <Divider />} </div> ))} - </Wrapper> - </Fragment> + </TransactionsWrapper> + </Wrapper> ); From 620123f40defeb62875ad8d68006be57d666352b Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:20:22 -0300 Subject: [PATCH 46/55] chore: use object instead of positional arguments --- app/components/wallet-summary.js | 16 ++++++++++------ app/utils/formatNumber.js | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/components/wallet-summary.js b/app/components/wallet-summary.js index f3a5ddf..13fb502 100644 --- a/app/components/wallet-summary.js +++ b/app/components/wallet-summary.js @@ -94,19 +94,23 @@ export const WalletSummaryComponent = ({ /> <AllAddresses value='ALL ADDRESSES' isBold /> <ValueBox> - <TextComponent size={theme.fontSize.zecValueBase * 2.5} value={`ZEC ${formatNumber(total)}`} isBold /> - <USDValue value={`USD $${formatNumber(total * dollarValue)}`} size={theme.fontSize.zecValueBase * 2} /> + <TextComponent size={theme.fontSize.zecValueBase * 2.5} value={`ZEC ${formatNumber({ value: total })}`} isBold /> + <USDValue value={`USD $${formatNumber({ value: total * dollarValue })}`} size={theme.fontSize.zecValueBase * 2} /> </ValueBox> <RowComponent> <ValueBox> <ShieldedValue value='● SHIELDED' isBold size={theme.fontSize.text * 0.8} /> - <TextComponent value={`ZEC ${formatNumber(shielded)}`} isBold size={theme.fontSize.zecValueBase} /> - <USDValue value={`USD $${formatNumber(shielded * dollarValue)}`} /> + <TextComponent value={`ZEC ${formatNumber({ value: shielded })}`} isBold size={theme.fontSize.zecValueBase} /> + <USDValue value={`USD $${formatNumber({ value: shielded * dollarValue })}`} /> </ValueBox> <ValueBox> <Label value='● TRANSPARENT' isBold size={theme.fontSize.text * 0.8} /> - <TextComponent value={`ZEC ${formatNumber(transparent)}`} isBold size={theme.fontSize.zecValueBase} /> - <USDValue value={`USD $${formatNumber(transparent * dollarValue)}`} /> + <TextComponent + value={`ZEC ${formatNumber({ value: transparent })}`} + isBold + size={theme.fontSize.zecValueBase} + /> + <USDValue value={`USD $${formatNumber({ value: transparent * dollarValue })}`} /> </ValueBox> </RowComponent> </Wrapper> diff --git a/app/utils/formatNumber.js b/app/utils/formatNumber.js index bd85119..5d96753 100644 --- a/app/utils/formatNumber.js +++ b/app/utils/formatNumber.js @@ -1,3 +1,3 @@ // @flow -export default (number: number, append?: string = '') => `${append}${number.toLocaleString('de-DE')}`; +export default ({ value, append = '' }: { value: number, append?: string }) => `${append}${(value || 0).toLocaleString('de-DE')}`; From c2f7abb3a084fe0ebde9948aa80d115361f51459 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:20:38 -0300 Subject: [PATCH 47/55] chore: remove connect from sidebar --- app/containers/sidebar.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/containers/sidebar.js b/app/containers/sidebar.js index d4dbc21..7fdd70b 100644 --- a/app/containers/sidebar.js +++ b/app/containers/sidebar.js @@ -1,17 +1,5 @@ // @flow -import { connect } from 'react-redux'; import { SidebarComponent } from '../components/sidebar'; -const mapStateToProps = (state: Object) => ({ - todos: state.todos, -}); - -// const mapDispatchToProps = (dispatch: Dispatch) => ({ -// addTodo: text => dispatch(addTodo(text)), -// }); - -export const SidebarContainer = connect( - mapStateToProps, - // mapDispatchToProps, -)(SidebarComponent); +export const SidebarContainer = SidebarComponent; From 7de3b6b0e9438099ddd3111342513e58307dc8f8 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 17:21:08 -0300 Subject: [PATCH 48/55] feature: add transactions list in redux --- app/containers/dashboard.js | 16 ++++++++++++++++ app/redux/modules/wallet.js | 6 ++++++ app/views/dashboard.js | 27 +++++++++++++++++++-------- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/app/containers/dashboard.js b/app/containers/dashboard.js index 7c8c2e3..90d9b30 100644 --- a/app/containers/dashboard.js +++ b/app/containers/dashboard.js @@ -2,6 +2,8 @@ import { connect } from 'react-redux'; import eres from 'eres'; +import * as R from 'ramda'; +import dateFns from 'date-fns'; import { DashboardView } from '../views/dashboard'; import rpc from '../../services/api'; import { loadWalletSummary, loadWalletSummarySuccess, loadWalletSummaryError } from '../redux/modules/wallet'; @@ -17,6 +19,7 @@ const mapStateToProps = ({ walletSummary }: AppState) => ({ isLoading: walletSummary.isLoading, dollarValue: walletSummary.dollarValue, addresses: walletSummary.addresses, + transactions: walletSummary.transactions, }); const mapDispatchToProps = (dispatch: Dispatch) => ({ @@ -31,12 +34,25 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ 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: R.pipe( + R.map(transaction => ({ + type: transaction.category, + date: new Date(transaction.time * 1000).toISOString(), + address: transaction.address, + amount: Math.abs(transaction.amount), + })), + R.groupBy(obj => dateFns.format(obj.date, 'MMM DD, YYYY')), + )(transactions), }), ); }, diff --git a/app/redux/modules/wallet.js b/app/redux/modules/wallet.js index 53034d3..c5da086 100644 --- a/app/redux/modules/wallet.js +++ b/app/redux/modules/wallet.js @@ -1,5 +1,6 @@ // @flow import type { Action } from '../../types/redux'; +import type { Transaction } from '../../components/transaction-item'; // Actions export const LOAD_WALLET_SUMMARY = 'LOAD_WALLET_SUMMARY'; @@ -17,11 +18,13 @@ export const loadWalletSummarySuccess = ({ shielded, transparent, addresses, + transactions, }: { total: number, shielded: number, transparent: number, addresses: string[], + transactions: { [day: string]: Transaction[] }, }) => ({ type: LOAD_WALLET_SUMMARY_SUCCESS, payload: { @@ -29,6 +32,7 @@ export const loadWalletSummarySuccess = ({ shielded, transparent, addresses, + transactions, }, }); @@ -45,6 +49,7 @@ export type State = { isLoading: boolean, dollarValue: number, addresses: [], + transactions: { [day: string]: Transaction[] }, }; const initialState = { @@ -55,6 +60,7 @@ const initialState = { isLoading: false, dollarValue: 0, addresses: [], + transactions: {}, }; export default (state: State = initialState, action: Action) => { diff --git a/app/views/dashboard.js b/app/views/dashboard.js index 39ce162..046318e 100644 --- a/app/views/dashboard.js +++ b/app/views/dashboard.js @@ -3,8 +3,11 @@ import React from 'react'; 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, @@ -14,6 +17,7 @@ type Props = { isLoading: boolean, dollarValue: number, addresses: string[], + transactions: { [day: string]: Transaction[] }, }; export class Dashboard extends React.Component<Props> { @@ -24,9 +28,11 @@ export class Dashboard extends React.Component<Props> { render() { const { - error, isLoading, total, shielded, transparent, dollarValue, addresses, + error, isLoading, total, shielded, transparent, dollarValue, addresses, transactions, } = this.props; + const days = Object.keys(transactions); + if (error) { return error; } @@ -36,13 +42,18 @@ export class Dashboard extends React.Component<Props> { {isLoading ? ( 'Loading' ) : ( - <WalletSummaryComponent - total={total} - shielded={shielded} - transparent={transparent} - dollarValue={dollarValue} - addresses={addresses} - /> + <div> + <WalletSummaryComponent + total={total} + shielded={shielded} + transparent={transparent} + dollarValue={dollarValue} + addresses={addresses} + /> + {days.map(day => ( + <TransactionDailyComponent transactionsDate={day} transactions={transactions[day]} /> + ))} + </div> )} </div> ); From 9075626b4f1f1e5fce4e973307c20b52c8c5ee89 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 18:35:21 -0300 Subject: [PATCH 49/55] hotfix: move electron to dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 53ae043..c614e1c 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "css-loader": "^1.0.1", "docz": "^0.12.13", "docz-plugin-css": "^0.11.0", - "electron": "^3.0.10", "electron-builder": "^20.36.2", "electron-icon-maker": "^0.0.4", "electron-is-dev": "^1.0.1", @@ -89,6 +88,7 @@ "autoprefixer": "^9.3.1", "connected-react-router": "^5.0.1", "date-fns": "^1.30.1", + "electron": "^3.0.10", "electron-store": "^2.0.0", "eres": "^1.0.1", "got": "^9.3.2", From 9d24fbaa07582c19ac5cb000e6c1d7f0d1582a71 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 18:36:50 -0300 Subject: [PATCH 50/55] feature: add helper function to grab zec price --- config/electron.js | 6 ++++++ services/zec-price.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 services/zec-price.js diff --git a/config/electron.js b/config/electron.js index 3c741d4..2da0330 100644 --- a/config/electron.js +++ b/config/electron.js @@ -13,6 +13,8 @@ import eres from 'eres'; import { registerDebugShortcut } from '../utils/debug-shortcut'; import runDaemon from './daemon/zcashd-child-process'; import zcashLog from './daemon/logger'; +import getZecPrice from '../services/zec-price'; +import store from './electron-store'; let mainWindow: BrowserWindowType; let updateAvailable: boolean = false; @@ -58,6 +60,10 @@ const createWindow = () => { }, }); + getZecPrice().then((obj) => { + store.set('ZEC_DOLLAR_PRICE', obj.USD); + }); + mainWindow.setVisibleOnAllWorkspaces(true); registerDebugShortcut(app, mainWindow); diff --git a/services/zec-price.js b/services/zec-price.js new file mode 100644 index 0000000..7a92c0a --- /dev/null +++ b/services/zec-price.js @@ -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=b6162b068ff9f8fe2872070b791146b06d186e83d5e52e49dcaa42ef8d1d3875`; + + 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(); +}); From 956782679bb4844bb5ab5a4dc0d9ff803c265806 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Thu, 13 Dec 2018 18:37:15 -0300 Subject: [PATCH 51/55] feature: pass down zec dollar value to dashboard components --- __tests__/actions/wallet-summary.test.js | 1 + app/components/transaction-daily.js | 11 +++++++++-- app/components/transaction-item.js | 5 +++-- app/components/wallet-summary.js | 10 +++++----- app/containers/dashboard.js | 4 +++- app/redux/modules/wallet.js | 7 +++++-- app/views/dashboard.js | 8 ++++---- 7 files changed, 30 insertions(+), 16 deletions(-) diff --git a/__tests__/actions/wallet-summary.test.js b/__tests__/actions/wallet-summary.test.js index ffb9d8d..0df55d5 100644 --- a/__tests__/actions/wallet-summary.test.js +++ b/__tests__/actions/wallet-summary.test.js @@ -33,6 +33,7 @@ describe('WalletSummary Actions', () => { shielded: 5000, addresses: [], transactions: {}, + zecPrice: 50, }; store.dispatch(loadWalletSummarySuccess(payload)); diff --git a/app/components/transaction-daily.js b/app/components/transaction-daily.js index 40fb2f5..382d0a7 100644 --- a/app/components/transaction-daily.js +++ b/app/components/transaction-daily.js @@ -35,9 +35,10 @@ const Divider = styled.div` type Props = { transactionsDate: string, transactions: Transaction[], + zecPrice: number, }; -export const TransactionDailyComponent = ({ transactionsDate, transactions }: Props) => ( +export const TransactionDailyComponent = ({ transactionsDate, transactions, zecPrice }: Props) => ( <Wrapper> <Day value={transactionsDate} /> <TransactionsWrapper> @@ -45,7 +46,13 @@ export const TransactionDailyComponent = ({ transactionsDate, transactions }: Pr date, type, address, amount, }, idx) => ( <div> - <TransactionItemComponent type={type} date={date} address={address || ''} amount={amount} /> + <TransactionItemComponent + type={type} + date={date} + address={address || ''} + amount={amount} + zecPrice={zecPrice} + /> {idx < transactions.length - 1 && <Divider />} </div> ))} diff --git a/app/components/transaction-item.js b/app/components/transaction-item.js index 4d766d8..78dbf1b 100644 --- a/app/components/transaction-item.js +++ b/app/components/transaction-item.js @@ -44,13 +44,14 @@ export type Transaction = { date: string, address: string, amount: number, + zecPrice: number, }; /* eslint-disable-next-line max-len */ const truncateAddress = (address: string) => `${address.substr(0, 20)}...${address.substr(address.length - 10, address.length)}`; export const TransactionItemComponent = ({ - type, date, address, amount, + type, date, address, amount, zecPrice, }: Transaction) => { const isReceived = type === 'receive'; return ( @@ -71,7 +72,7 @@ export const TransactionItemComponent = ({ color={isReceived ? theme.colors.transactionReceived : theme.colors.transactionSent} /> <TextComponent - value={formatNumber({ value: amount, append: `${isReceived ? '+' : '-'}USD $` })} + value={formatNumber({ value: amount * zecPrice, append: `${isReceived ? '+' : '-'}USD $` })} color={theme.colors.inactiveItem} /> </ColumnComponent> diff --git a/app/components/wallet-summary.js b/app/components/wallet-summary.js index 13fb502..5c340b9 100644 --- a/app/components/wallet-summary.js +++ b/app/components/wallet-summary.js @@ -75,12 +75,12 @@ type Props = { total: number, shielded: number, transparent: number, - dollarValue: number, + zecPrice: number, addresses: string[], }; export const WalletSummaryComponent = ({ - total, shielded, transparent, dollarValue, addresses, + total, shielded, transparent, zecPrice, addresses, }: Props) => ( <Wrapper> <DropdownComponent @@ -95,13 +95,13 @@ export const WalletSummaryComponent = ({ <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 * dollarValue })}`} size={theme.fontSize.zecValueBase * 2} /> + <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 * dollarValue })}`} /> + <USDValue value={`USD $${formatNumber({ value: shielded * zecPrice })}`} /> </ValueBox> <ValueBox> <Label value='● TRANSPARENT' isBold size={theme.fontSize.text * 0.8} /> @@ -110,7 +110,7 @@ export const WalletSummaryComponent = ({ isBold size={theme.fontSize.zecValueBase} /> - <USDValue value={`USD $${formatNumber({ value: transparent * dollarValue })}`} /> + <USDValue value={`USD $${formatNumber({ value: transparent * zecPrice })}`} /> </ValueBox> </RowComponent> </Wrapper> diff --git a/app/containers/dashboard.js b/app/containers/dashboard.js index 90d9b30..eff8240 100644 --- a/app/containers/dashboard.js +++ b/app/containers/dashboard.js @@ -6,6 +6,7 @@ import * as R from 'ramda'; 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'; @@ -17,7 +18,7 @@ const mapStateToProps = ({ walletSummary }: AppState) => ({ transparent: walletSummary.transparent, error: walletSummary.error, isLoading: walletSummary.isLoading, - dollarValue: walletSummary.dollarValue, + zecPrice: walletSummary.zecPrice, addresses: walletSummary.addresses, transactions: walletSummary.transactions, }); @@ -53,6 +54,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ })), R.groupBy(obj => dateFns.format(obj.date, 'MMM DD, YYYY')), )(transactions), + zecPrice: store.get('ZEC_DOLLAR_PRICE'), }), ); }, diff --git a/app/redux/modules/wallet.js b/app/redux/modules/wallet.js index c5da086..aec93d9 100644 --- a/app/redux/modules/wallet.js +++ b/app/redux/modules/wallet.js @@ -19,12 +19,14 @@ export const loadWalletSummarySuccess = ({ transparent, addresses, transactions, + zecPrice, }: { total: number, shielded: number, transparent: number, addresses: string[], transactions: { [day: string]: Transaction[] }, + zecPrice: number, }) => ({ type: LOAD_WALLET_SUMMARY_SUCCESS, payload: { @@ -33,6 +35,7 @@ export const loadWalletSummarySuccess = ({ transparent, addresses, transactions, + zecPrice, }, }); @@ -47,7 +50,7 @@ export type State = { transparent: number, error: string | null, isLoading: boolean, - dollarValue: number, + zecPrice: number, addresses: [], transactions: { [day: string]: Transaction[] }, }; @@ -58,7 +61,7 @@ const initialState = { transparent: 0, error: null, isLoading: false, - dollarValue: 0, + zecPrice: 0, addresses: [], transactions: {}, }; diff --git a/app/views/dashboard.js b/app/views/dashboard.js index 046318e..b19516f 100644 --- a/app/views/dashboard.js +++ b/app/views/dashboard.js @@ -15,7 +15,7 @@ type Props = { transparent: number, error: string | null, isLoading: boolean, - dollarValue: number, + zecPrice: number, addresses: string[], transactions: { [day: string]: Transaction[] }, }; @@ -28,7 +28,7 @@ export class Dashboard extends React.Component<Props> { render() { const { - error, isLoading, total, shielded, transparent, dollarValue, addresses, transactions, + error, isLoading, total, shielded, transparent, zecPrice, addresses, transactions, } = this.props; const days = Object.keys(transactions); @@ -47,11 +47,11 @@ export class Dashboard extends React.Component<Props> { total={total} shielded={shielded} transparent={transparent} - dollarValue={dollarValue} + zecPrice={zecPrice} addresses={addresses} /> {days.map(day => ( - <TransactionDailyComponent transactionsDate={day} transactions={transactions[day]} /> + <TransactionDailyComponent transactionsDate={day} transactions={transactions[day]} zecPrice={zecPrice} /> ))} </div> )} From 7abc3b20ade554d478ea511ea202f8fe9497cc93 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Sat, 15 Dec 2018 12:08:18 -0300 Subject: [PATCH 52/55] hotfix: remove ramda --- app/containers/dashboard.js | 28 ++++++++++++++++++++-------- package.json | 3 ++- yarn.lock | 15 ++++++++++----- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/app/containers/dashboard.js b/app/containers/dashboard.js index eff8240..6b94c6f 100644 --- a/app/containers/dashboard.js +++ b/app/containers/dashboard.js @@ -2,12 +2,17 @@ import { connect } from 'react-redux'; import eres from 'eres'; -import * as R from 'ramda'; +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 { + loadWalletSummary, + loadWalletSummarySuccess, + loadWalletSummaryError, +} from '../redux/modules/wallet'; import type { AppState } from '../types/app-state'; import type { Dispatch } from '../types/redux'; @@ -33,11 +38,18 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ 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()); + const [transactionsErr, transactions = []] = await eres( + rpc.listtransactions(), + ); - if (transactionsErr) return dispatch(loadWalletSummaryError({ error: transactionsErr.message })); + if (transactionsErr) { + return dispatch( + loadWalletSummaryError({ error: transactionsErr.message }), + ); + } dispatch( loadWalletSummarySuccess({ @@ -45,15 +57,15 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ total: walletSummary.total, shielded: walletSummary.private, addresses, - transactions: R.pipe( - R.map(transaction => ({ + transactions: flow([ + arr => arr.map(transaction => ({ type: transaction.category, date: new Date(transaction.time * 1000).toISOString(), address: transaction.address, amount: Math.abs(transaction.amount), })), - R.groupBy(obj => dateFns.format(obj.date, 'MMM DD, YYYY')), - )(transactions), + arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')), + ])(transactions), zecPrice: store.get('ZEC_DOLLAR_PRICE'), }), ); diff --git a/package.json b/package.json index c614e1c..9972e63 100644 --- a/package.json +++ b/package.json @@ -93,10 +93,11 @@ "eres": "^1.0.1", "got": "^9.3.2", "history": "^4.7.2", + "lodash.flow": "^3.5.0", + "lodash.groupby": "^4.6.0", "p-queue": "^3.0.0", "process-exists": "^3.1.0", "qrcode.react": "^0.8.0", - "ramda": "^0.26.1", "react": "^16.6.0", "react-click-outside": "tj/react-click-outside", "react-dom": "^16.6.0", diff --git a/yarn.lock b/yarn.lock index df1654d..9ff9f73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8871,11 +8871,21 @@ lodash.flattendepth@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.flattendepth/-/lodash.flattendepth-4.7.0.tgz#b4d2d14fc7d9c53deb96642eb616fff22a60932f" 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: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 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: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -11394,11 +11404,6 @@ raf@^3.4.0: dependencies: performance-now "^2.1.0" -ramda@^0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" - integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== - randomatic@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" From 8cca0a36968e0b09911aee68e9739a50c34c605e Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Sat, 15 Dec 2018 12:08:56 -0300 Subject: [PATCH 53/55] test: remove normalize from docz --- doczrc.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/doczrc.js b/doczrc.js index 8b48c23..6a61d60 100644 --- a/doczrc.js +++ b/doczrc.js @@ -12,10 +12,6 @@ export default { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=PT+Sans:400,700', }, - { - rel: 'stylesheet', - href: 'https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css', - }, ], }, }, From 0ee42e2bb6b75f0aaedd313c31bc31fdf1281124 Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Sat, 15 Dec 2018 12:09:46 -0300 Subject: [PATCH 54/55] hotfix: split truncateAddress helper --- app/utils/truncateAddress.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 app/utils/truncateAddress.js diff --git a/app/utils/truncateAddress.js b/app/utils/truncateAddress.js new file mode 100644 index 0000000..3a78a3d --- /dev/null +++ b/app/utils/truncateAddress.js @@ -0,0 +1,6 @@ +// @flow + +export default (address: string = '') => `${address.substr(0, 20)}...${address.substr( + address.length - 10, + address.length, +)}`; From ec6422bf75fc8f716a2385133214104c7a3f8abd Mon Sep 17 00:00:00 2001 From: George Lima <georgelima11@hotmail.com> Date: Sat, 15 Dec 2018 12:10:39 -0300 Subject: [PATCH 55/55] lint: decrease max-len --- .eslintrc | 2 +- app/components/button.js | 1 + app/components/dropdown.js | 14 +++-- app/components/input.js | 8 ++- app/components/qrcode.js | 4 +- app/components/sidebar.js | 16 ++++-- app/components/text.js | 19 +++++-- app/components/transaction-daily.js | 6 ++- app/components/transaction-item.js | 44 ++++++++++++---- app/components/wallet-summary.js | 47 +++++++++++++---- app/components/with-daemon-status-check.js | 1 + app/constants/sidebar.js | 6 ++- app/redux/create.js | 4 +- app/router/router.js | 12 ++++- app/views/dashboard.js | 15 +++++- config/daemon/fetch-windows-params.js | 60 ++++++++++++++++++---- config/daemon/get-binaries-path.js | 7 ++- config/daemon/run-fetch-params.js | 4 +- config/daemon/zcashd-child-process.js | 21 ++++++-- config/electron.js | 12 +++-- 20 files changed, 238 insertions(+), 65 deletions(-) diff --git a/.eslintrc b/.eslintrc index afeb5e9..3373b46 100644 --- a/.eslintrc +++ b/.eslintrc @@ -34,7 +34,7 @@ "max-len": [ "error", { - "code": 120, + "code": 80, "tabWidth": 2, "ignoreUrls": true, "ignoreComments": true, diff --git a/app/components/button.js b/app/components/button.js index c77b368..5b1cc18 100644 --- a/app/components/button.js +++ b/app/components/button.js @@ -4,6 +4,7 @@ import React from 'react'; import styled from 'styled-components'; import { Link } from 'react-router-dom'; /* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable max-len */ // $FlowFixMe import { darken } from 'polished'; diff --git a/app/components/dropdown.js b/app/components/dropdown.js index 8772135..59c9101 100644 --- a/app/components/dropdown.js +++ b/app/components/dropdown.js @@ -11,7 +11,10 @@ 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})`}; + 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; @@ -77,7 +80,9 @@ export class DropdownComponent extends Component<Props, State> { preferPlace='below' enterExitTransitionDurationMs={0} body={[ - <ClickOutside onClickOutside={() => this.setState(() => ({ isOpen: false }))}> + <ClickOutside + onClickOutside={() => this.setState(() => ({ isOpen: false }))} + > <MenuWrapper> {label && ( <MenuItem disabled> @@ -94,7 +99,10 @@ export class DropdownComponent extends Component<Props, State> { ]} tipSize={7} > - {renderTrigger(() => this.setState(state => ({ isOpen: !state.isOpen })), isOpen)} + {renderTrigger( + () => this.setState(state => ({ isOpen: !state.isOpen })), + isOpen, + )} </PopoverWithStyle> ); } diff --git a/app/components/input.js b/app/components/input.js index 68ce8fb..3e61a97 100644 --- a/app/components/input.js +++ b/app/components/input.js @@ -36,8 +36,12 @@ type Props = { export const InputComponent = ({ inputType, onChange, ...props }: Props) => { const inputTypes = { - input: () => <Input onChange={evt => onChange(evt.target.value)} {...props} />, - textarea: () => <Textarea onChange={evt => onChange(evt.target.value)} {...props} />, + input: () => ( + <Input onChange={evt => onChange(evt.target.value)} {...props} /> + ), + textarea: () => ( + <Textarea onChange={evt => onChange(evt.target.value)} {...props} /> + ), dropdown: () => null, }; diff --git a/app/components/qrcode.js b/app/components/qrcode.js index 64dc250..862d572 100644 --- a/app/components/qrcode.js +++ b/app/components/qrcode.js @@ -8,7 +8,9 @@ type Props = { 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 = { size: 128, diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 2ad9221..4295d52 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -16,10 +16,14 @@ const Wrapper = styled.div` `; const StyledLink = styled(Link)` - color: ${props => (props.isActive ? props.theme.colors.sidebarItemActive : props.theme.colors.sidebarItem)}; + color: ${props => (props.isActive + ? props.theme.colors.sidebarItemActive + : props.theme.colors.sidebarItem)}; font-size: ${props => `${props.theme.fontSize.text}em`}; text-decoration: none; - font-weight: ${props => (props.isActive ? props.theme.fontWeight.bold : props.theme.fontWeight.default)}; + font-weight: ${props => (props.isActive + ? props.theme.fontWeight.bold + : props.theme.fontWeight.default)}; padding: 0 20px; height: 35px; width: 100%; @@ -27,11 +31,15 @@ const StyledLink = styled(Link)` display: flex; align-items: center; outline: none; - border-right: ${props => (props.isActive ? `1px solid ${props.theme.colors.sidebarItemActive}` : '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)}; + props => (props.isActive + ? props.theme.colors.sidebarItemActive + : props.theme.colors.sidebarHoveredItemLabel)}; background-color: ${props => props.theme.colors.sidebarHoveredItem}; } `; diff --git a/app/components/text.js b/app/components/text.js index c8dac60..2e110fc 100644 --- a/app/components/text.js +++ b/app/components/text.js @@ -10,7 +10,9 @@ const Text = styled.p` 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)}; + font-weight: ${props => (props.isBold + ? props.theme.fontWeight.bold + : props.theme.fontWeight.default)}; text-align: ${props => props.align}; `; @@ -24,9 +26,20 @@ type Props = { }; export const TextComponent = ({ - value, isBold, color, className, size, align, + value, + isBold, + color, + className, + size, + align, }: Props) => ( - <Text className={className} isBold={isBold} color={color} size={`${String(size)}em`} align={align}> + <Text + className={className} + isBold={isBold} + color={color} + size={`${String(size)}em`} + align={align} + > {value} </Text> ); diff --git a/app/components/transaction-daily.js b/app/components/transaction-daily.js index 382d0a7..68a3cd7 100644 --- a/app/components/transaction-daily.js +++ b/app/components/transaction-daily.js @@ -38,7 +38,11 @@ type Props = { zecPrice: number, }; -export const TransactionDailyComponent = ({ transactionsDate, transactions, zecPrice }: Props) => ( +export const TransactionDailyComponent = ({ + transactionsDate, + transactions, + zecPrice, +}: Props) => ( <Wrapper> <Day value={transactionsDate} /> <TransactionsWrapper> diff --git a/app/components/transaction-item.js b/app/components/transaction-item.js index 78dbf1b..0001b94 100644 --- a/app/components/transaction-item.js +++ b/app/components/transaction-item.js @@ -13,6 +13,7 @@ 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}; @@ -25,7 +26,9 @@ const Icon = styled.img` `; const TransactionTypeLabel = styled(TextComponent)` - color: ${props => (props.isReceived ? props.theme.colors.transactionReceived : props.theme.colors.transactionSent)}; + color: ${props => (props.isReceived + ? props.theme.colors.transactionReceived + : props.theme.colors.transactionSent)}; text-transform: capitalize; `; @@ -47,32 +50,51 @@ export type Transaction = { zecPrice: number, }; -/* eslint-disable-next-line max-len */ -const truncateAddress = (address: string) => `${address.substr(0, 20)}...${address.substr(address.length - 10, address.length)}`; - export const TransactionItemComponent = ({ - type, date, address, amount, zecPrice, + 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' /> + <Icon + src={isReceived ? ReceivedIcon : SentIcon} + alt='Transaction Type Icon' + /> <TransactionColumn> <TransactionTypeLabel isReceived={isReceived} value={type} /> - <TransactionTime value={dateFns.format(new Date(date), 'HH:mm A')} /> + <TransactionTime value={transactionTime} /> </TransactionColumn> </RowComponent> - <TextComponent value={truncateAddress(address)} align='left' /> + <TextComponent value={transactionAddress} align='left' /> </RowComponent> <ColumnComponent alignItems='flex-end'> <TextComponent - value={formatNumber({ value: amount, append: `${isReceived ? '+' : '-'}ZEC ` })} - color={isReceived ? theme.colors.transactionReceived : theme.colors.transactionSent} + value={transactionValueInZec} + color={ + isReceived + ? theme.colors.transactionReceived + : theme.colors.transactionSent + } /> <TextComponent - value={formatNumber({ value: amount * zecPrice, append: `${isReceived ? '+' : '-'}USD $` })} + value={transactionValueInUsd} color={theme.colors.inactiveItem} /> </ColumnComponent> diff --git a/app/components/wallet-summary.js b/app/components/wallet-summary.js index 5c340b9..63816e0 100644 --- a/app/components/wallet-summary.js +++ b/app/components/wallet-summary.js @@ -53,7 +53,9 @@ const SeeMoreButton = styled.button` border-style: solid; border-radius: 100%; border-width: 1px; - border-color: ${props => (props.isOpen ? props.theme.colors.activeItem : props.theme.colors.inactiveItem)}; + border-color: ${props => (props.isOpen + ? props.theme.colors.activeItem + : props.theme.colors.inactiveItem)}; background-color: transparent; padding: 5px; cursor: pointer; @@ -80,7 +82,11 @@ type Props = { }; export const WalletSummaryComponent = ({ - total, shielded, transparent, zecPrice, addresses, + total, + shielded, + transparent, + zecPrice, + addresses, }: Props) => ( <Wrapper> <DropdownComponent @@ -94,23 +100,46 @@ export const WalletSummaryComponent = ({ /> <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} /> + <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 })}`} /> + <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} /> + <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 })}`} /> + <USDValue + value={`USD $${formatNumber({ value: transparent * zecPrice })}`} + /> </ValueBox> </RowComponent> </Wrapper> diff --git a/app/components/with-daemon-status-check.js b/app/components/with-daemon-status-check.js index b2d5735..e587b7d 100644 --- a/app/components/with-daemon-status-check.js +++ b/app/components/with-daemon-status-check.js @@ -9,6 +9,7 @@ type State = { type Props = {}; +/* eslint-disable max-len */ export const withDaemonStatusCheck = <PassedProps: {}>( WrappedComponent: ComponentType<PassedProps>, ): ComponentType<$Diff<PassedProps, Props>> => class extends Component<PassedProps, State> { diff --git a/app/constants/sidebar.js b/app/constants/sidebar.js index ea23fb1..0684fa0 100644 --- a/app/constants/sidebar.js +++ b/app/constants/sidebar.js @@ -25,7 +25,8 @@ export const MENU_OPTIONS = [ { label: 'Dashboard', route: DASHBOARD_ROUTE, - icon: (isActive: boolean) => (isActive ? DashboardIconActive : DashboardIcon), + // eslint-disable-next-line + icon: (isActive: boolean) => isActive ? DashboardIconActive : DashboardIcon, }, { label: 'Send', @@ -40,7 +41,8 @@ export const MENU_OPTIONS = [ { label: 'Transactions', route: TRANSACTIONS_ROUTE, - icon: (isActive: boolean) => (isActive ? TransactionsIconActive : TransactionsIcon), + // eslint-disable-next-line + icon: (isActive: boolean) => isActive ? TransactionsIconActive : TransactionsIcon, }, { label: 'Settings', diff --git a/app/redux/create.js b/app/redux/create.js index e2bf0a2..8e06402 100644 --- a/app/redux/create.js +++ b/app/redux/create.js @@ -9,7 +9,9 @@ import { createRootReducer } from './modules/reducer'; 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) => { const middleware = applyMiddleware(thunk, routerMiddleware(history)); diff --git a/app/router/router.js b/app/router/router.js index 0534ffd..d3eaaff 100644 --- a/app/router/router.js +++ b/app/router/router.js @@ -16,7 +16,11 @@ import { LayoutComponent } from '../components/layout'; import { HeaderComponent } from '../components/header'; import { - DASHBOARD_ROUTE, SEND_ROUTE, RECEIVE_ROUTE, SETTINGS_ROUTE, CONSOLE_ROUTE, + DASHBOARD_ROUTE, + SEND_ROUTE, + RECEIVE_ROUTE, + SETTINGS_ROUTE, + CONSOLE_ROUTE, } from '../constants/routes'; const FullWrapper = styled.div` @@ -46,7 +50,11 @@ export const RouterComponent = ({ location }: { location: Location }) => ( {/* $FlowFixMe */} <LayoutComponent> <Switch> - <Route exact path={DASHBOARD_ROUTE} component={DashboardContainer} /> + <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} /> diff --git a/app/views/dashboard.js b/app/views/dashboard.js index b19516f..a76b495 100644 --- a/app/views/dashboard.js +++ b/app/views/dashboard.js @@ -28,7 +28,14 @@ export class Dashboard extends React.Component<Props> { render() { const { - error, isLoading, total, shielded, transparent, zecPrice, addresses, transactions, + error, + isLoading, + total, + shielded, + transparent, + zecPrice, + addresses, + transactions, } = this.props; const days = Object.keys(transactions); @@ -51,7 +58,11 @@ export class Dashboard extends React.Component<Props> { addresses={addresses} /> {days.map(day => ( - <TransactionDailyComponent transactionsDate={day} transactions={transactions[day]} zecPrice={zecPrice} /> + <TransactionDailyComponent + transactionsDate={day} + transactions={transactions[day]} + zecPrice={zecPrice} + /> ))} </div> )} diff --git a/config/daemon/fetch-windows-params.js b/config/daemon/fetch-windows-params.js index e2e1548..dfba7d9 100644 --- a/config/daemon/fetch-windows-params.js +++ b/config/daemon/fetch-windows-params.js @@ -19,16 +19,36 @@ import log from './logger'; 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 }> = [ - { name: 'sprout-proving.key', hash: '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7' }, - { name: 'sprout-verifying.key', hash: '4bd498dae0aacfd8e98dc306338d017d9c08dd0918ead18172bd0aec2fc5df82' }, - { name: 'sapling-spend.params', hash: '8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13' }, - { name: 'sapling-output.params', hash: '2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4' }, - { name: 'sprout-groth16.params', hash: 'b685d700c60328498fbde589c8c7c484c722b788b265b72af448a5bf0ee55b50' }, + { + name: 'sprout-proving.key', + hash: '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7', + }, + { + 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) => { fs.readFile(pathToFile, (err, file) => { 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) => { log(`Downloading ${file.name}...`); @@ -50,7 +71,9 @@ const downloadFile = ({ file, pathToSave }): Promise<*> => new Promise((resolve, log(`SHA256 validation for file ${file.name} succeeded!`); resolve(file.name); } 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; 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.stderr.on('data', data => reject(data.toString())); @@ -70,20 +95,33 @@ export default (): Promise<*> => new Promise((resolve, reject) => { await Promise.all( 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) { missingDownloadParam = true; + // eslint-disable-next-line max-len queue.add(() => downloadFile({ file, pathToSave }).then(() => log(`Download ${file.name} finished!`))); } else { const isValid = await checkSha256(pathToSave, file.hash); if (isValid) { log(`${file.name} already is in ${pathToSave}...`); } 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(() => { + // eslint-disable-next-line max-len downloadFile({ file, pathToSave }).then(() => log(`Download ${file.name} finished!`)); }); } diff --git a/config/daemon/get-binaries-path.js b/config/daemon/get-binaries-path.js index aa827b3..c5b8f0a 100644 --- a/config/daemon/get-binaries-path.js +++ b/config/daemon/get-binaries-path.js @@ -3,5 +3,8 @@ import path from 'path'; /* eslint-disable-next-line import/no-extraneous-dependencies */ import isDev from 'electron-is-dev'; -// $FlowFixMe -export default () => (isDev ? path.join(__dirname, '..', '..', './bin') : path.join(process.resourcesPath, 'bin')); +/* eslint-disable operator-linebreak */ +export default () => (isDev + ? path.join(__dirname, '..', '..', './bin') + : // $FlowFixMe + path.join(process.resourcesPath, 'bin')); diff --git a/config/daemon/run-fetch-params.js b/config/daemon/run-fetch-params.js index fac0e1d..1ef41aa 100644 --- a/config/daemon/run-fetch-params.js +++ b/config/daemon/run-fetch-params.js @@ -8,5 +8,7 @@ import runUnixFetchParams from './fetch-unix-params'; export default (): Promise<*> => { log('Fetching params'); - return os.platform() === 'win32' ? fetchWindowsParams() : runUnixFetchParams(); + return os.platform() === 'win32' + ? fetchWindowsParams() + : runUnixFetchParams(); }; diff --git a/config/daemon/zcashd-child-process.js b/config/daemon/zcashd-child-process.js index af09d67..34779b1 100644 --- a/config/daemon/zcashd-child-process.js +++ b/config/daemon/zcashd-child-process.js @@ -35,13 +35,20 @@ const getDaemonOptions = ({ username, password }) => { `-rpcuser=${username}`, `-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; +// eslint-disable-next-line 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()); @@ -76,9 +83,13 @@ const runDaemon: () => Promise<?ChildProcess> = () => new Promise(async (resolve store.set('rpcpassword', rpcCredentials.password); } - const childProcess = cp.spawn(processName, getDaemonOptions(rpcCredentials), { - stdio: ['ignore', 'pipe', 'pipe'], - }); + const childProcess = cp.spawn( + processName, + getDaemonOptions(rpcCredentials), + { + stdio: ['ignore', 'pipe', 'pipe'], + }, + ); childProcess.stdout.on('data', (data) => { if (mainWindow) mainWindow.webContents.send('zcashd-log', data.toString()); diff --git a/config/electron.js b/config/electron.js index 2da0330..5f5816c 100644 --- a/config/electron.js +++ b/config/electron.js @@ -39,9 +39,9 @@ const createWindow = () => { autoUpdater.on('download-progress', progress => showStatus( /* eslint-disable-next-line max-len */ - `Download speed: ${progress.bytesPerSecond} - Downloaded ${progress.percent}% (${progress.transferred}/${ - progress.total - })`, + `Download speed: ${progress.bytesPerSecond} - Downloaded ${ + progress.percent + }% (${progress.transferred}/${progress.total})`, )); autoUpdater.on('update-downloaded', () => { updateAvailable = true; @@ -67,7 +67,11 @@ const createWindow = () => { mainWindow.setVisibleOnAllWorkspaces(true); 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.mainWindow = mainWindow;