diff --git a/src/settings/RealTimeUpdates.tsx b/src/settings/RealTimeUpdates.tsx index dc89f1d5..d89f6526 100644 --- a/src/settings/RealTimeUpdates.tsx +++ b/src/settings/RealTimeUpdates.tsx @@ -1,20 +1,49 @@ import React from 'react'; -import { Card, CardBody, CardHeader } from 'reactstrap'; +import { Card, CardBody, CardHeader, FormGroup, Input } from 'reactstrap'; +import classNames from 'classnames'; import ToggleSwitch from '../utils/ToggleSwitch'; import { Settings } from './reducers/settings'; interface RealTimeUpdatesProps { settings: Settings; - setRealTimeUpdates: (enabled: boolean) => void; + toggleRealTimeUpdates: (enabled: boolean) => void; + setRealTimeUpdatesInterval: (interval: number) => void; } -const RealTimeUpdates = ({ settings: { realTimeUpdates }, setRealTimeUpdates }: RealTimeUpdatesProps) => ( +const intervalValue = (interval?: number) => !interval ? '' : `${interval}`; + +const RealTimeUpdates = ( + { settings: { realTimeUpdates }, toggleRealTimeUpdates, setRealTimeUpdatesInterval }: RealTimeUpdatesProps, +) => ( Real-time updates - - Enable or disable real-time updates, when using Shlink v2.2.0 or newer. - + + + Enable or disable real-time updates, when using Shlink v2.2.0 or newer. + + + + + setRealTimeUpdatesInterval(Number(e.target.value))} + /> + {realTimeUpdates.enabled && ( + + {realTimeUpdates.interval !== undefined && realTimeUpdates.interval > 0 && + Updates will be reflected in the UI every {realTimeUpdates.interval} minutes. + } + {!realTimeUpdates.interval && 'Updates will be reflected in the UI as soon as they happen.'} + + )} + ); diff --git a/src/settings/reducers/settings.ts b/src/settings/reducers/settings.ts index a87455c8..8a21d146 100644 --- a/src/settings/reducers/settings.ts +++ b/src/settings/reducers/settings.ts @@ -1,10 +1,13 @@ import { Action } from 'redux'; +import { mergeDeepRight } from 'ramda'; import { buildReducer } from '../../utils/helpers/redux'; +import { RecursivePartial } from '../../utils/utils'; export const SET_REAL_TIME_UPDATES = 'shlink/realTimeUpdates/SET_REAL_TIME_UPDATES'; interface RealTimeUpdates { enabled: boolean; + interval?: number; } export interface Settings { @@ -19,11 +22,18 @@ const initialState: Settings = { type SettingsAction = Action & Settings; +type PartialSettingsAction = Action & RecursivePartial; + export default buildReducer({ - [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => ({ ...state, realTimeUpdates }), + [SET_REAL_TIME_UPDATES]: (state, { realTimeUpdates }) => mergeDeepRight(state, { realTimeUpdates }), }, initialState); -export const setRealTimeUpdates = (enabled: boolean): SettingsAction => ({ +export const toggleRealTimeUpdates = (enabled: boolean): PartialSettingsAction => ({ type: SET_REAL_TIME_UPDATES, realTimeUpdates: { enabled }, }); + +export const setRealTimeUpdatesInterval = (interval: number): PartialSettingsAction => ({ + type: SET_REAL_TIME_UPDATES, + realTimeUpdates: { interval }, +}); diff --git a/src/settings/services/provideServices.ts b/src/settings/services/provideServices.ts index 6db2052d..78d86e47 100644 --- a/src/settings/services/provideServices.ts +++ b/src/settings/services/provideServices.ts @@ -1,7 +1,7 @@ import Bottle from 'bottlejs'; import RealTimeUpdates from '../RealTimeUpdates'; import Settings from '../Settings'; -import { setRealTimeUpdates } from '../reducers/settings'; +import { setRealTimeUpdatesInterval, toggleRealTimeUpdates } from '../reducers/settings'; import { ConnectDecorator } from '../../container/types'; import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer'; @@ -13,10 +13,11 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { // Services bottle.serviceFactory('RealTimeUpdates', () => RealTimeUpdates); - bottle.decorator('RealTimeUpdates', connect([ 'settings' ], [ 'setRealTimeUpdates' ])); + bottle.decorator('RealTimeUpdates', connect([ 'settings' ], [ 'setRealTimeUpdatesInterval' ])); // Actions - bottle.serviceFactory('setRealTimeUpdates', () => setRealTimeUpdates); + bottle.serviceFactory('toggleRealTimeUpdates', () => toggleRealTimeUpdates); + bottle.serviceFactory('setRealTimeUpdatesInterval', () => setRealTimeUpdatesInterval); }; export default provideServices; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 25c22222..850fd4b6 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -39,3 +39,7 @@ export type Nullable = { type Optional = T | null | undefined; export type OptionalString = Optional; + +export type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; diff --git a/test/settings/reducers/settings.test.ts b/test/settings/reducers/settings.test.ts index c290f4f3..9018b311 100644 --- a/test/settings/reducers/settings.test.ts +++ b/test/settings/reducers/settings.test.ts @@ -1,4 +1,4 @@ -import reducer, { SET_REAL_TIME_UPDATES, setRealTimeUpdates } from '../../../src/settings/reducers/settings'; +import reducer, { SET_REAL_TIME_UPDATES, toggleRealTimeUpdates, setRealTimeUpdatesInterval } from '../../../src/settings/reducers/settings'; describe('settingsReducer', () => { const realTimeUpdates = { enabled: true }; @@ -9,11 +9,19 @@ describe('settingsReducer', () => { }); }); - describe('setRealTimeUpdates', () => { + describe('toggleRealTimeUpdates', () => { it.each([[ true ], [ false ]])('updates settings with provided value and then loads updates again', (enabled) => { - const result = setRealTimeUpdates(enabled); + const result = toggleRealTimeUpdates(enabled); expect(result).toEqual({ type: SET_REAL_TIME_UPDATES, realTimeUpdates: { enabled } }); }); }); + + describe('setRealTimeUpdatesInterval', () => { + it.each([[ 0 ], [ 1 ], [ 2 ], [ 10 ]])('updates settings with provided value and then loads updates again', (interval) => { + const result = setRealTimeUpdatesInterval(interval); + + expect(result).toEqual({ type: SET_REAL_TIME_UPDATES, realTimeUpdates: { interval } }); + }); + }); });