mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-03-14 02:08:41 +03:00
Added redux middleware to save parts of the store in the local storage transparently
This commit is contained in:
parent
bbc47b387e
commit
86bf1515d4
8 changed files with 65 additions and 77 deletions
27
package-lock.json
generated
27
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
36
src/App.js
36
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) => () => (
|
||||
<div className="container-fluid app-container">
|
||||
<MainHeader />
|
||||
|
||||
return (
|
||||
<div className="container-fluid app-container">
|
||||
<MainHeader />
|
||||
|
||||
<div className="app">
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route exact path="/settings" component={Settings} />
|
||||
<Route exact path="/server/create" component={CreateServer} />
|
||||
<Route exact path="/server/:serverId/edit" component={EditServer} />
|
||||
<Route path="/server/:serverId" component={MenuLayout} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
<div className="app">
|
||||
<Switch>
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route exact path="/settings" component={Settings} />
|
||||
<Route exact path="/server/create" component={CreateServer} />
|
||||
<Route exact path="/server/:serverId/edit" component={EditServer} />
|
||||
<Route path="/server/:serverId" component={MenuLayout} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 },
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 } });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue