From 41f885d8ec89e86103f4c41b6f39b15477d8052d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 25 Apr 2020 09:49:54 +0200 Subject: [PATCH] Created settings page and reducers to handle real-time updates config --- src/App.js | 37 ++++++++++++++---------- src/container/index.js | 5 +++- src/reducers/index.js | 2 ++ src/settings/RealTimeUpdates.js | 31 ++++++++++++++++++++ src/settings/Settings.js | 6 +++- src/settings/reducers/realTimeUpdates.js | 32 ++++++++++++++++++++ src/settings/services/SettingsService.js | 14 +++++++++ src/settings/services/provideServices.js | 21 ++++++++++++++ test/App.test.js | 1 + 9 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 src/settings/RealTimeUpdates.js create mode 100644 src/settings/reducers/realTimeUpdates.js create mode 100644 src/settings/services/SettingsService.js create mode 100644 src/settings/services/provideServices.js diff --git a/src/App.js b/src/App.js index fffc6705..fd6c859c 100644 --- a/src/App.js +++ b/src/App.js @@ -1,24 +1,29 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Route, Switch } from 'react-router-dom'; import NotFound from './common/NotFound'; -import Settings from './settings/Settings'; import './App.scss'; -const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer) => () => ( -
- +const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer, Settings) => ({ loadRealTimeUpdates }) => { + useEffect(() => { + loadRealTimeUpdates(); + }, []); -
- - - - - - - - + return ( +
+ + +
+ + + + + + + + +
-
-); + ); +}; export default App; diff --git a/src/container/index.js b/src/container/index.js index d634dffb..95383b13 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -10,6 +10,7 @@ import provideVisitsServices from '../visits/services/provideServices'; import provideTagsServices from '../tags/services/provideServices'; import provideUtilsServices from '../utils/services/provideServices'; import provideMercureServices from '../mercure/services/provideServices'; +import provideSettingsServices from '../settings/services/provideServices'; const bottle = new Bottle(); const { container } = bottle; @@ -27,7 +28,8 @@ const connect = (propsFromState, actionServiceNames = []) => actionServiceNames.reduce(mapActionService, {}) ); -bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer', 'EditServer'); +bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer', 'EditServer', 'Settings'); +bottle.decorator('App', connect(null, [ 'loadRealTimeUpdates' ])); provideCommonServices(bottle, connect, withRouter); provideShortUrlsServices(bottle, connect); @@ -36,5 +38,6 @@ provideTagsServices(bottle, connect); provideVisitsServices(bottle, connect); provideUtilsServices(bottle); provideMercureServices(bottle); +provideSettingsServices(bottle, connect); export default container; diff --git a/src/reducers/index.js b/src/reducers/index.js index 8b96ede8..8e81e18a 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -14,6 +14,7 @@ import tagsListReducer from '../tags/reducers/tagsList'; import tagDeleteReducer from '../tags/reducers/tagDelete'; import tagEditReducer from '../tags/reducers/tagEdit'; import mercureInfoReducer from '../mercure/reducers/mercureInfo'; +import realTimeUpdatesReducer from '../settings/reducers/realTimeUpdates'; export default combineReducers({ servers: serversReducer, @@ -31,4 +32,5 @@ export default combineReducers({ tagDelete: tagDeleteReducer, tagEdit: tagEditReducer, mercureInfo: mercureInfoReducer, + realTimeUpdates: realTimeUpdatesReducer, }); diff --git a/src/settings/RealTimeUpdates.js b/src/settings/RealTimeUpdates.js new file mode 100644 index 00000000..eccd585f --- /dev/null +++ b/src/settings/RealTimeUpdates.js @@ -0,0 +1,31 @@ +import React from 'react'; +import { Card, CardBody, CardHeader, UncontrolledTooltip } from 'reactstrap'; +import PropTypes from 'prop-types'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import Checkbox from '../utils/Checkbox'; +import { RealTimeUpdatesType } from './reducers/realTimeUpdates'; + +const propTypes = { + realTimeUpdates: RealTimeUpdatesType, + setRealTimeUpdates: PropTypes.func, +}; + +const RealTimeUpdates = ({ realTimeUpdates, setRealTimeUpdates }) => ( + + Real-time updates + + + Enable real-time updates + + + + Enable or disable real-time updates, when using Shlink v2.2.0 or newer. + + + +); + +RealTimeUpdates.propTypes = propTypes; + +export default RealTimeUpdates; diff --git a/src/settings/Settings.js b/src/settings/Settings.js index cacd8581..81b662bd 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -1,6 +1,10 @@ import React from 'react'; import NoMenuLayout from '../common/NoMenuLayout'; -const Settings = () => Settings; +const Settings = (RealTimeUpdates) => () => ( + + + +); export default Settings; diff --git a/src/settings/reducers/realTimeUpdates.js b/src/settings/reducers/realTimeUpdates.js new file mode 100644 index 00000000..19fac094 --- /dev/null +++ b/src/settings/reducers/realTimeUpdates.js @@ -0,0 +1,32 @@ +import { handleActions } from 'redux-actions'; +import PropTypes from 'prop-types'; + +const LOAD_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/LOAD_REAL_TIME_UPDATES'; + +export const RealTimeUpdatesType = PropTypes.shape({ + enabled: PropTypes.bool.isRequired, +}); + +const initialState = { + enabled: true, +}; + +export default handleActions({ + [LOAD_REAL_TIME_UPDATES]: (state, { enabled }) => ({ ...state, enabled }), +}, initialState); + +export const setRealTimeUpdates = ({ updateSettings }, loadRealTimeUpdatesAction) => (enabled) => { + updateSettings({ realTimeUpdates: { enabled } }); + + return loadRealTimeUpdatesAction(); +}; + +export const loadRealTimeUpdates = ({ loadSettings }) => () => { + const { realTimeUpdates = {} } = loadSettings(); + const { enabled = true } = realTimeUpdates; + + return { + type: LOAD_REAL_TIME_UPDATES, + enabled, + }; +}; diff --git a/src/settings/services/SettingsService.js b/src/settings/services/SettingsService.js new file mode 100644 index 00000000..a3a39446 --- /dev/null +++ b/src/settings/services/SettingsService.js @@ -0,0 +1,14 @@ +const SETTINGS_STORAGE_KEY = 'settings'; + +export default class SettingsService { + constructor(storage) { + this.storage = storage; + } + + loadSettings = () => this.storage.get(SETTINGS_STORAGE_KEY) || {}; + + updateSettings = (settingsToUpdate) => this.storage.set(SETTINGS_STORAGE_KEY, { + ...this.loadSettings(), + ...settingsToUpdate, + }) +} diff --git a/src/settings/services/provideServices.js b/src/settings/services/provideServices.js new file mode 100644 index 00000000..ef70b49f --- /dev/null +++ b/src/settings/services/provideServices.js @@ -0,0 +1,21 @@ +import RealTimeUpdates from '../RealTimeUpdates'; +import Settings from '../Settings'; +import { loadRealTimeUpdates, setRealTimeUpdates } from '../reducers/realTimeUpdates'; +import SettingsService from './SettingsService'; + +const provideServices = (bottle, connect) => { + // Components + bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates'); + + bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates); + bottle.decorator('RealTimeUpdates', connect([ 'realTimeUpdates' ], [ 'setRealTimeUpdates' ])); + + // Services + bottle.service('SettingsService', SettingsService, 'Storage'); + + // Actions + bottle.serviceFactory('setRealTimeUpdates', setRealTimeUpdates, 'SettingsService', 'loadRealTimeUpdates'); + bottle.serviceFactory('loadRealTimeUpdates', loadRealTimeUpdates, 'SettingsService'); +}; + +export default provideServices; diff --git a/test/App.test.js b/test/App.test.js index 668239ca..ef67971c 100644 --- a/test/App.test.js +++ b/test/App.test.js @@ -21,6 +21,7 @@ describe('', () => { const routes = wrapper.find(Route); const expectedPaths = [ '/', + '/settings', '/server/create', '/server/:serverId/edit', '/server/:serverId',