diff --git a/src/App.tsx b/src/App.tsx
index 4928f42d..23938617 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,11 +2,14 @@ import { useEffect, FC } from 'react';
import { Route, Switch } from 'react-router-dom';
import NotFound from './common/NotFound';
import { ServersMap } from './servers/data';
+import { Settings } from './settings/reducers/settings';
+import { changeThemeInMarkup } from './utils/theme';
import './App.scss';
interface AppProps {
fetchServers: Function;
servers: ServersMap;
+ settings: Settings;
}
const App = (
@@ -17,12 +20,14 @@ const App = (
EditServer: FC,
Settings: FC,
ShlinkVersionsContainer: FC,
-) => ({ fetchServers, servers }: AppProps) => {
- // On first load, try to fetch the remote servers if the list is empty
+) => ({ fetchServers, servers, settings }: AppProps) => {
useEffect(() => {
+ // On first load, try to fetch the remote servers if the list is empty
if (Object.keys(servers).length === 0) {
fetchServers();
}
+
+ changeThemeInMarkup(settings.ui?.theme ?? 'light');
}, []);
return (
diff --git a/src/container/index.ts b/src/container/index.ts
index f7a0d3cf..b369c1f8 100644
--- a/src/container/index.ts
+++ b/src/container/index.ts
@@ -43,7 +43,7 @@ bottle.serviceFactory(
'Settings',
'ShlinkVersionsContainer',
);
-bottle.decorator('App', connect([ 'servers' ], [ 'fetchServers' ]));
+bottle.decorator('App', connect([ 'servers', 'settings' ], [ 'fetchServers' ]));
provideCommonServices(bottle, connect, withRouter);
provideApiServices(bottle);
diff --git a/src/settings/RealTimeUpdates.tsx b/src/settings/RealTimeUpdates.tsx
index 914c5a6f..d737f6e9 100644
--- a/src/settings/RealTimeUpdates.tsx
+++ b/src/settings/RealTimeUpdates.tsx
@@ -19,6 +19,9 @@ const RealTimeUpdates = (
Enable or disable real-time updates, when using Shlink v2.2.0 or newer.
+
+ Real-time updates are currently being {realTimeUpdates.enabled ? 'processed' : 'ignored'}.
+
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index bbd524d9..31e6fc6f 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -2,14 +2,19 @@ import { FC } from 'react';
import { Row } from 'reactstrap';
import NoMenuLayout from '../common/NoMenuLayout';
-const Settings = (RealTimeUpdates: FC, ShortUrlCreation: FC) => () => (
+const Settings = (RealTimeUpdates: FC, ShortUrlCreation: FC, UserInterface:FC) => () => (
-
+
diff --git a/src/settings/UserInterface.tsx b/src/settings/UserInterface.tsx
new file mode 100644
index 00000000..89597320
--- /dev/null
+++ b/src/settings/UserInterface.tsx
@@ -0,0 +1,26 @@
+import { FC } from 'react';
+import { SimpleCard } from '../utils/SimpleCard';
+import ToggleSwitch from '../utils/ToggleSwitch';
+import { changeThemeInMarkup, Theme } from '../utils/theme';
+import { Settings, UiSettings } from './reducers/settings';
+
+interface UserInterfaceProps {
+ settings: Settings;
+ setUiSettings: (settings: UiSettings) => void;
+}
+
+export const UserInterface: FC = ({ settings: { ui }, setUiSettings }) => (
+
+ {
+ const theme: Theme = useDarkTheme ? 'dark' : 'light';
+
+ setUiSettings({ theme });
+ changeThemeInMarkup(theme);
+ }}
+ >
+ Use dark theme
+
+
+);
diff --git a/src/settings/reducers/settings.ts b/src/settings/reducers/settings.ts
index aa1bc929..e5c0d1f9 100644
--- a/src/settings/reducers/settings.ts
+++ b/src/settings/reducers/settings.ts
@@ -2,6 +2,7 @@ import { Action } from 'redux';
import { dissoc, mergeDeepRight } from 'ramda';
import { buildReducer } from '../../utils/helpers/redux';
import { RecursivePartial } from '../../utils/utils';
+import { Theme } from '../../utils/theme';
export const SET_SETTINGS = 'shlink/realTimeUpdates/SET_SETTINGS';
@@ -19,9 +20,14 @@ export interface ShortUrlCreationSettings {
validateUrls: boolean;
}
+export interface UiSettings {
+ theme: Theme;
+}
+
export interface Settings {
realTimeUpdates: RealTimeUpdatesSettings;
shortUrlCreation?: ShortUrlCreationSettings;
+ ui?: UiSettings;
}
const initialState: Settings = {
@@ -31,6 +37,9 @@ const initialState: Settings = {
shortUrlCreation: {
validateUrls: false,
},
+ ui: {
+ theme: 'light',
+ },
};
type SettingsAction = Action & Settings;
@@ -55,3 +64,8 @@ export const setShortUrlCreationSettings = (settings: ShortUrlCreationSettings):
type: SET_SETTINGS,
shortUrlCreation: settings,
});
+
+export const setUiSettings = (settings: UiSettings): PartialSettingsAction => ({
+ type: SET_SETTINGS,
+ ui: settings,
+});
diff --git a/src/settings/services/provideServices.ts b/src/settings/services/provideServices.ts
index 393ccefc..cd01599b 100644
--- a/src/settings/services/provideServices.ts
+++ b/src/settings/services/provideServices.ts
@@ -1,14 +1,20 @@
import Bottle from 'bottlejs';
import RealTimeUpdates from '../RealTimeUpdates';
import Settings from '../Settings';
-import { setRealTimeUpdatesInterval, setShortUrlCreationSettings, toggleRealTimeUpdates } from '../reducers/settings';
+import {
+ setRealTimeUpdatesInterval,
+ setShortUrlCreationSettings,
+ setUiSettings,
+ toggleRealTimeUpdates,
+} from '../reducers/settings';
import { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { ShortUrlCreation } from '../ShortUrlCreation';
+import { UserInterface } from '../UserInterface';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components
- bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates', 'ShortUrlCreation');
+ bottle.serviceFactory('Settings', Settings, 'RealTimeUpdates', 'ShortUrlCreation', 'UserInterface');
bottle.decorator('Settings', withoutSelectedServer);
bottle.decorator('Settings', connect(null, [ 'resetSelectedServer' ]));
@@ -21,10 +27,14 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('ShortUrlCreation', () => ShortUrlCreation);
bottle.decorator('ShortUrlCreation', connect([ 'settings' ], [ 'setShortUrlCreationSettings' ]));
+ bottle.serviceFactory('UserInterface', () => UserInterface);
+ bottle.decorator('UserInterface', connect([ 'settings' ], [ 'setUiSettings' ]));
+
// Actions
bottle.serviceFactory('toggleRealTimeUpdates', () => toggleRealTimeUpdates);
bottle.serviceFactory('setRealTimeUpdatesInterval', () => setRealTimeUpdatesInterval);
bottle.serviceFactory('setShortUrlCreationSettings', () => setShortUrlCreationSettings);
+ bottle.serviceFactory('setUiSettings', () => setUiSettings);
};
export default provideServices;
diff --git a/src/utils/theme/index.ts b/src/utils/theme/index.ts
index a4b5f96b..ca56555b 100644
--- a/src/utils/theme/index.ts
+++ b/src/utils/theme/index.ts
@@ -5,3 +5,11 @@ export const MAIN_COLOR_ALPHA = 'rgba(70, 150, 229, 0.4)';
export const HIGHLIGHTED_COLOR = '#F77F28';
export const HIGHLIGHTED_COLOR_ALPHA = 'rgba(247, 127, 40, 0.4)';
+
+export type Theme = 'dark' | 'light';
+
+export const changeThemeInMarkup = (theme: Theme) => {
+ const html = document.getElementsByTagName('html');
+
+ html?.[0]?.setAttribute('data-theme', theme);
+};
diff --git a/test/App.test.tsx b/test/App.test.tsx
index 26938298..da1fda0b 100644
--- a/test/App.test.tsx
+++ b/test/App.test.tsx
@@ -1,6 +1,8 @@
import { shallow, ShallowWrapper } from 'enzyme';
import { Route } from 'react-router-dom';
import { identity } from 'ramda';
+import { Mock } from 'ts-mockery';
+import { Settings } from '../src/settings/reducers/settings';
import appFactory from '../src/App';
describe('', () => {
@@ -10,7 +12,7 @@ describe('', () => {
beforeEach(() => {
const App = appFactory(MainHeader, () => null, () => null, () => null, () => null, () => null, () => null);
- wrapper = shallow();
+ wrapper = shallow(()} />);
});
afterEach(() => wrapper.unmount());
diff --git a/test/settings/reducers/settings.test.ts b/test/settings/reducers/settings.test.ts
index 1bfd1701..57d22066 100644
--- a/test/settings/reducers/settings.test.ts
+++ b/test/settings/reducers/settings.test.ts
@@ -3,12 +3,14 @@ import reducer, {
toggleRealTimeUpdates,
setRealTimeUpdatesInterval,
setShortUrlCreationSettings,
+ setUiSettings,
} from '../../../src/settings/reducers/settings';
describe('settingsReducer', () => {
const realTimeUpdates = { enabled: true };
const shortUrlCreation = { validateUrls: false };
- const settings = { realTimeUpdates, shortUrlCreation };
+ const ui = { theme: 'light' };
+ const settings = { realTimeUpdates, shortUrlCreation, ui };
describe('reducer', () => {
it('returns realTimeUpdates when action is SET_SETTINGS', () => {
@@ -39,4 +41,12 @@ describe('settingsReducer', () => {
expect(result).toEqual({ type: SET_SETTINGS, shortUrlCreation: { validateUrls: true } });
});
});
+
+ describe('setUiSettings', () => {
+ it('creates action to set ui settings', () => {
+ const result = setUiSettings({ theme: 'dark' });
+
+ expect(result).toEqual({ type: SET_SETTINGS, ui: { theme: 'dark' } });
+ });
+ });
});