diff --git a/package-lock.json b/package-lock.json
index 1a22485b..05204cab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1463,6 +1463,14 @@
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
"dev": true
},
+ "@shlinkio/redux-localstorage-simple": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@shlinkio/redux-localstorage-simple/-/redux-localstorage-simple-2.2.0.tgz",
+ "integrity": "sha512-2/VggbehDAM1dOH7rT3Qjr/MTp7qQ6VeTM+Ez4JnMUPtU9OxgV9FQbKqduasLT4EZhlRUhxwBp7K6WO3gROQDA==",
+ "requires": {
+ "object-merge": "2.5.1"
+ }
+ },
"@stryker-mutator/api": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-2.1.0.tgz",
@@ -4246,6 +4254,11 @@
"shallow-clone": "^0.1.2"
}
},
+ "clone-function": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/clone-function/-/clone-function-1.0.6.tgz",
+ "integrity": "sha1-QoRxk3dQvKnEjsv7wW9uIy90oD0="
+ },
"clone-regexp": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz",
@@ -11720,6 +11733,11 @@
"kind-of": "^3.0.3"
}
},
+ "object-foreach": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/object-foreach/-/object-foreach-0.1.2.tgz",
+ "integrity": "sha1-10IcW0DjtqPvV6xiQ2jSHY+NLew="
+ },
"object-hash": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz",
@@ -11744,6 +11762,15 @@
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"dev": true
},
+ "object-merge": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/object-merge/-/object-merge-2.5.1.tgz",
+ "integrity": "sha1-B36JFc446nKUeIRIxd0znjTfQic=",
+ "requires": {
+ "clone-function": ">=1.0.1",
+ "object-foreach": ">=0.1.2"
+ }
+ },
"object-visit": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
diff --git a/package.json b/package.json
index ab2033c1..2e2678a4 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@fortawesome/free-regular-svg-icons": "^5.11.2",
"@fortawesome/free-solid-svg-icons": "^5.11.2",
"@fortawesome/react-fontawesome": "^0.1.5",
+ "@shlinkio/redux-localstorage-simple": "^2.2.0",
"array-filter": "^1.0.0",
"array-map": "^0.0.0",
"array-reduce": "^0.0.0",
diff --git a/src/App.js b/src/App.js
index fd6c859c..edb9df36 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,29 +1,23 @@
-import React, { useEffect } from 'react';
+import React from 'react';
import { Route, Switch } from 'react-router-dom';
import NotFound from './common/NotFound';
import './App.scss';
-const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer, Settings) => ({ loadRealTimeUpdates }) => {
- useEffect(() => {
- loadRealTimeUpdates();
- }, []);
+const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer, Settings) => () => (
+
+
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- );
-};
+
+);
export default App;
diff --git a/src/container/index.js b/src/container/index.js
index 95383b13..3a8ac83a 100644
--- a/src/container/index.js
+++ b/src/container/index.js
@@ -29,7 +29,6 @@ const connect = (propsFromState, actionServiceNames = []) =>
);
bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer', 'EditServer', 'Settings');
-bottle.decorator('App', connect(null, [ 'loadRealTimeUpdates' ]));
provideCommonServices(bottle, connect, withRouter);
provideShortUrlsServices(bottle, connect);
diff --git a/src/container/store.js b/src/container/store.js
index 0d5b2b2d..6d0de762 100644
--- a/src/container/store.js
+++ b/src/container/store.js
@@ -1,13 +1,20 @@
import ReduxThunk from 'redux-thunk';
import { applyMiddleware, compose, createStore } from 'redux';
+import { save, load } from '@shlinkio/redux-localstorage-simple';
import reducers from '../reducers';
const composeEnhancers = process.env.NODE_ENV !== 'production' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
: compose;
-const store = createStore(reducers, composeEnhancers(
- applyMiddleware(ReduxThunk)
+const localStorageConfig = {
+ states: [ 'settings' ],
+ namespace: 'shlink',
+ namespaceSeparator: '.',
+};
+
+const store = createStore(reducers, load(localStorageConfig), composeEnhancers(
+ applyMiddleware(save(localStorageConfig), ReduxThunk)
));
export default store;
diff --git a/src/settings/reducers/settings.js b/src/settings/reducers/settings.js
index 3160862b..6771dbb5 100644
--- a/src/settings/reducers/settings.js
+++ b/src/settings/reducers/settings.js
@@ -1,7 +1,7 @@
import { handleActions } from 'redux-actions';
import PropTypes from 'prop-types';
-export const LOAD_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/LOAD_REAL_TIME_UPDATES';
+export const SET_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/SET_REAL_TIME_UPDATES';
export const SettingsType = PropTypes.shape({
realTimeUpdates: PropTypes.shape({
@@ -16,20 +16,10 @@ const initialState = {
};
export default handleActions({
- [LOAD_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => ({ ...state, realTimeUpdates }),
+ [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => ({ ...state, realTimeUpdates }),
}, initialState);
-export const setRealTimeUpdates = ({ updateSettings }, loadRealTimeUpdatesAction) => (enabled) => {
- updateSettings({ realTimeUpdates: { enabled } });
-
- return loadRealTimeUpdatesAction();
-};
-
-export const loadRealTimeUpdates = ({ loadSettings }) => () => {
- const { realTimeUpdates = {} } = loadSettings();
-
- return {
- type: LOAD_REAL_TIME_UPDATES,
- realTimeUpdates,
- };
-};
+export const setRealTimeUpdates = (enabled) => ({
+ type: SET_REAL_TIME_UPDATES,
+ realTimeUpdates: { enabled },
+});
diff --git a/src/settings/services/provideServices.js b/src/settings/services/provideServices.js
index cbc24a12..08ba77d3 100644
--- a/src/settings/services/provideServices.js
+++ b/src/settings/services/provideServices.js
@@ -1,6 +1,6 @@
import RealTimeUpdates from '../RealTimeUpdates';
import Settings from '../Settings';
-import { loadRealTimeUpdates, setRealTimeUpdates } from '../reducers/settings';
+import { setRealTimeUpdates } from '../reducers/settings';
import SettingsService from './SettingsService';
const provideServices = (bottle, connect) => {
@@ -14,8 +14,7 @@ const provideServices = (bottle, connect) => {
bottle.service('SettingsService', SettingsService, 'Storage');
// Actions
- bottle.serviceFactory('setRealTimeUpdates', setRealTimeUpdates, 'SettingsService', 'loadRealTimeUpdates');
- bottle.serviceFactory('loadRealTimeUpdates', loadRealTimeUpdates, 'SettingsService');
+ bottle.serviceFactory('setRealTimeUpdates', () => setRealTimeUpdates);
};
export default provideServices;
diff --git a/test/settings/reducers/settings.test.js b/test/settings/reducers/settings.test.js
index 2eb7e5db..80f22c49 100644
--- a/test/settings/reducers/settings.test.js
+++ b/test/settings/reducers/settings.test.js
@@ -1,48 +1,19 @@
-import reducer, {
- LOAD_REAL_TIME_UPDATES,
- loadRealTimeUpdates,
- setRealTimeUpdates,
-} from '../../../src/settings/reducers/settings';
+import reducer, { SET_REAL_TIME_UPDATES, setRealTimeUpdates } from '../../../src/settings/reducers/settings';
describe('settingsReducer', () => {
- const SettingsServiceMock = {
- updateSettings: jest.fn(),
- loadSettings: jest.fn(),
- };
const realTimeUpdates = { enabled: true };
- afterEach(jest.clearAllMocks);
-
describe('reducer', () => {
- it('returns realTimeUpdates when action is LOAD_REAL_TIME_UPDATES', () => {
- expect(reducer({}, { type: LOAD_REAL_TIME_UPDATES, realTimeUpdates })).toEqual({ realTimeUpdates });
- });
- });
-
- describe('loadRealTimeUpdates', () => {
- it.each([[ true ], [ false ]])('loads settings and returns LOAD_REAL_TIME_UPDATES action', (enabled) => {
- const realTimeUpdates = { enabled };
-
- SettingsServiceMock.loadSettings.mockReturnValue({ realTimeUpdates });
-
- const result = loadRealTimeUpdates(SettingsServiceMock)();
-
- expect(result).toEqual({
- type: LOAD_REAL_TIME_UPDATES,
- realTimeUpdates,
- });
- expect(SettingsServiceMock.loadSettings).toHaveBeenCalled();
+ it('returns realTimeUpdates when action is SET_REAL_TIME_UPDATES', () => {
+ expect(reducer({}, { type: SET_REAL_TIME_UPDATES, realTimeUpdates })).toEqual({ realTimeUpdates });
});
});
describe('setRealTimeUpdates', () => {
it.each([[ true ], [ false ]])('updates settings with provided value and then loads updates again', (enabled) => {
- const loadRealTimeUpdatesAction = jest.fn();
+ const result = setRealTimeUpdates(enabled);
- setRealTimeUpdates(SettingsServiceMock, loadRealTimeUpdatesAction)(enabled);
-
- expect(SettingsServiceMock.updateSettings).toHaveBeenCalledWith({ realTimeUpdates: { enabled } });
- expect(loadRealTimeUpdatesAction).toHaveBeenCalled();
+ expect(result).toEqual({ type: SET_REAL_TIME_UPDATES, realTimeUpdates: { enabled } });
});
});
});