import { createSelector } from 'reselect' // ------------------------------------ // Initial State // ------------------------------------ const initialState = { filterPulldown: false, filter: { key: 'ALL_ACTIVITY', name: 'All Activity' }, filters: [ { key: 'ALL_ACTIVITY', name: 'All' }, { key: 'SENT_ACTIVITY', name: 'Sent' }, { key: 'REQUESTED_ACTIVITY', name: 'Requested' }, { key: 'PENDING_ACTIVITY', name: 'Pending' } ], modal: { modalType: null, modalProps: {}, showCurrencyFilters: false }, searchActive: false, searchText: '' } // ------------------------------------ // Constants // ------------------------------------ export const SHOW_ACTIVITY_MODAL = 'SHOW_ACTIVITY_MODAL' export const HIDE_ACTIVITY_MODAL = 'HIDE_ACTIVITY_MODAL' export const CHANGE_FILTER = 'CHANGE_FILTER' export const TOGGLE_PULLDOWN = 'TOGGLE_PULLDOWN' export const SET_ACTIVITY_MODAL_CURRENCY_FILTERS = 'SET_ACTIVITY_MODAL_CURRENCY_FILTERS' export const UPDATE_SEARCH_ACTIVE = 'UPDATE_SEARCH_ACTIVE' export const UPDATE_SEARCH_TEXT = 'UPDATE_SEARCH_TEXT' // ------------------------------------ // Actions // ------------------------------------ export function showActivityModal(modalType, modalProps) { return { type: SHOW_ACTIVITY_MODAL, modalType, modalProps } } export function hideActivityModal() { return { type: HIDE_ACTIVITY_MODAL } } export function changeFilter(filter) { return { type: CHANGE_FILTER, filter } } export function toggleFilterPulldown() { return { type: TOGGLE_PULLDOWN } } export function updateSearchActive(searchActive) { return { type: UPDATE_SEARCH_ACTIVE, searchActive } } export function updateSearchText(searchText) { return { type: UPDATE_SEARCH_TEXT, searchText } } export function setActivityModalCurrencyFilters(showCurrencyFilters) { return { type: SET_ACTIVITY_MODAL_CURRENCY_FILTERS, showCurrencyFilters } } // ------------------------------------ // Action Handlers // ------------------------------------ const ACTION_HANDLERS = { [SHOW_ACTIVITY_MODAL]: (state, { modalType, modalProps }) => ({ ...state, modal: { modalType, modalProps } }), [HIDE_ACTIVITY_MODAL]: state => ({ ...state, modal: { modalType: null, modalProps: {} } }), [CHANGE_FILTER]: (state, { filter }) => ({ ...state, filter, filterPulldown: false }), [TOGGLE_PULLDOWN]: state => ({ ...state, filterPulldown: !state.filterPulldown }), [SET_ACTIVITY_MODAL_CURRENCY_FILTERS]: (state, { showCurrencyFilters }) => ({ ...state, modal: { modalType: state.modal.modalType, modalProps: state.modal.modalProps, showCurrencyFilters } }), [UPDATE_SEARCH_ACTIVE]: (state, { searchActive }) => ({ ...state, searchActive }), [UPDATE_SEARCH_TEXT]: (state, { searchText }) => ({ ...state, searchText }) } // ------------------------------------ // Selectors // ------------------------------------ const activitySelectors = {} const filtersSelector = state => state.activity.filters const filterSelector = state => state.activity.filter const searchSelector = state => state.activity.searchText const paymentsSelector = state => state.payment.payments const invoicesSelector = state => state.invoice.invoices const transactionsSelector = state => state.transaction.transactions const invoiceExpired = invoice => { const expiresAt = parseInt(invoice.creation_date, 10) + parseInt(invoice.expiry, 10) return expiresAt < Date.now() / 1000 } // helper function that returns invoice, payment or transaction timestamp function returnTimestamp(transaction) { // if on-chain txn if (Object.prototype.hasOwnProperty.call(transaction, 'time_stamp')) { return transaction.time_stamp } // if invoice that has been paid if (transaction.settled) { return transaction.settle_date } // if invoice that has not been paid or an LN payment return transaction.creation_date } // getMonth() returns the month in 0 index (0 for Jan), so we create an arr of the // string representation we want for the UI const months = [ 'Jan', 'Feb', 'Mar', 'April', 'May', 'June', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] // groups the data by day function groupData(data) { return data.reduce((arr, el) => { const d = new Date(returnTimestamp(el) * 1000) const date = d.getDate() const title = `${months[d.getMonth()]} ${date}, ${d.getFullYear()}` if (!arr[title]) { arr[title] = [] } arr[title].push({ el }) // sort the activity within a day new -> old arr[title].sort((a, b) => returnTimestamp(b.el) - returnTimestamp(a.el)) return arr }, {}) } // takes the result of groupData and returns an array function groupArray(data) { return Object.keys(data).map(title => ({ title, activity: data[title] })) } // sorts data form new to old according to the timestamp function sortNewToOld(data) { return data.sort((a, b) => new Date(b.title).getTime() - new Date(a.title).getTime()) } // take in a dataset and return an array grouped by day function groupAll(data) { const groups = groupData(data) const groupArrays = groupArray(groups) return sortNewToOld(groupArrays) } const allActivity = createSelector( searchSelector, paymentsSelector, invoicesSelector, transactionsSelector, (searchText, payments, invoices, transactions) => { const searchedArr = [...payments, ...invoices, ...transactions].filter(tx => { if ( (tx.tx_hash && tx.tx_hash.includes(searchText)) || (tx.payment_hash && tx.payment_hash.includes(searchText)) || (tx.payment_request && tx.payment_request.includes(searchText)) ) { return true } return false }) if (!searchedArr.length) { return [] } return groupAll(searchedArr) } ) const invoiceActivity = createSelector(invoicesSelector, invoices => groupAll(invoices)) const sentActivity = createSelector( transactionsSelector, paymentsSelector, (transactions, payments) => groupAll([...transactions.filter(transaction => !transaction.received), ...payments]) ) const pendingActivity = createSelector(invoicesSelector, invoices => groupAll(invoices.filter(invoice => !invoice.settled && !invoiceExpired(invoice))) ) const FILTERS = { ALL_ACTIVITY: allActivity, SENT_ACTIVITY: sentActivity, REQUESTED_ACTIVITY: invoiceActivity, PENDING_ACTIVITY: pendingActivity } activitySelectors.currentActivity = createSelector(filterSelector, filter => FILTERS[filter.key]) activitySelectors.nonActiveFilters = createSelector( filtersSelector, filterSelector, (filters, filter) => filters.filter(f => f.key !== filter.key) ) export { activitySelectors } // ------------------------------------ // Reducer // ------------------------------------ export default function activityReducer(state = initialState, action) { const handler = ACTION_HANDLERS[action.type] return handler ? handler(state, action) : state }