Created settings page and reducers to handle real-time updates config

This commit is contained in:
Alejandro Celaya 2020-04-25 09:49:54 +02:00
parent 7516ca8dd9
commit 41f885d8ec
9 changed files with 131 additions and 18 deletions

View file

@ -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 (
<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 exact path="/settings" component={Settings} />
<Route path="/server/:serverId" component={MenuLayout} />
<Route component={NotFound} />
</Switch>
</div>
</div>
);
};
export default App;

View file

@ -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;

View file

@ -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,
});

View file

@ -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 }) => (
<Card>
<CardHeader>Real-time updates</CardHeader>
<CardBody>
<Checkbox checked={realTimeUpdates.enabled} onChange={setRealTimeUpdates}>
Enable real-time updates
<FontAwesomeIcon icon={faInfoCircle} className="ml-2" id="realTimeUpdatesInfo" />
</Checkbox>
<UncontrolledTooltip target="realTimeUpdatesInfo">
Enable or disable real-time updates, when using Shlink v2.2.0 or newer.
</UncontrolledTooltip>
</CardBody>
</Card>
);
RealTimeUpdates.propTypes = propTypes;
export default RealTimeUpdates;

View file

@ -1,6 +1,10 @@
import React from 'react';
import NoMenuLayout from '../common/NoMenuLayout';
const Settings = () => <NoMenuLayout>Settings</NoMenuLayout>;
const Settings = (RealTimeUpdates) => () => (
<NoMenuLayout>
<RealTimeUpdates />
</NoMenuLayout>
);
export default Settings;

View file

@ -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,
};
};

View file

@ -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,
})
}

View file

@ -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;

View file

@ -21,6 +21,7 @@ describe('<App />', () => {
const routes = wrapper.find(Route);
const expectedPaths = [
'/',
'/settings',
'/server/create',
'/server/:serverId/edit',
'/server/:serverId',