diff --git a/__tests__/utils/sort-by-descend.js b/__tests__/utils/sort-by-descend.js new file mode 100644 index 0000000..d56b37c --- /dev/null +++ b/__tests__/utils/sort-by-descend.js @@ -0,0 +1,13 @@ +// @flow + +import 'jest-dom/extend-expect'; + +import { sortByDescend } from '../../app/utils/sort-by-descend'; + +describe('truncateAddress', () => { + test('should truncate ZEC address', () => { + expect( + sortByDescend('id')([{ id: 5 }, { id: 2 }, { id: 1 }, { id: 0 }, { id: 1 }, { id: 1 }]), + ).toEqual([{ id: 5 }, { id: 2 }, { id: 1 }, { id: 1 }, { id: 1 }, { id: 0 }]); + }); +}); diff --git a/__tests__/utils/sort-by.js b/__tests__/utils/sort-by.js new file mode 100644 index 0000000..6d48fae --- /dev/null +++ b/__tests__/utils/sort-by.js @@ -0,0 +1,13 @@ +// @flow + +import 'jest-dom/extend-expect'; + +import { sortBy } from '../../app/utils/sort-by'; + +describe('truncateAddress', () => { + test('should truncate ZEC address', () => { + expect( + sortBy('id')([{ id: 5 }, { id: 2 }, { id: 1 }, { id: 0 }, { id: 1 }, { id: 1 }]), + ).toEqual([{ id: 0 }, { id: 1 }, { id: 1 }, { id: 1 }, { id: 2 }, { id: 5 }]); + }); +}); diff --git a/app/components/sidebar.js b/app/components/sidebar.js index 10f29cf..d852e34 100644 --- a/app/components/sidebar.js +++ b/app/components/sidebar.js @@ -32,7 +32,7 @@ const StyledLink = styled.a` display: flex; align-items: center; outline: none; - border-right: ${(props: StyledLinkProps) => (props.isActive ? `3px solid ${props.theme.colors.sidebarItemActive}` : 'none')}; + border-right: ${(props: StyledLinkProps) => (props.isActive ? `3px solid ${props.theme.colors.sidebarItemActive(props)}` : 'none')}; cursor: pointer; transition: all 0.03s ${(props: StyledLinkProps) => props.theme.transitionEase}; diff --git a/app/components/wallet-address.js b/app/components/wallet-address.js index 5e23ae2..3d2a4fd 100644 --- a/app/components/wallet-address.js +++ b/app/components/wallet-address.js @@ -17,6 +17,7 @@ const AddressWrapper = styled.div` background-color: #000; border-radius: 6px; padding: 7px 13px; + margin-bottom: 10px; width: 100%; `; @@ -41,7 +42,7 @@ const QRCodeWrapper = styled.div` background-color: #000; border-radius: 6px; padding: 20px; - margin-top: 10px; + margin-bottom: 10px; width: 100%; `; diff --git a/app/containers/dashboard.js b/app/containers/dashboard.js index 28b88cd..e2477a6 100644 --- a/app/containers/dashboard.js +++ b/app/containers/dashboard.js @@ -8,13 +8,14 @@ import dateFns from 'date-fns'; import { BigNumber } from 'bignumber.js'; import { DashboardView } from '../views/dashboard'; import rpc from '../../services/api'; +import { listShieldedTransactions } from '../../services/shielded-transactions'; import store from '../../config/electron-store'; import { loadWalletSummary, loadWalletSummarySuccess, loadWalletSummaryError, } from '../redux/modules/wallet'; -import { sortBy } from '../utils/sort-by'; +import { sortByDescend } from '../utils/sort-by-descend'; import type { AppState } from '../types/app-state'; import type { Dispatch } from '../types/redux'; @@ -47,7 +48,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ ); } - const formattedTransactions = flow([ + const formattedTransactions: Array = flow([ arr => arr.map(transaction => ({ transactionId: transaction.txid, type: transaction.category, @@ -58,10 +59,11 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')), obj => Object.keys(obj).map(day => ({ day, - list: sortBy('date')(obj[day]), + jsDay: new Date(day), + list: sortByDescend('date')(obj[day]), })), - sortBy('day'), - ])(transactions); + sortByDescend('jsDay'), + ])([...transactions, ...listShieldedTransactions()]); if (!zAddresses.length) { const [, newZAddress] = await eres(rpc.z_getnewaddress()); diff --git a/app/containers/send.js b/app/containers/send.js index 883bdea..c4b62d3 100644 --- a/app/containers/send.js +++ b/app/containers/send.js @@ -21,6 +21,7 @@ import { } from '../redux/modules/send'; import { filterObjectNullKeys } from '../utils/filter-object-null-keys'; +import { saveShieldedTransaction } from '../../services/shielded-transactions'; import type { AppState } from '../types/app-state'; import type { Dispatch } from '../types/redux'; @@ -92,6 +93,15 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ if (operationStatus && operationStatus.status === 'success') { clearInterval(interval); + if (from.startsWith('z')) { + saveShieldedTransaction({ + category: 'send', + time: Date.now() / 1000, + address: '(Shielded)', + amount: new BigNumber(amount).toNumber(), + memo, + }); + } dispatch(sendTransactionSuccess({ operationId: operationStatus.result.txid })); } diff --git a/app/containers/transactions.js b/app/containers/transactions.js index d8534a9..1ab4818 100644 --- a/app/containers/transactions.js +++ b/app/containers/transactions.js @@ -14,9 +14,10 @@ import { loadTransactionsError, } from '../redux/modules/transactions'; import rpc from '../../services/api'; +import { listShieldedTransactions } from '../../services/shielded-transactions'; import store from '../../config/electron-store'; -import { sortBy } from '../utils/sort-by'; +import { sortByDescend } from '../utils/sort-by-descend'; import type { AppState } from '../types/app-state'; import type { Dispatch } from '../types/redux'; @@ -32,7 +33,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ getTransactions: async () => { dispatch(loadTransactions()); - const [transactionsErr, transactions = []] = await eres(rpc.listtransactions()); + const [transactionsErr, transactions = []] = await eres(rpc.listtransactions('', 200)); if (transactionsErr) { return dispatch(loadTransactionsError({ error: transactionsErr.message })); @@ -49,10 +50,11 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')), obj => Object.keys(obj).map(day => ({ day, - list: sortBy('date')(obj[day]), + jsDay: new Date(day), + list: sortByDescend('date')(obj[day]), })), - sortBy('day'), - ])(transactions); + sortByDescend('jsDay'), + ])([...transactions, ...listShieldedTransactions()]); dispatch( loadTransactionsSuccess({ diff --git a/app/utils/sort-by-descend.js b/app/utils/sort-by-descend.js new file mode 100644 index 0000000..fafa6ed --- /dev/null +++ b/app/utils/sort-by-descend.js @@ -0,0 +1,5 @@ +// @flow +/* eslint-disable max-len */ + +// $FlowFixMe +export const sortByDescend = (field: string) => (arr: T[]): T[] => arr.sort((a, b) => (a[field] < b[field] ? 1 : -1)); diff --git a/app/utils/sort-by.js b/app/utils/sort-by.js index 33ab020..8c506fd 100644 --- a/app/utils/sort-by.js +++ b/app/utils/sort-by.js @@ -1,5 +1,5 @@ // @flow - /* eslint-disable max-len */ + // $FlowFixMe -export const sortBy = (field: string) => (arr: T[]): T[] => arr.sort((a, b) => (a[field] < b[field] ? 1 : -1)); +export const sortBy = (field: string) => (arr: T[]): T[] => arr.sort((a, b) => (a[field] > b[field] ? 1 : -1)); diff --git a/app/utils/truncate-address.js b/app/utils/truncate-address.js index 978a324..1320a62 100644 --- a/app/utils/truncate-address.js +++ b/app/utils/truncate-address.js @@ -1,6 +1,4 @@ // @flow - -export const truncateAddress = (address: string = '') => `${address.substr(0, 20)}...${address.substr( - address.length - 10, - address.length, -)}`; +export const truncateAddress = (address: string = '') => (address.length < 20 + ? address + : `${address.substr(0, 20)}...${address.substr(address.length - 10, address.length)}`); diff --git a/app/views/send.js b/app/views/send.js index 6477bc6..52a0e70 100644 --- a/app/views/send.js +++ b/app/views/send.js @@ -159,11 +159,9 @@ const ConfirmItemWrapper = styled(RowComponent)` width: 100%; `; -type ItemLabelProps = - | { - color: string, - } - | Object; +type ItemLabelProps = { + color: string, +}; /* eslint-disable max-len */ const ItemLabel = styled(TextComponent)` font-weight: ${(props: PropsWithTheme) => String(props.theme.fontWeight.bold)}; @@ -227,7 +225,7 @@ const MaxAvailableAmount = styled.button` background: none; color: white; cursor: pointer; - border-left: ${props => `1px solid ${props.theme.colors.background}`}; + border-left: ${props => `1px solid ${props.theme.colors.background(props)}`}; opacity: 0.8; &:hover { diff --git a/flow-custom-typedefs/electron-store.js b/flow-custom-typedefs/electron-store.js index d01bd5d..70492a4 100644 --- a/flow-custom-typedefs/electron-store.js +++ b/flow-custom-typedefs/electron-store.js @@ -10,9 +10,9 @@ declare module 'electron-store' { fileExtension?: string, }): ElectronStore; - set(key: string, value: string): void; + set(key: string, value: any): void; set(payload: Object): void; - get(key: string): string; + get(key: string): any; has(key: string): boolean; delete(key: string): void; clear(): void; diff --git a/services/shielded-transactions.js b/services/shielded-transactions.js new file mode 100644 index 0000000..8b15260 --- /dev/null +++ b/services/shielded-transactions.js @@ -0,0 +1,34 @@ +// @flow +import electronStore from '../config/electron-store'; + +const STORE_KEY = 'SHIELDED_TRANSACTIONS'; + +type ShieldedTransaction = {| + category: 'send' | 'receive', + time: number, + address: string, + amount: number, + memo: ?string, +|}; + +// eslint-disable-next-line +export const listShieldedTransactions = (): Array => electronStore.has(STORE_KEY) ? electronStore.get(STORE_KEY) : []; + +export const saveShieldedTransaction = ({ + category, + time, + address, + amount, + memo, +}: ShieldedTransaction): void => { + electronStore.set( + STORE_KEY, + listShieldedTransactions().concat({ + category, + time, + address, + amount, + memo: memo || '', + }), + ); +};