UNPKG

@codetanzania/ewea-api-states

Version:
1,466 lines (1,344 loc) 43.3 kB
'use strict'; const forIn = require('lodash/forIn'); const get = require('lodash/get'); const forEach = require('lodash/forEach'); const merge = require('lodash/merge'); const isFunction = require('lodash/isFunction'); const isObject = require('lodash/isObject'); const PropTypes = require('prop-types'); const React = require('react'); const reactRedux = require('react-redux'); const redux = require('redux'); const toolkit = require('@reduxjs/toolkit'); const eweaApiClient = require('@codetanzania/ewea-api-client'); const inflection = require('inflection'); const upperFirst = require('lodash/upperFirst'); const camelCase = require('lodash/camelCase'); const map = require('lodash/map'); const cloneDeep = require('lodash/cloneDeep'); const isEmpty = require('lodash/isEmpty'); const lowerFirst = require('lodash/lowerFirst'); const pick = require('lodash/pick'); /** * @function * @name camelize * @description Joins names and generate camelCase of joined words them * * @param {...string} words list of words to join and camelize * @returns {string} camelCase of joined words * * @version 0.1.0 * @since 0.1.0 */ function camelize(...words) { return camelCase([...words].join(' ')); } /** * @function * @name wrapActionsWithDispatch * @description Wrap actions with dispatch function. Make users to just * invoke actions without have to dispatch them. * * @param {object} actions list of redux actions * @param {Function} dispatch store dispatch function * @returns {object} map of redux action wrapped with dispatch function * * @version 0.1.0 * @since 0.1.0 */ function wrapActionsWithDispatch(actions, dispatch) { const wrappedActions = {}; forIn(actions, (fn, key) => { wrappedActions[key] = (...params) => dispatch(fn(...params)); }); return wrappedActions; } /** * @function * @name extractReducers * @description Extract all resource reducers into a single object * @param {string[]} resources list of exposed API resources * @param {object[]} slices list of resource slices * @returns {object} map of all resources reducers * @version 0.1.0 * @since 0.1.0 */ function extractReducers(resources, slices) { const reducers = {}; // reducers resources.forEach(resource => { reducers[inflection.pluralize(resource)] = slices[resource].reducer; }); return reducers; } /** * @function * @name extractReportReducers * @description Extract all resource reducers into a single object * @param {string[]} reports list of exposed API reports * @param {object[]} slices list of resource slices * @returns {object} map of all reports reducers * @version 0.1.0 * @since 0.20.0 */ function extractReportReducers(reports, slices) { const reducers = {}; reports.forEach(report => { reducers[`${inflection.pluralize(report)}Report`] = slices[`${report}Report`].reducer; }); return reducers; } /** * @function * @name extractActions * @description Extracts all actions from all slices into into a single object * @param {string[]} resources list of api resources * @param {object[]} slices list of all resources slices * @param {boolean} isReportActions Flag to indicate extracting report actions * from slice * @returns {object} map of all resources actions * @version 0.1.0 * @since 0.1.0 */ function extractActions(resources, slices, isReportActions = false) { const actions = {}; resources.forEach(resource => { const key = isReportActions ? `${resource}Report` : resource; actions[key] = slices[key].actions; }); return actions; } /** * @function * @name normalizedError * @description normalize error object from the client to be stored in redux * store * * @param {object} error error object from the client * @returns {object} normalizedError Error object with normalized messages * * @version 0.1.0 * @since 0.10.2 */ function normalizeError(error) { const normalizedError = cloneDeep(error); const errors = get(normalizedError, 'errors', null); if (errors) { forIn(errors, (value, key) => { errors[key] = `${value.name} : ${value.message}`; }); normalizedError.errors = errors; } return normalizedError; } /** * @function * @name getPartyPermissionsWildcards * @description Extract wildcards from party permissions * @param {object} party Authenticated party * @returns {string[]} Wildcards extracted from permissions * @version 0.1.0 * @since 0.30.0 */ function getPartyPermissionsWildcards(party) { const permissions = get(party, 'role.relations.permissions', []); return map(permissions, 'wildcard'); } /** * @function * @name getDefaultReducers * @description Generate defaultReducers object * * @param {string} resourceName Resource name * @returns {object} Resource reducers * * @version 0.2.0 * @since 0.1.0 */ function getDefaultReducers(resourceName) { const plural = upperFirst(inflection.pluralize(resourceName)); const singular = upperFirst(inflection.singularize(resourceName)); return { [camelize('select', singular)]: (state, action) => ({ ...state, selected: action.payload }), [camelize('filter', plural)]: (state, action) => ({ ...state, filter: action.payload }), [camelize('sort', plural)]: (state, action) => ({ ...state, sort: action.payload }), [camelize('search', plural)]: (state, action) => ({ ...state, q: action.payload }), [camelize('clear', plural, 'filters')]: state => ({ ...state, filters: null }), [camelize('clear', plural, 'sort')]: state => ({ ...state, sort: null }), [camelize('get', plural, 'Request')]: state => ({ ...state, loading: true }), [camelize('get', plural, 'Success')]: (state, action) => ({ ...state, list: [...action.payload.data], page: action.payload.page, total: action.payload.total, size: action.payload.size, hasMore: action.payload.hasMore, loading: false }), [camelize('get', plural, 'Failure')]: (state, action) => ({ ...state, error: action.payload, loading: false }), [camelize('load', 'more', plural, 'Request')]: state => ({ ...state, loading: true }), [camelize('load', 'more', plural, 'Success')]: (state, action) => ({ ...state, list: [...state.list, ...action.payload.data], page: action.payload.page, total: action.payload.total, size: action.payload.size, hasMore: action.payload.hasMore, loading: false }), [camelize('load', 'more', plural, 'Failure')]: (state, action) => ({ ...state, error: action.payload, loading: false }), [camelize('get', singular, 'Request')]: state => ({ ...state, loading: true }), [camelize('get', singular, 'Success')]: (state, action) => ({ ...state, selected: action.payload, loading: false }), [camelize('get', singular, 'Failure')]: (state, action) => ({ ...state, loading: false, error: action.payload }), [camelize('post', singular, 'Request')]: state => ({ ...state, posting: true }), [camelize('post', singular, 'Success')]: state => ({ ...state, posting: false, showForm: false }), [camelize('post', singular, 'Failure')]: (state, action) => ({ ...state, error: action.payload, posting: false }), [camelize('put', singular, 'Request')]: state => ({ ...state, posting: true }), [camelize('put', singular, 'Success')]: (state, action) => ({ ...state, selected: action.payload, posting: false, showForm: false }), [camelize('put', singular, 'Failure')]: (state, action) => ({ ...state, posting: false, error: action.payload }), [camelize('delete', singular, 'Request')]: state => ({ ...state, posting: true }), [camelize('delete', singular, 'Success')]: state => ({ ...state, posting: false }), [camelize('delete', singular, 'Failure')]: (state, action) => ({ ...state, posting: false, error: action.payload }), [camelize('open', singular, 'Form')]: state => ({ ...state, showForm: true }), [camelize('close', singular, 'Form')]: state => ({ ...state, showForm: false }), [camelize('set', singular, 'Schema')]: (state, action) => ({ ...state, schema: action.payload }) }; } /** * @function * @name getReportDefaultReducer * @description Generate defaultReducers for reports object * * @param {string} report Report Name * @returns {object} Report reducers * * @version 0.1.0 * @since 0.20.0 */ function getReportDefaultReducer(report) { const plural = inflection.pluralize(report); return { [camelize('get', plural, 'report', 'request')]: state => ({ ...state, loading: true }), [camelize('get', plural, 'report', 'success')]: (state, action) => ({ ...state, data: action.payload.data, loading: false }), [camelize('get', plural, 'report', 'failure')]: (state, action) => ({ ...state, loading: false, error: action.payload }) }; } /** * @function * @name getDefaultInitialState * @description Generate default initial State for resource * * @returns {object} Initial states of a resource * * @version 0.1.0 * @since 0.1.0 */ function getDefaultInitialState() { return { list: [], selected: null, page: 1, total: 1, pages: 1, size: 0, loading: false, posting: false, showForm: false, schema: null, filter: null, sort: null, q: undefined, hasMore: false }; } /** * @function * @name getDefaultInitialState * @description Create initial state for a report resource * @returns {object} initial state for report state * @version 0.1.0 * @since 0.20.0 */ function getDefaultReportInitialState() { return { data: null, error: null, loading: false }; } /** * @function * @name createSliceFor * @description Slice Factory which is used to create slice * * @param {string} sliceName Slice name which will results to be reducer name * @param {object} initialState Optional override of default initial state * @param {object} reducers Optional override of default reducers * @returns {object} slice resource slice * * @version 0.1.0 * @since 0.1.0 */ function createSliceFor(sliceName, initialState = null, reducers = null) { const defaultReducers = reducers || getDefaultReducers(sliceName); const initialDefaultState = initialState || getDefaultInitialState(); return toolkit.createSlice({ name: sliceName, initialState: initialDefaultState, reducers: defaultReducers }); } /* application action types */ const INITIALIZE_APP_START = 'app/initialize'; const INITIALIZE_APP_SUCCESS = 'app/initializeSuccess'; const INITIALIZE_APP_FAILURE = 'app/initializeFailure'; const SIGNIN_APP_START = 'app/signin'; const SIGNIN_APP_SUCCESS = 'app/signinSuccess'; const SIGNIN_APP_FAILURE = 'app/signinFailure'; const SIGNOUT = 'app/signout'; /* constants */ const appDefaultState = { loading: false, signing: false, error: null, party: eweaApiClient.getAuthenticatedParty(), permissions: getPartyPermissionsWildcards(eweaApiClient.getAuthenticatedParty()) }; /** * @function * @name createResourcesSlices * @description Create slices from all EWEA resources * * @param {string[]} resources list of api resources * @returns {object} slices resources slice * * @version 0.1.0 * @since 0.1.0 */ function createResourcesSlices(resources) { const slices = {}; // slices resources.forEach(resource => { slices[resource] = createSliceFor(resource); }); return slices; } /** * @function * @name createReportsSlices * @description Create slices from all EWEA reports * @param {string[]} reports List of exposed reports by the API * @returns {object} slices reports slice * @version 0.1.0 * @since 0.20.0 */ function createReportsSlices(reports) { const slices = {}; reports.forEach(report => { const key = `${report}Report`; slices[key] = createSliceFor(key, getDefaultReportInitialState(), getReportDefaultReducer(report)); }); return slices; } /** * @function * @name app * @description App reducer for controlling application initialization state * * @param {object} state previous app state value * @param {object} action dispatched action object * @returns {object} updated app state * * @version 0.2.0 * @since 0.1.0 */ function app(state = appDefaultState, action) { switch (action.type) { case INITIALIZE_APP_START: return { ...state, loading: true }; case INITIALIZE_APP_SUCCESS: return { ...state, loading: false }; case INITIALIZE_APP_FAILURE: return { ...state, loading: false, error: action.payload }; case SIGNIN_APP_START: return { ...state, signing: true }; case SIGNIN_APP_SUCCESS: return { ...state, party: action.payload.party, permissions: action.payload.permissions, signing: false }; case SIGNIN_APP_FAILURE: return { ...state, error: action.payload, signing: false }; case SIGNOUT: return { ...state, error: null, party: null }; default: return state; } } // all resources exposed by this library const resources = ['administrativeArea', 'administrativeLevel', 'agency', 'campaign', 'changelog', 'dispatch', 'event', 'eventAction', 'eventActionCatalogue', 'eventFunction', 'eventGroup', 'eventIndicator', 'eventLevel', 'eventSeverity', 'eventCertainty', 'eventStatus', 'eventUrgency', 'eventResponse', 'eventQuestion', 'eventTopic', 'eventType', 'feature', 'featureType', 'focalPerson', 'notificationTemplate', 'partyGender', 'partyGroup', 'partyOwnership', 'partyRole', 'partyOccupation', 'partyNationality', 'permission', 'priority', 'unit', 'vehicle', 'vehicleModel', 'vehicleMake', 'vehicleStatus', 'vehicleType', 'case', 'caseStage', 'caseSeverity']; // Exposed reports by the API const REPORTS = ['action', 'alert', 'case', 'dispatch', 'effect', 'event', 'indicator', 'need', 'overview', 'party', 'resource', 'risk']; // create crud resources slices const slices = createResourcesSlices(resources); // create reports slices const reportSlices = createReportsSlices(REPORTS); // merge reducers const reducers = merge({}, extractReducers(resources, slices), extractReportReducers(REPORTS, reportSlices), { app }); const rootReducer = redux.combineReducers(reducers); const store = toolkit.configureStore({ reducer: rootReducer, devTools: process.env.NODE_ENV === 'development' }); const actions = { ...extractActions(resources, slices), ...extractActions(REPORTS, reportSlices, true) }; const { dispatch } = store; /** * @function * @name createThunkFor * @description Create and expose all common thunks for a resource. * * Custom thunk implementations can be added to the specific resource * actions module * * @param {string} resource resource name * @returns {object} thunks resource thunks * * @version 0.3.0 * @since 0.1.0 */ function createThunksFor(resource) { const pluralName = upperFirst(inflection.pluralize(resource)); const singularName = upperFirst(inflection.singularize(resource)); const resourceName = lowerFirst(singularName); const storeKey = lowerFirst(pluralName); const thunks = {}; /** * @function * @name getResources * @description A thunk that will be dispatched when fetching data from API * * @param {object} param Param object to be passed to API client * @param {Function} onSuccess Callback to be called when fetching * resources from the API succeed * @param {Function} onError Callback to be called when fetching * resources from the API fails * @returns {Function} Thunk function * * @version 0.2.0 * @since 0.1.0 */ thunks[camelize('get', pluralName)] = (param, onSuccess, onError) => dispatch => { dispatch(actions[resourceName][camelize('get', pluralName, 'request')]()); return eweaApiClient.httpActions[camelize('get', pluralName)](param).then(data => { dispatch(actions[resourceName][camelize('get', pluralName, 'success')](data)); // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('get', pluralName, 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; /** * @function * @name getResource * @description A thunk that will be dispatched when fetching * single resource data from the API * * @param {string} id Resource unique identification * @param {Function} onSuccess Callback to be called when getting a * resource from the API succeed * @param {Function} onError Callback to be called when getting a resource * from the API fails * @returns {Function} Thunk function * * @version 0.2.0 * @since 0.1.0 */ thunks[camelize('get', singularName)] = (id, onSuccess, onError) => dispatch => { dispatch(actions[resourceName][camelize('get', singularName, 'request')]()); return eweaApiClient.httpActions[camelize('get', singularName)](id).then(data => { dispatch(actions[resourceName][camelize('get', singularName, 'success')](data)); // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('get', singularName, 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; /** * @function * @name postResource * @description A thunk that will be dispatched when creating a single * resource data in the API * * @param {object} param Resource object to be created/Saved * @param {Function} onSuccess Callback to be executed when posting a * resource succeed * @param {Function} onError Callback to be executed when posting * resource fails * @param {object} options Additional options params i.e {filters:{}} will be * applied on successfully post action * @returns {Function} Thunk function * * @version 0.2.0 * @since 0.1.0 */ thunks[camelize('post', singularName)] = (param, onSuccess, onError, options) => (dispatch, getState) => { dispatch(actions[resourceName][camelize('post', singularName, 'request')]()); return eweaApiClient.httpActions[camelize('post', singularName)](param).then(data => { dispatch(actions[resourceName][camelize('post', singularName, 'success')](data)); dispatch(actions[resourceName][camelize('clear', pluralName, 'filters')]()); dispatch(actions[resourceName][camelize('clear', pluralName, 'sort')]()); dispatch(actions[resourceName][camelize('search', pluralName)]()); if (!isEmpty(options) && !isEmpty(options.filters)) { dispatch(actions[resourceName][camelize('filter', pluralName)](options.filters)); const { filter } = getState()[storeKey]; dispatch(thunks[camelize('get', pluralName)]({ filter })); } else { dispatch(thunks[camelize('get', pluralName)]()); } // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('post', singularName, 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; /** * @function * @name putResource * @description A thunk that will be dispatched when updating a single * resource data in the API * * @param {object} param Resource object to be updated * @param {Function} onSuccess Callback to be executed when updating a * resource succeed * @param {Function} onError Callback to be executed when updating a * resource fails * @returns {Function} Thunk function * * @version 0.2.0 * @since 0.1.0 */ thunks[camelize('put', singularName)] = (param, onSuccess, onError) => dispatch => { dispatch(actions[resourceName][camelize('put', singularName, 'request')]()); return eweaApiClient.httpActions[camelize('put', singularName)](param).then(data => { dispatch(actions[resourceName][camelize('put', singularName, 'success')](data)); dispatch(actions[resourceName][camelize('clear', pluralName, 'filters')]()); dispatch(actions[resourceName][camelize('clear', pluralName, 'sort')]()); dispatch(actions[resourceName][camelize('search', pluralName)]()); dispatch(thunks[camelize('get', pluralName)]()); // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('put', singularName, 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; /** * @function * @name deleteResource * @description A thunk that will be dispatched when deleting/archiving * a single resource data in the API * * @param {string} id Resource unique identification * @param {Function} onSuccess Callback to be executed when updating a * resource succeed * @param {Function} onError Callback to be executed when updating a * resource fails * @returns {Function} Thunk function * * @version 0.2.0 * @since 0.1.0 */ thunks[camelize('delete', singularName)] = (id, onSuccess, onError) => (dispatch, getState) => { dispatch(actions[resourceName][camelize('delete', singularName, 'request')]()); return eweaApiClient.httpActions[camelize('delete', singularName)](id).then(data => { dispatch(actions[resourceName][camelize('delete', singularName, 'success')](data)); const { page, filter } = getState()[storeKey]; // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } return dispatch(thunks[camelize('get', pluralName)]({ page, filter })); }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('delete', singularName, 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; /** * @function * @name fetchResources * @description A thunk that for fetching data from the API the difference * between this and get thunk is this will apply all the criteria on fetch. * Pagination, filters, Search Query and sort. * * @param {Function} onSuccess Callback to be called when fetching * resources from the API succeed * @param {Function} onError Callback to be called when fetching * resources from the API fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('fetch', pluralName)] = (onSuccess, onError) => (dispatch, getState) => { const { page, sort, filter, q } = getState()[storeKey]; return dispatch(thunks[camelize('get', pluralName)]({ page, filter, sort, q }, onSuccess, onError)); }; /** * @function * @name filterResources * @description A thunk that will be dispatched when filtering resources * data in the API * * @param {object} filter Resource filter criteria object * @param {Function} onSuccess Callback to be executed when filtering * resources succeed * @param {Function} onError Callback to be executed when filtering * resources fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('filter', pluralName)] = (filter, onSuccess, onError) => dispatch => { dispatch(actions[resourceName][camelize('filter', pluralName)](filter)); return dispatch(thunks[camelize('get', pluralName)]({ filter }, onSuccess, onError)); }; /** * @function * @name refreshResources * @description A thunk that will be dispatched when refreshing resources * data in the API * * @param {Function} onSuccess Callback to be executed when refreshing * resources succeed * @param {Function} onError Callback to be executed when refreshing * resources fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('refresh', pluralName)] = (onSuccess, onError) => (dispatch, getState) => { const { page, filter, q } = getState()[storeKey]; return dispatch(thunks[camelize('get', pluralName)]({ page, filter, q }, onSuccess, onError)); }; /** * @function * @name searchResources * @description A thunk that will be dispatched when searching resources * data in the API * * @param {string} query Search query string * @param {Function} onSuccess Callback to be executed when searching * resources succeed * @param {Function} onError Callback to be executed when searching * resources fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('search', pluralName)] = (query, onSuccess, onError) => (dispatch, getState) => { dispatch(actions[resourceName][camelize('search', pluralName)](query)); const { filter } = getState()[storeKey]; return dispatch(thunks[camelize('get', pluralName)]({ q: query, filter }, onSuccess, onError)); }; /** * @function * @name sortResources * @description A thunk that will be dispatched when sorting resources * data in the API * * @param {object} order sort order object * @param {Function} onSuccess Callback to be executed when sorting * resources succeed * @param {Function} onError Callback to be executed when sorting * resources fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('sort', pluralName)] = (order, onSuccess, onError) => (dispatch, getState) => { const { page } = getState()[storeKey]; dispatch(actions[resourceName][camelize('sort', pluralName)](order)); return dispatch(thunks[camelize('get', pluralName)]({ page, sort: order }, onSuccess, onError)); }; /** * @function * @name paginateResources * @description A thunk that will be dispatched when paginating resources * data in the API * * @param {number} page paginate to page * @param {Function} onSuccess Callback to be executed when paginating * resources succeed * @param {Function} onError Callback to be executed when paginating * resources fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('paginate', pluralName)] = (page, onSuccess, onError) => (dispatch, getState) => { const { filter, q } = getState()[storeKey]; return dispatch(thunks[camelize('get', pluralName)]({ page, filter, q }, onSuccess, onError)); }; /** * @function * @name loadMoreResources * @description A thunk that will be dispatched when loading more data without * paginating resource data in the API * * @param {Function} onSuccess Callback to be executed when paginating * resources succeed * @param {Function} onError Callback to be executed when paginating * resources fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('load', 'more', pluralName)] = (onSuccess, onError) => (dispatch, getState) => { dispatch(actions[resourceName][camelize('load', 'more', pluralName, 'request')]()); const { page, filter, hasMore } = getState()[storeKey]; const nextPage = page + 1; if (!hasMore) { return undefined; } return eweaApiClient.httpActions[camelize('get', pluralName)]({ page: nextPage, filter }).then(data => { dispatch(actions[resourceName][camelize('load', 'more', pluralName, 'success')](data)); // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('load', 'more', pluralName, 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; /** * @function * @name clearReourceFilters * @description A thunk that will be dispatched when clearing filters on * resources data in the API * * @param {Function} onSuccess Callback to be executed when filters are * cleared and resources data is reloaded successfully * @param {Function} onError Callback to be executed when filters are * cleared and resources data fails to reload * @param {string[]} keep list of filter names to be kept * @returns {Function} Thunk Function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('clear', singularName, 'filters')] = (onSuccess, onError, keep = []) => (dispatch, getState) => { if (!isEmpty(keep)) { // keep specified filters let keptFilters = pick(getState()[storeKey].filter, keep); keptFilters = isEmpty(keptFilters) ? null : keptFilters; return dispatch(thunks[camelize('filter', pluralName)](keptFilters, onSuccess, onError)); } // clear all filters return dispatch(thunks[camelize('filter', pluralName)](null, onSuccess, onError)); }; /** * @function * @name clearResourcesSort * @description A thunk that will be dispatched when clearing sort order on * resources data in the API * * @param {Function} onSuccess Callback to be executed when sort are * cleared and resources data is reloaded successfully * @param {Function} onError Callback to be executed when sort are * cleared and resources data fails to reload * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.1.0 */ thunks[camelize('clear', pluralName, 'sort')] = (onSuccess, onError) => (dispatch, getState) => { const { page } = getState()[storeKey]; dispatch(actions[resourceName][camelize('clear', pluralName, 'sort')]()); return dispatch(thunks[camelize('get', pluralName)]({ page }, onSuccess, onError)); }; return thunks; } /** * @function * @name createReportThunkFor * @description Create thunk for reports as exposed by api client * @param {string} report Report name * @returns {object} thunks resource thunks * * @version 0.1.0 * @since 0.20.0 */ function createReportThunkFor(report) { const thunks = {}; const pluralName = upperFirst(inflection.pluralize(report)); const resourceName = `${lowerFirst(inflection.singularize(report))}Report`; /** * @function * @name getResources * @description A thunk that will be dispatched when fetching data from API * * @param {object} param Param object to be passed to API client * @param {Function} onSuccess Callback to be called when fetching * resources from the API succeed * @param {Function} onError Callback to be called when fetching * resources from the API fails * @returns {Function} Thunk function * * @version 0.1.0 * @since 0.20.0 */ thunks[camelize('get', pluralName, 'report')] = (param, onSuccess, onError) => dispatch => { dispatch(actions[resourceName][camelize('get', pluralName, 'report', 'request')]()); return eweaApiClient.httpActions[camelize('get', pluralName, 'report')](param).then(data => { dispatch(actions[resourceName][camelize('get', pluralName, 'report', 'success')](data)); // custom provided onSuccess callback if (isFunction(onSuccess)) { onSuccess(); } }).catch(error => { const normalizedError = normalizeError(error); dispatch(actions[resourceName][camelize('get', pluralName, 'report', 'failure')](normalizedError)); // custom provided onError callback if (isFunction(onError)) { onError(error); } }); }; return thunks; } /** * @function * @name generateExposedActions * @description Generate all actions which are exposed from the library for * consumers to use. All exposed actions are wrapped in dispatch function so * use should not have call dispatch again. * * @param {string} resource Resource Name * @param {object} actions Resources actions * @param {Function} dispatch Store action dispatcher * @param {object} thunks Custom thunks to override/extends existing thunks * @returns {object} wrapped resource actions with dispatching ability * * @version 0.1.0 * @since 0.1.0 */ function generateExposedActions(resource, actions, dispatch, thunks = null) { const resourceName = inflection.singularize(upperFirst(resource)); const generatedThunks = createThunksFor(resourceName); merge(generatedThunks, thunks); const extractedActions = {}; extractedActions[camelize('select', resourceName)] = get(actions[resource], camelize('select', resourceName)); extractedActions[camelize('open', resourceName, 'form')] = get(actions[resource], camelize('open', resourceName, 'form')); extractedActions[camelize('close', resourceName, 'form')] = get(actions[resource], camelize('close', resourceName, 'form')); extractedActions[camelize('set', resourceName, 'schema')] = get(actions[resource], camelize('set', resourceName, 'schema')); const allActions = merge({}, extractedActions, generatedThunks); return wrapActionsWithDispatch(allActions, dispatch); } /** * @function * @name generateExposedActions * @description Generate all actions which are exposed from the library for * consumers to use. All exposed actions are wrapped in dispatch function so * use should not have call dispatch again. * @param {string} report Report Name * @param {Function} dispatch Store action dispatcher * @param {object} thunks Custom thunks to override/extends existing thunks * @returns {object} Wrapped actions in dispatch function * @version 0.1.0 * @since 0.20.0 */ function generateReportExposedActions(report, dispatch, thunks = null) { const generatedThunks = merge({}, createReportThunkFor(report), thunks); return wrapActionsWithDispatch(generatedThunks, dispatch); } /* declarations */ const { getSchemas } = eweaApiClient.httpActions; /** * Action dispatched when application initialization starts * * @function * @name initializeAppStart * * @returns {object} Action object * * @version 0.1.0 * @since 0.1.0 */ function initializeAppStart() { return { type: INITIALIZE_APP_START }; } /** * Action dispatched when application initialization is successfully * * @function * @name initializeAppSuccess * * * @returns {object} action Object * * @version 0.1.0 * @since 0.1.0 */ function initializeAppSuccess() { return { type: INITIALIZE_APP_SUCCESS }; } /** * Action dispatched when an error occurs during application initialization * * @function * @name initializeAppFailure * * @param {object} error error happened during application initialization * * @returns {object} Nothing is returned * * @version 0.1.0 * @since 0.1.0 */ function initializeAppFailure(error) { return { type: INITIALIZE_APP_FAILURE, payload: error }; } /** * @function * @name signInStart * @description Action dispatched when user start to signIng into the system * @returns {object} redux action * @version 0.1.0 * @since 0.10.3 */ function signInStart() { return { type: SIGNIN_APP_START }; } /** * @function * @name signInSuccess * @description Action dispatched when user successfully signed In * into the system * @param {object} data signed In user/party and extracted * permissions wildcards * @returns {object} redux action * @version 0.2.0 * @since 0.10.3 */ function signInSuccess(data) { return { type: SIGNIN_APP_SUCCESS, payload: data }; } /** * @function * @name signInFailure * @description Action dispatched when user signing In fails * @param {object} error Error instance * @returns {object} redux action * @version 0.1.0 * @since 0.10.3 */ function signInFailure(error) { return { type: SIGNIN_APP_FAILURE, payload: error }; } /** * @function * @name signOut * @description Action dispatched when user signOut * @returns {object} Redux action * @version 0.1.0 * @since 0.10.3 */ function signOut() { return { type: SIGNOUT }; } /** * @function * @name initializeApp * @description Action dispatched when application is started. * It will load up all schema need for in the application * @returns {Promise} thunk function * @version 0.1.0 * @since 0.1.0 */ function initializeApp() { return dispatch => { dispatch(initializeAppStart()); return getSchemas().then(schemas => { const { agency: { setAgencySchema }, event: { setEventSchema }, // alert: { setAlertSchema }, // district: { setDistrictSchema }, // feature: { setFeatureSchema }, focalPerson: { setFocalPersonSchema } // indicator: { setIndicatorSchema }, // incidentType: { setIncidentTypeSchema }, // question: { setQuestionSchema }, // questionnaire: { setQuestionnaireSchema }, // region: { setRegionSchema }, // role: { setRoleSchema }, } = actions; const { Agency: agencySchema, Event: eventSchema, // Alert: alertSchema, // District: districtSchema, // Feature: featureSchema, FocalPerson: focalPersonSchema // IncidentType: incidentTypeSchema, // Indicator: indicatorSchema, // Question: questionSchema, // Questionnaire: questionnaireSchema, // Region: regionSchema, // Role: roleSchema, } = schemas; dispatch(setAgencySchema(agencySchema)); dispatch(setEventSchema(eventSchema)); // dispatch(setAlertSchema(alertSchema)); // dispatch(setDistrictSchema(districtSchema)); // dispatch(setFeatureSchema(featureSchema)); dispatch(setFocalPersonSchema(focalPersonSchema)); // dispatch(setIndicatorSchema(indicatorSchema)); // dispatch(setIncidentTypeSchema(incidentTypeSchema)); // dispatch(setQuestionSchema(questionSchema)); // dispatch(setQuestionnaireSchema(questionnaireSchema)); // dispatch(setRegionSchema(regionSchema)); // dispatch(setRoleSchema(roleSchema)); dispatch(initializeAppSuccess()); }).catch(error => { dispatch(initializeAppFailure(error)); }); }; } /** * * @function * @name signIn * @description Thunk action to signIn user/party * @param {object} credentials Email and password * @param {Function} onSuccess Callback for successfully signIn * @param {Function} onError Callback for failed signIn * @returns {Promise} redux thunk * @version 0.2.0 * @since 0.10.3 */ function signIn(credentials, onSuccess, onError) { return dispatch => { dispatch(signInStart()); return eweaApiClient.signIn(credentials).then(results => { const { party } = results; const permissions = getPartyPermissionsWildcards(party); dispatch(signInSuccess({ party, permissions })); if (isFunction(onSuccess)) { onSuccess(party); } }).catch(error => { dispatch(signInFailure(error)); if (isFunction(onError)) { onError(error); } }); }; } /** * * @function * @name wrappedInitializeApp * @description Wrapped initialize app thunk * @returns {Promise} - dispatched initialize app thunk * @version 0.1.0 * @since 0.3.2 */ function wrappedInitializeApp() { return dispatch(initializeApp()); } /** * @function * @name wrappedSignIn * @description Wrapped signIng thunk * @param {object} credentials email and password provided by user * @param {Function} onSuccess Callback for successfully signIn * @param {Function} onError Callback for failed signIn * @returns {Promise} dispatched signIng thunk * @version 0.1.0 * @since 0.10.3 */ function wrappedSignIn(credentials, onSuccess, onError) { return dispatch(signIn(credentials, onSuccess, onError)); } /** * @function * @name wrappedSignOut * @description Wrapped signOut action * @returns {undefined} * @version 0.2.0 * @since 0.10.3 */ function wrappedSignOut() { eweaApiClient.signOut(); // clear sessionStorage return dispatch(signOut()); } /** * @function * @name StoreProvider * @description Store Provider for EWEA store * * @param {object} props react nodes * @param {object} props.children react nodes * @returns {object} Store provider * @version 0.1.0 * @since 0.1.0 * @example * import {StoreProvider} from '@codetanzania/ewea-api-states'; * * ReactDom.render(<StoreProvider><App /></StoreProvider>, * document.getElementById('root')); */ function StoreProvider({ children }) { return /*#__PURE__*/React.createElement(reactRedux.Provider, { store: store }, children); } StoreProvider.propTypes = { children: PropTypes.node.isRequired }; /** * @function * @name Connect * @description Expose simplified connect function * * This function subscribe component to the store and inject props * to the component * * @param {object} component react node * @param {object|Function} stateToProps states to inject into props * @returns {object} React component which is injected with props * * @version 0.1.0 * @since 0.1.0 * @example * function AlertList({alerts}){ * return ( * ... jsx stuff * ); * } * * export Connect(AlertList,{alerts:'alerts.list'}) */ function Connect(component, stateToProps = null) { let mapStateToProps = stateToProps; if (!isFunction(stateToProps) && isObject(stateToProps)) { mapStateToProps = state => { const mappedState = {}; forIn(stateToProps, (value, key) => { mappedState[key] = get(state, value); }); return mappedState; }; } return reactRedux.connect(mapStateToProps)(component); } const reduxActions = {}; forEach(resources, resource => { const generatedActions = generateExposedActions(resource, actions, dispatch); merge(reduxActions, generatedActions); }); forEach(REPORTS, report => { const generatedReportActions = generateReportExposedActions(report, dispatch); merge(reduxActions, generatedReportActions); }); exports.Connect = Connect; exports.StoreProvider = StoreProvider; exports.initializeApp = wrappedInitializeApp; exports.reduxActions = reduxActions; exports.signIn = wrappedSignIn; exports.signOut = wrappedSignOut;