mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-16 20:43:48 +00:00
Added first redux toolkit based reducer for domains
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { IContainer } from 'bottlejs';
|
||||||
import { save, load, RLSOptions } from 'redux-localstorage-simple';
|
import { save, load, RLSOptions } from 'redux-localstorage-simple';
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
import reducer from '../reducers';
|
import reducer from '../reducers';
|
||||||
@@ -13,9 +14,9 @@ const localStorageConfig: RLSOptions = {
|
|||||||
};
|
};
|
||||||
const preloadedState = migrateDeprecatedSettings(load(localStorageConfig) as ShlinkState);
|
const preloadedState = migrateDeprecatedSettings(load(localStorageConfig) as ShlinkState);
|
||||||
|
|
||||||
export const store = configureStore({
|
export const setUpStore = (container: IContainer) => configureStore({
|
||||||
devTools: !isProduction,
|
devTools: !isProduction,
|
||||||
reducer,
|
reducer: reducer(container),
|
||||||
preloadedState,
|
preloadedState,
|
||||||
middleware: (defaultMiddlewaresIncludingReduxThunk) => defaultMiddlewaresIncludingReduxThunk(
|
middleware: (defaultMiddlewaresIncludingReduxThunk) => defaultMiddlewaresIncludingReduxThunk(
|
||||||
{ immutableCheck: false, serializableCheck: false }, // State is too big for these
|
{ immutableCheck: false, serializableCheck: false }, // State is too big for these
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { Action, Dispatch } from 'redux';
|
import { createSlice, PayloadAction, createAsyncThunk, SliceCaseReducers } from '@reduxjs/toolkit';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
import { ShlinkDomainRedirects } from '../../api/types';
|
import { ShlinkDomainRedirects } from '../../api/types';
|
||||||
import { buildReducer } from '../../utils/helpers/redux';
|
|
||||||
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||||
import { GetState } from '../../container/types';
|
import { GetState, ShlinkState } from '../../container/types';
|
||||||
import { parseApiError } from '../../api/utils';
|
|
||||||
import { ApiErrorAction } from '../../api/types/actions';
|
import { ApiErrorAction } from '../../api/types/actions';
|
||||||
import { Domain, DomainStatus } from '../data';
|
import { Domain, DomainStatus } from '../data';
|
||||||
import { hasServerData } from '../../servers/data';
|
import { hasServerData } from '../../servers/data';
|
||||||
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
|
import { replaceAuthorityFromUri } from '../../utils/helpers/uri';
|
||||||
import { EDIT_DOMAIN_REDIRECTS, EditDomainRedirectsAction } from './domainRedirects';
|
import { EDIT_DOMAIN_REDIRECTS, EditDomainRedirectsAction } from './domainRedirects';
|
||||||
import { ProblemDetailsError } from '../../api/types/errors';
|
import { ProblemDetailsError } from '../../api/types/errors';
|
||||||
|
import { parseApiError } from '../../api/utils';
|
||||||
|
import { buildReducer } from '../../utils/helpers/redux';
|
||||||
|
|
||||||
export const LIST_DOMAINS_START = 'shlink/domainsList/LIST_DOMAINS_START';
|
export const LIST_DOMAINS_START = 'shlink/domainsList/LIST_DOMAINS_START';
|
||||||
export const LIST_DOMAINS_ERROR = 'shlink/domainsList/LIST_DOMAINS_ERROR';
|
export const LIST_DOMAINS_ERROR = 'shlink/domainsList/LIST_DOMAINS_ERROR';
|
||||||
@@ -26,19 +28,17 @@ export interface DomainsList {
|
|||||||
errorData?: ProblemDetailsError;
|
errorData?: ProblemDetailsError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListDomainsAction extends Action<string> {
|
type ListDomainsAction = PayloadAction<{
|
||||||
domains: Domain[];
|
domains: Domain[];
|
||||||
defaultRedirects?: ShlinkDomainRedirects;
|
defaultRedirects?: ShlinkDomainRedirects;
|
||||||
}
|
}>;
|
||||||
|
|
||||||
interface FilterDomainsAction extends Action<string> {
|
type FilterDomainsAction = PayloadAction<string>;
|
||||||
searchTerm: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ValidateDomain extends Action<string> {
|
type ValidateDomain = PayloadAction<{
|
||||||
domain: string;
|
domain: string;
|
||||||
status: DomainStatus;
|
status: DomainStatus;
|
||||||
}
|
}>;
|
||||||
|
|
||||||
const initialState: DomainsList = {
|
const initialState: DomainsList = {
|
||||||
domains: [],
|
domains: [],
|
||||||
@@ -59,27 +59,28 @@ export const replaceRedirectsOnDomain = (domain: string, redirects: ShlinkDomain
|
|||||||
export const replaceStatusOnDomain = (domain: string, status: DomainStatus) =>
|
export const replaceStatusOnDomain = (domain: string, status: DomainStatus) =>
|
||||||
(d: Domain): Domain => (d.domain !== domain ? d : { ...d, status });
|
(d: Domain): Domain => (d.domain !== domain ? d : { ...d, status });
|
||||||
|
|
||||||
export default buildReducer<DomainsList, DomainsCombinedAction>({
|
const oldReducer = buildReducer<DomainsList, DomainsCombinedAction>({
|
||||||
[LIST_DOMAINS_START]: () => ({ ...initialState, loading: true }),
|
[LIST_DOMAINS_START]: () => ({ ...initialState, loading: true }),
|
||||||
[LIST_DOMAINS_ERROR]: ({ errorData }) => ({ ...initialState, error: true, errorData }),
|
[LIST_DOMAINS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||||
[LIST_DOMAINS]: (_, { domains, defaultRedirects }) =>
|
[LIST_DOMAINS]: (_, { payload }) => ({ ...initialState, searchTerm: payload, filteredDomains: payload.domains }),
|
||||||
({ ...initialState, domains, filteredDomains: domains, defaultRedirects }),
|
[FILTER_DOMAINS]: (state, { payload }) => ({
|
||||||
[FILTER_DOMAINS]: (state, { searchTerm }) => ({
|
|
||||||
...state,
|
...state,
|
||||||
filteredDomains: state.domains.filter(({ domain }) => domain.toLowerCase().match(searchTerm.toLowerCase())),
|
filteredDomains: state.domains.filter(({ domain }) => domain.toLowerCase().match(payload.toLowerCase())),
|
||||||
}),
|
}),
|
||||||
[EDIT_DOMAIN_REDIRECTS]: (state, { domain, redirects }) => ({
|
[EDIT_DOMAIN_REDIRECTS]: (state, { domain, redirects }) => ({
|
||||||
...state,
|
...state,
|
||||||
domains: state.domains.map(replaceRedirectsOnDomain(domain, redirects)),
|
domains: state.domains.map(replaceRedirectsOnDomain(domain, redirects)),
|
||||||
filteredDomains: state.filteredDomains.map(replaceRedirectsOnDomain(domain, redirects)),
|
filteredDomains: state.filteredDomains.map(replaceRedirectsOnDomain(domain, redirects)),
|
||||||
}),
|
}),
|
||||||
[VALIDATE_DOMAIN]: (state, { domain, status }) => ({
|
[VALIDATE_DOMAIN]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
domains: state.domains.map(replaceStatusOnDomain(domain, status)),
|
domains: state.domains.map(replaceStatusOnDomain(payload.domain, payload.status)),
|
||||||
filteredDomains: state.filteredDomains.map(replaceStatusOnDomain(domain, status)),
|
filteredDomains: state.filteredDomains.map(replaceStatusOnDomain(payload.domain, payload.status)),
|
||||||
}),
|
}),
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
|
||||||
|
export default oldReducer;
|
||||||
|
|
||||||
export const listDomains = (buildShlinkApiClient: ShlinkApiClientBuilder) => () => async (
|
export const listDomains = (buildShlinkApiClient: ShlinkApiClientBuilder) => () => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
getState: GetState,
|
getState: GetState,
|
||||||
@@ -88,18 +89,21 @@ export const listDomains = (buildShlinkApiClient: ShlinkApiClientBuilder) => ()
|
|||||||
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = await shlinkListDomains().then(({ data, defaultRedirects }) => ({
|
const payload = await shlinkListDomains().then(({ data, defaultRedirects }) => ({
|
||||||
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
||||||
defaultRedirects,
|
defaultRedirects,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch<ListDomainsAction>({ type: LIST_DOMAINS, ...resp });
|
dispatch<ListDomainsAction>({ type: LIST_DOMAINS, payload });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
dispatch<ApiErrorAction>({ type: LIST_DOMAINS_ERROR, errorData: parseApiError(e) });
|
dispatch<ApiErrorAction>({ type: LIST_DOMAINS_ERROR, errorData: parseApiError(e) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const filterDomains = (searchTerm: string): FilterDomainsAction => ({ type: FILTER_DOMAINS, searchTerm });
|
export const filterDomains = (searchTerm: string): FilterDomainsAction => ({
|
||||||
|
type: FILTER_DOMAINS,
|
||||||
|
payload: searchTerm,
|
||||||
|
});
|
||||||
|
|
||||||
export const checkDomainHealth = (buildShlinkApiClient: ShlinkApiClientBuilder) => (domain: string) => async (
|
export const checkDomainHealth = (buildShlinkApiClient: ShlinkApiClientBuilder) => (domain: string) => async (
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
@@ -108,7 +112,10 @@ export const checkDomainHealth = (buildShlinkApiClient: ShlinkApiClientBuilder)
|
|||||||
const { selectedServer } = getState();
|
const { selectedServer } = getState();
|
||||||
|
|
||||||
if (!hasServerData(selectedServer)) {
|
if (!hasServerData(selectedServer)) {
|
||||||
dispatch<ValidateDomain>({ type: VALIDATE_DOMAIN, domain, status: 'invalid' });
|
dispatch<ValidateDomain>({
|
||||||
|
type: VALIDATE_DOMAIN,
|
||||||
|
payload: { domain, status: 'invalid' },
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -122,8 +129,102 @@ export const checkDomainHealth = (buildShlinkApiClient: ShlinkApiClientBuilder)
|
|||||||
|
|
||||||
const { status } = await health();
|
const { status } = await health();
|
||||||
|
|
||||||
dispatch<ValidateDomain>({ type: VALIDATE_DOMAIN, domain, status: status === 'pass' ? 'valid' : 'invalid' });
|
dispatch<ValidateDomain>({
|
||||||
|
type: VALIDATE_DOMAIN,
|
||||||
|
payload: { domain, status: status === 'pass' ? 'valid' : 'invalid' },
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dispatch<ValidateDomain>({ type: VALIDATE_DOMAIN, domain, status: 'invalid' });
|
dispatch<ValidateDomain>({
|
||||||
|
type: VALIDATE_DOMAIN,
|
||||||
|
payload: { domain, status: 'invalid' },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const domainsReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const listDomains = createAsyncThunk<{
|
||||||
|
domains: Domain[];
|
||||||
|
defaultRedirects?: ShlinkDomainRedirects;
|
||||||
|
}, void, { state: ShlinkState }>(
|
||||||
|
LIST_DOMAINS,
|
||||||
|
async (_, { getState }) => {
|
||||||
|
const { listDomains: shlinkListDomains } = buildShlinkApiClient(getState);
|
||||||
|
const { data, defaultRedirects } = await shlinkListDomains();
|
||||||
|
|
||||||
|
return {
|
||||||
|
domains: data.map((domain): Domain => ({ ...domain, status: 'validating' })),
|
||||||
|
defaultRedirects,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
const checkDomainHealth = createAsyncThunk<{ domain: string; status: DomainStatus }, string, { state: ShlinkState }>(
|
||||||
|
VALIDATE_DOMAIN,
|
||||||
|
async (domain: string, { getState }) => {
|
||||||
|
const { selectedServer } = getState();
|
||||||
|
|
||||||
|
if (!hasServerData(selectedServer)) {
|
||||||
|
return { domain, status: 'invalid' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { url, ...rest } = selectedServer;
|
||||||
|
const { health } = buildShlinkApiClient({
|
||||||
|
...rest,
|
||||||
|
url: replaceAuthorityFromUri(url, domain),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { status } = await health();
|
||||||
|
|
||||||
|
return { domain, status: status === 'pass' ? 'valid' : 'invalid' };
|
||||||
|
} catch (e) {
|
||||||
|
return { domain, status: 'invalid' };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { actions, reducer } = createSlice<DomainsList, SliceCaseReducers<DomainsList>>({
|
||||||
|
name: 'domainsList',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
filterDomains: (state, { payload }) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
state.filteredDomains = state.domains.filter(
|
||||||
|
({ domain }) => domain.toLowerCase().match(payload.toLowerCase()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(listDomains.pending, () => ({ ...initialState, loading: true }));
|
||||||
|
builder.addCase(listDomains.rejected, (_, { error }) => (
|
||||||
|
{ ...initialState, error: true, errorData: parseApiError(error as AxiosError<ProblemDetailsError>) } // TODO Fix this casting
|
||||||
|
));
|
||||||
|
builder.addCase(listDomains.fulfilled, (_, { payload }) => (
|
||||||
|
{ ...initialState, ...payload, filteredDomains: payload.domains }
|
||||||
|
));
|
||||||
|
|
||||||
|
builder.addCase(checkDomainHealth.fulfilled, (state, { payload }) => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
state.domains = state.domains.map(replaceStatusOnDomain(payload.domain, payload.status));
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
state.filteredDomains = state.filteredDomains.map(replaceStatusOnDomain(payload.domain, payload.status));
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.addCase(EDIT_DOMAIN_REDIRECTS, (state, { domain, redirects }: any) => { // TODO Fix this "any"
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
state.domains = state.domains.map(replaceRedirectsOnDomain(domain, redirects));
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
state.filteredDomains = state.filteredDomains.map(replaceRedirectsOnDomain(domain, redirects));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
reducer,
|
||||||
|
listDomains,
|
||||||
|
checkDomainHealth,
|
||||||
|
...actions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { prop } from 'ramda';
|
||||||
import Bottle from 'bottlejs';
|
import Bottle from 'bottlejs';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { checkDomainHealth, filterDomains, listDomains } from '../reducers/domainsList';
|
import { domainsReducerCreator } from '../reducers/domainsList';
|
||||||
import { DomainSelector } from '../DomainSelector';
|
import { DomainSelector } from '../DomainSelector';
|
||||||
import { ManageDomains } from '../ManageDomains';
|
import { ManageDomains } from '../ManageDomains';
|
||||||
import { editDomainRedirects } from '../reducers/domainRedirects';
|
import { editDomainRedirects } from '../reducers/domainRedirects';
|
||||||
@@ -16,11 +17,15 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||||||
['listDomains', 'filterDomains', 'editDomainRedirects', 'checkDomainHealth'],
|
['listDomains', 'filterDomains', 'editDomainRedirects', 'checkDomainHealth'],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Reducer
|
||||||
|
bottle.serviceFactory('domainsReducerCreator', domainsReducerCreator, 'buildShlinkApiClient');
|
||||||
|
bottle.serviceFactory('domainsListReducer', prop('reducer'), 'domainsReducerCreator'); // TODO Improve type checks on the prop that gets picked here
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
bottle.serviceFactory('listDomains', listDomains, 'buildShlinkApiClient');
|
bottle.serviceFactory('listDomains', prop('listDomains'), 'domainsReducerCreator'); // TODO Improve type checks on the prop that gets picked here
|
||||||
bottle.serviceFactory('filterDomains', () => filterDomains);
|
bottle.serviceFactory('filterDomains', prop('filterDomains'), 'domainsReducerCreator'); // TODO Improve type checks on the prop that gets picked here
|
||||||
bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient');
|
bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient');
|
||||||
bottle.serviceFactory('checkDomainHealth', checkDomainHealth, 'buildShlinkApiClient');
|
bottle.serviceFactory('checkDomainHealth', prop('checkDomainHealth'), 'domainsReducerCreator'); // TODO Improve type checks on the prop that gets picked here
|
||||||
};
|
};
|
||||||
|
|
||||||
export default provideServices;
|
export default provideServices;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Provider } from 'react-redux';
|
|||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import pack from '../package.json';
|
import pack from '../package.json';
|
||||||
import { container } from './container';
|
import { container } from './container';
|
||||||
import { store } from './container/store';
|
import { setUpStore } from './container/store';
|
||||||
import { fixLeafletIcons } from './utils/helpers/leaflet';
|
import { fixLeafletIcons } from './utils/helpers/leaflet';
|
||||||
import { register as registerServiceWorker } from './serviceWorkerRegistration';
|
import { register as registerServiceWorker } from './serviceWorkerRegistration';
|
||||||
import 'chart.js/auto'; // TODO Import specific ones to reduce bundle size https://react-chartjs-2.js.org/docs/migration-to-v4/#tree-shaking
|
import 'chart.js/auto'; // TODO Import specific ones to reduce bundle size https://react-chartjs-2.js.org/docs/migration-to-v4/#tree-shaking
|
||||||
@@ -14,6 +14,7 @@ import './index.scss';
|
|||||||
// This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS
|
// This overwrites icons used for leaflet maps, fixing some issues caused by webpack while processing the CSS
|
||||||
fixLeafletIcons();
|
fixLeafletIcons();
|
||||||
|
|
||||||
|
const store = setUpStore(container);
|
||||||
const { App, ScrollToTop, ErrorHandler, appUpdateAvailable } = container;
|
const { App, ScrollToTop, ErrorHandler, appUpdateAvailable } = container;
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render( // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
createRoot(document.getElementById('root')!).render( // eslint-disable-line @typescript-eslint/no-non-null-assertion
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import Bottle from 'bottlejs';
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import serversReducer from '../servers/reducers/servers';
|
import serversReducer from '../servers/reducers/servers';
|
||||||
import selectedServerReducer from '../servers/reducers/selectedServer';
|
import selectedServerReducer from '../servers/reducers/selectedServer';
|
||||||
@@ -16,13 +17,12 @@ import tagDeleteReducer from '../tags/reducers/tagDelete';
|
|||||||
import tagEditReducer from '../tags/reducers/tagEdit';
|
import tagEditReducer from '../tags/reducers/tagEdit';
|
||||||
import mercureInfoReducer from '../mercure/reducers/mercureInfo';
|
import mercureInfoReducer from '../mercure/reducers/mercureInfo';
|
||||||
import settingsReducer from '../settings/reducers/settings';
|
import settingsReducer from '../settings/reducers/settings';
|
||||||
import domainsListReducer from '../domains/reducers/domainsList';
|
|
||||||
import visitsOverviewReducer from '../visits/reducers/visitsOverview';
|
import visitsOverviewReducer from '../visits/reducers/visitsOverview';
|
||||||
import appUpdatesReducer from '../app/reducers/appUpdates';
|
import appUpdatesReducer from '../app/reducers/appUpdates';
|
||||||
import sidebarReducer from '../common/reducers/sidebar';
|
import sidebarReducer from '../common/reducers/sidebar';
|
||||||
import { ShlinkState } from '../container/types';
|
import { ShlinkState } from '../container/types';
|
||||||
|
|
||||||
export default combineReducers<ShlinkState>({
|
export default (container: Bottle.IContainer) => combineReducers<ShlinkState>({
|
||||||
servers: serversReducer,
|
servers: serversReducer,
|
||||||
selectedServer: selectedServerReducer,
|
selectedServer: selectedServerReducer,
|
||||||
shortUrlsList: shortUrlsListReducer,
|
shortUrlsList: shortUrlsListReducer,
|
||||||
@@ -40,7 +40,7 @@ export default combineReducers<ShlinkState>({
|
|||||||
tagEdit: tagEditReducer,
|
tagEdit: tagEditReducer,
|
||||||
mercureInfo: mercureInfoReducer,
|
mercureInfo: mercureInfoReducer,
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
domainsList: domainsListReducer,
|
domainsList: container.domainsListReducer,
|
||||||
visitsOverview: visitsOverviewReducer,
|
visitsOverview: visitsOverviewReducer,
|
||||||
appUpdated: appUpdatesReducer,
|
appUpdated: appUpdatesReducer,
|
||||||
sidebar: sidebarReducer,
|
sidebar: sidebarReducer,
|
||||||
|
|||||||
Reference in New Issue
Block a user