Add models, reducers, actions and selectors for notification handling

This commit is contained in:
Germán Martínez 2019-09-25 15:08:33 +02:00
parent 4335ec6c09
commit d4ed0d7f4a
8 changed files with 142 additions and 9 deletions

View File

@ -0,0 +1,8 @@
// @flow
import { createAction } from 'redux-actions'
export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR'
const closeSnackbar = createAction<string, *>(CLOSE_SNACKBAR)
export default closeSnackbar

View File

@ -0,0 +1,24 @@
// @flow
import { createAction } from 'redux-actions'
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import { type GlobalState } from '~/store'
import { type NotificationProps } from '~/logic/notifications/store/models/notification'
export const ENQUEUE_SNACKBAR = 'ENQUEUE_SNACKBAR'
const addSnackbar = createAction<string, *, *>(ENQUEUE_SNACKBAR, (notification: NotificationProps): ActionReturn => ({
notification,
}))
const enqueueSnackbar = (notification: NotificationProps) => (
dispatch: ReduxDispatch<GlobalState>,
getState: GetState<GlobalState>,
) => {
const newNotification = {
...notification,
key: new Date().getTime() + Math.random(),
}
dispatch(addSnackbar(newNotification))
}
export default enqueueSnackbar

View File

@ -0,0 +1,8 @@
// @flow
import { createAction } from 'redux-actions'
export const REMOVE_SNACKBAR = 'REMOVE_SNACKBAR'
const removeSnackbar = createAction<string, *>(REMOVE_SNACKBAR)
export default removeSnackbar

View File

@ -0,0 +1,17 @@
// @flow
import { Record } from 'immutable'
import type { RecordFactory, RecordOf } from 'immutable'
export type NotificationProps = {
message: string,
options: Object,
dismissed: boolean,
}
export const makeNotification: RecordFactory<NotificationProps> = Record({
message: '',
options: {},
dismissed: false,
})
export type Notification = RecordOf<NotificationProps>

View File

@ -0,0 +1,38 @@
// @flow
import { Map } from 'immutable'
import { handleActions, type ActionType } from 'redux-actions'
import { makeNotification, type NotificationProps } from '~/logic/notifications/store/models/notification'
import { ENQUEUE_SNACKBAR } from '../actions/enqueueSnackbar'
import { CLOSE_SNACKBAR } from '../actions/closeSnackbar'
import { REMOVE_SNACKBAR } from '../actions/removeSnackbar'
export const NOTIFICATIONS_REDUCER_ID = 'notifications'
export type NotificationReducerState = Map<string, *>
export default handleActions<NotificationReducerState, *>(
{
[ENQUEUE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
const { notification }: { notification: NotificationProps } = action.payload
if (state.hasIn(['notifications', notification.options.key])) {
return state.updateIn(['notifications', notification.options.key], (prev) => prev.merge(notification))
}
return state.setIn(['notifications', notification.options.key], makeNotification(notification))
},
[CLOSE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
const { notification }: { notification: NotificationProps } = action.payload
notification.dismissed = true
return state.updateIn(['notifications', notification.key], (prev) => prev.merge(notification))
},
[REMOVE_SNACKBAR]: (state: NotificationReducerState, action: ActionType<Function>): NotificationReducerState => {
const key = action.payload
return state.deleteIn(['notification', key])
},
},
Map({
notifications: Map(),
}),
)

View File

@ -0,0 +1,15 @@
// @flow
import { List, Map } from 'immutable'
import { createSelector, type Selector } from 'reselect'
import { type GlobalState } from '~/store'
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
import { type Notification } from '~/logic/notifications/store/models/notification'
export const notificationsMapSelector = (
state: GlobalState,
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]
export const notificationsListSelector: Selector<GlobalState, {}, List<Notification>> = createSelector(
notificationsMapSelector,
(notifications: Map<string, Notification>): List<Notification> => notifications.toList(),
)

View File

@ -4,6 +4,8 @@ import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
import { makeProvider } from '~/logic/wallets/store/model/provider'
import { NOTIFICATIONS } from '~/logic/notifications'
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
import closeSnackbar from '~/logic/notifications/store/actions/closeSnackbar'
import addProvider from './addProvider'
export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: ProviderProps) => {
@ -22,7 +24,7 @@ export const processProviderResponse = (dispatch: ReduxDispatch<*>, provider: Pr
dispatch(addProvider(walletRecord))
}
const handleProviderNotification = (enqueueSnackbar: Function, closeSnackbar: Function, provider: ProviderProps) => {
const handleProviderNotification = (dispatch: ReduxDispatch<*>, provider: ProviderProps) => {
const { loaded, available, network } = provider
if (!loaded) {
@ -40,15 +42,26 @@ const handleProviderNotification = (enqueueSnackbar: Function, closeSnackbar: Fu
enqueueSnackbar(NOTIFICATIONS.RINKEBY_VERSION_MSG.description, NOTIFICATIONS.RINKEBY_VERSION_MSG.options)
if (available) {
enqueueSnackbar(NOTIFICATIONS.WALLET_CONNECTED_MSG.description, NOTIFICATIONS.WALLET_CONNECTED_MSG.options)
// NOTE:
// if you want to be able to dispatch a `closeSnackbar` action later on,
// you SHOULD pass your own `key` in the options. `key` can be any sequence
// of number or characters, but it has to be unique to a given snackbar.
dispatch(
enqueueSnackbar({
message: NOTIFICATIONS.WALLET_CONNECTED_MSG.description,
options: {
...NOTIFICATIONS.WALLET_CONNECTED_MSG.options,
key: new Date().getTime() + Math.random(),
},
}),
)
// enqueueSnackbar(NOTIFICATIONS.WALLET_CONNECTED_MSG.description, NOTIFICATIONS.WALLET_CONNECTED_MSG.options)
} else {
enqueueSnackbar(NOTIFICATIONS.UNLOCK_WALLET_MSG.description, NOTIFICATIONS.UNLOCK_WALLET_MSG.options)
}
}
export default (provider: ProviderProps, enqueueSnackbar: Function, closeSnackbar: Function) => (
dispatch: ReduxDispatch<*>,
) => {
handleProviderNotification(enqueueSnackbar, closeSnackbar, provider)
export default (provider: ProviderProps) => (dispatch: ReduxDispatch<*>) => {
handleProviderNotification(dispatch, provider)
processProviderResponse(dispatch, provider)
}

View File

@ -5,14 +5,18 @@ import {
combineReducers, createStore, applyMiddleware, compose, type Reducer, type Store,
} from 'redux'
import thunk from 'redux-thunk'
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
import safe, { SAFE_REDUCER_ID, type SafeReducerState as SafeState } from '~/routes/safe/store/reducer/safe'
import safeStorage from '~/routes/safe/store/middleware/safeStorage'
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
import transactions, {
type State as TransactionsState,
TRANSACTIONS_REDUCER_ID,
} from '~/routes/safe/store/reducer/transactions'
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
import notifications, {
NOTIFICATIONS_REDUCER_ID,
type State as NotificationsState,
} from '~/logic/notifications/store/reducer/notifications'
export const history = createBrowserHistory()
@ -25,6 +29,7 @@ export type GlobalState = {
safes: SafeState,
tokens: TokensState,
transactions: TransactionsState,
notifications: NotificationsState,
}
export type GetState = () => GlobalState
@ -35,8 +40,13 @@ const reducers: Reducer<GlobalState> = combineReducers({
[SAFE_REDUCER_ID]: safe,
[TOKEN_REDUCER_ID]: tokens,
[TRANSACTIONS_REDUCER_ID]: transactions,
[NOTIFICATIONS_REDUCER_ID]: notifications,
})
export const store: Store<GlobalState> = createStore(reducers, finalCreateStore)
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(reducers, localState, finalCreateStore)
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(
reducers,
localState,
finalCreateStore,
)