diff --git a/app/containers/transactions.js b/app/containers/transactions.js new file mode 100644 index 0000000..6789a0f --- /dev/null +++ b/app/containers/transactions.js @@ -0,0 +1,61 @@ +// @flow +import { connect } from 'react-redux'; +import eres from 'eres'; +import flow from 'lodash.flow'; +import groupBy from 'lodash.groupby'; +import dateFns from 'date-fns'; + +import { TransactionsView } from '../views/transactions'; +import { + loadTransactions, + loadTransactionsSuccess, + loadTransactionsError, +} from '../redux/modules/transactions'; +import rpc from '../../services/api'; +import store from '../../config/electron-store'; + +import type { AppState } from '../types/app-state'; +import type { Dispatch } from '../types/redux'; + +const mapStateToProps = ({ transactions }: AppState) => ({ + transactions: transactions.list, + isLoading: transactions.isLoading, + error: transactions.error, + zecPrice: transactions.zecPrice, +}); + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + getTransactions: async () => { + dispatch(loadTransactions()); + + const [transactionsErr, transactions = []] = await eres( + rpc.listtransactions(), + ); + + if (transactionsErr) { + return dispatch( + loadTransactionsError({ error: transactionsErr.message }), + ); + } + + dispatch( + loadTransactionsSuccess({ + list: flow([ + arr => arr.map(transaction => ({ + type: transaction.category, + date: new Date(transaction.time * 1000).toISOString(), + address: transaction.address, + amount: Math.abs(transaction.amount), + })), + arr => groupBy(arr, obj => dateFns.format(obj.date, 'MMM DD, YYYY')), + ])(transactions), + zecPrice: store.get('ZEC_DOLLAR_PRICE'), + }), + ); + }, +}); + +export const TransactionsContainer = connect( + mapStateToProps, + mapDispatchToProps, +)(TransactionsView); diff --git a/app/redux/modules/reducer.js b/app/redux/modules/reducer.js index 87ad85a..8c9e2ad 100644 --- a/app/redux/modules/reducer.js +++ b/app/redux/modules/reducer.js @@ -5,8 +5,10 @@ import { connectRouter } from 'connected-react-router'; import type { RouterHistory } from 'react-router-dom'; import wallet from './wallet'; +import transactions from './transactions'; export const createRootReducer = (history: RouterHistory) => combineReducers({ walletSummary: wallet, + transactions, router: connectRouter(history), }); diff --git a/app/redux/modules/transactions.js b/app/redux/modules/transactions.js new file mode 100644 index 0000000..2529ebb --- /dev/null +++ b/app/redux/modules/transactions.js @@ -0,0 +1,64 @@ +// @flow +import type { Action } from '../../types/redux'; +import type { Transaction } from '../../components/transaction-item'; + +// Actions +export const LOAD_TRANSACTIONS = 'LOAD_TRANSACTIONS'; +export const LOAD_TRANSACTIONS_SUCCESS = 'LOAD_TRANSACTIONS_SUCCESS'; +export const LOAD_TRANSACTIONS_ERROR = 'LOAD_TRANSACTIONS_ERROR'; + +export const loadTransactions = () => ({ + type: LOAD_TRANSACTIONS, + payload: {}, +}); + +export const loadTransactionsSuccess = ({ + list, + zecPrice, +}: { + list: { [day: string]: Transaction[] }, + zecPrice: number, +}) => ({ + type: LOAD_TRANSACTIONS_SUCCESS, + payload: { + list, + zecPrice, + }, +}); + +export const loadTransactionsError = ({ error }: { error: string }) => ({ + type: LOAD_TRANSACTIONS_ERROR, + payload: { error }, +}); + +export type State = { + isLoading: boolean, + error: string | null, + list: { [day: string]: Transaction[] }, + zecPrice: number, +}; + +const initialState = { + zecPrice: 0, + list: {}, + error: null, + isLoading: false, +}; + +export default (state: State = initialState, action: Action) => { + switch (action.type) { + case LOAD_TRANSACTIONS: + return { ...state, error: null, isLoading: true }; + case LOAD_TRANSACTIONS_SUCCESS: + return { + ...state, + ...action.payload, + isLoading: false, + error: null, + }; + case LOAD_TRANSACTIONS_ERROR: + return { ...state, isLoading: false, error: action.payload.error }; + default: + return state; + } +}; diff --git a/app/router/router.js b/app/router/router.js index d3eaaff..9b6050b 100644 --- a/app/router/router.js +++ b/app/router/router.js @@ -7,6 +7,7 @@ import styled from 'styled-components'; import { ScrollTopComponent } from './scroll-top'; import { SidebarContainer } from '../containers/sidebar'; import { DashboardContainer } from '../containers/dashboard'; +import { TransactionsContainer } from '../containers/transactions'; import { SendView } from '../views/send'; import { ReceiveView } from '../views/receive'; import { SettingsView } from '../views/settings'; @@ -21,6 +22,7 @@ import { RECEIVE_ROUTE, SETTINGS_ROUTE, CONSOLE_ROUTE, + TRANSACTIONS_ROUTE, } from '../constants/routes'; const FullWrapper = styled.div` @@ -59,6 +61,10 @@ export const RouterComponent = ({ location }: { location: Location }) => ( + diff --git a/app/types/app-state.js b/app/types/app-state.js index 684a66c..281fa60 100644 --- a/app/types/app-state.js +++ b/app/types/app-state.js @@ -1,7 +1,9 @@ // @flow import type { State as WalletSummaryState } from '../redux/modules/wallet'; +import type { State as TransactionsState } from '../redux/modules/transactions'; export type AppState = { walletSummary: WalletSummaryState, + transactions: TransactionsState, }; diff --git a/app/views/transactions.js b/app/views/transactions.js new file mode 100644 index 0000000..beb89c1 --- /dev/null +++ b/app/views/transactions.js @@ -0,0 +1,47 @@ +// @flow +import React, { Component, Fragment } from 'react'; + +import { TransactionDailyComponent } from '../components/transaction-daily'; + +import type { Transaction } from '../components/transaction-item'; + +type Props = { + isLoading: boolean, + error: string | null, + transactions: { [day: string]: Transaction[] }, + zecPrice: number, + getTransactions: () => void, +}; + +export class TransactionsView extends Component { + componentDidMount() { + // eslint-disable-next-line + this.props.getTransactions(); + } + + render() { + const { + error, isLoading, transactions, zecPrice, + } = this.props; + + if (error) { + return error; + } + + const days = Object.keys(transactions); + + return ( + + {isLoading + ? 'Loading' + : days.map(day => ( + + ))} + + ); + } +}