Remove request helper and use axiosInstance in CM

Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
soupette 2021-06-24 13:36:09 +02:00
parent b775f7614a
commit 1ce1101fc4
15 changed files with 183 additions and 145 deletions

View File

@ -86,7 +86,8 @@
}
},
"slug": {
"type": "uid"
"type": "uid",
"targetField": "city"
},
"notrepeat_req": {
"type": "component",

View File

@ -250,7 +250,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
// Show a loading button in the EditView/Header.js && lock the app => no navigation
dispatch(setStatus('submit-pending'));
const { data } = await axiosInstance.post(endPoint, { ...body });
const { data } = await axiosInstance.post(endPoint, body);
trackUsageRef.current('didCreateEntry', trackerProperty);
toggleNotification({
@ -305,7 +305,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
dispatch(setStatus('submit-pending'));
const { data } = await axiosInstance.put(endPoint, { ...body });
const { data } = await axiosInstance.put(endPoint, body);
trackUsageRef.current('didEditEntry', { trackerProperty });
toggleNotification({

View File

@ -6,13 +6,13 @@ import { Label, Error } from '@buffetjs/core';
import { useDebounce, useClickAwayListener } from '@buffetjs/hooks';
import styled from 'styled-components';
import {
request,
LabelIconWrapper,
LoadingIndicator,
useContentManagerEditViewDataManager,
} from '@strapi/helper-plugin';
import { FormattedMessage, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import { get } from 'lodash';
import { axiosInstance } from '../../../core/utils';
import getTrad from '../../utils/getTrad';
import pluginId from '../../pluginId';
import getRequestUrl from '../../utils/getRequestUrl';
@ -73,13 +73,12 @@ const InputUID = ({
setIsLoading(true);
const requestURL = getRequestUrl('uid/generate');
try {
const { data } = await request(requestURL, {
method: 'POST',
body: {
contentTypeUID,
field: name,
data: modifiedData,
},
const {
data: { data },
} = await axiosInstance.post(requestURL, {
contentTypeUID,
field: name,
data: modifiedData,
});
onChange({ target: { name, value: data, type: 'text' } }, shouldSetInitialValue);
@ -100,14 +99,12 @@ const InputUID = ({
}
try {
const data = await request(requestURL, {
method: 'POST',
body: {
contentTypeUID,
field: name,
value: value ? value.trim() : '',
},
const { data } = await axiosInstance.post(requestURL, {
contentTypeUID,
field: name,
value: value ? value.trim() : '',
});
setAvailability(data);
if (data.suggestion) {
@ -270,20 +267,16 @@ const InputUID = ({
)}
</RightContent>
{availability && availability.suggestion && isSuggestionOpen && (
<FormattedMessage id={`${pluginId}.components.uid.suggested`}>
{msg => (
<Options
title={msg}
options={[
{
id: 'suggestion',
label: availability.suggestion,
onClick: handleSuggestionClick,
},
]}
/>
)}
</FormattedMessage>
<Options
title={formatMessage({ id: `${pluginId}.components.uid.suggested` })}
options={[
{
id: 'suggestion',
label: availability.suggestion,
onClick: handleSuggestionClick,
},
]}
/>
)}
</InputContainer>
{!hasError && description && <SubLabel as={Description}>{description}</SubLabel>}

View File

@ -1,8 +1,9 @@
import React, { useState, useEffect, useCallback, useRef, useLayoutEffect } from 'react';
import { Text, Padded } from '@buffetjs/core';
import { request } from '@strapi/helper-plugin';
import { LoadingIndicator, Tooltip } from '@buffetjs/styles';
import PropTypes from 'prop-types';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import { getDisplayedValue, getRequestUrl } from '../../utils';
const RelationPreviewTooltip = ({
@ -18,17 +19,21 @@ const RelationPreviewTooltip = ({
const tooltipRef = useRef();
const fetchRelationData = useCallback(
async signal => {
async source => {
const requestURL = getRequestUrl(`${endPoint}/${rowId}/${name}`);
try {
const { results } = await request(requestURL, {
method: 'GET',
signal,
});
const {
data: { results },
} = await axiosInstance.get(requestURL, { cancelToken: source.token });
setRelationData(results);
setIsLoading(false);
} catch (err) {
if (axios.isCancel(err)) {
return;
}
console.error({ err });
setIsLoading(false);
}
@ -37,16 +42,17 @@ const RelationPreviewTooltip = ({
);
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const timeout = setTimeout(() => {
fetchRelationData(signal);
fetchRelationData(source);
}, 500);
return () => {
clearTimeout(timeout);
abortController.abort();
source.cancel('Operation canceled by the user.');
};
}, [fetchRelationData]);

View File

@ -7,12 +7,13 @@ import {
DropdownIndicator,
LabelIconWrapper,
NotAllowedInput,
request,
useContentManagerEditViewDataManager,
useQueryParams,
} from '@strapi/helper-plugin';
import { Flex, Text, Padded } from '@buffetjs/core';
import { stringify } from 'qs';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import pluginId from '../../pluginId';
import SelectOne from '../SelectOne';
import SelectMany from '../SelectMany';
@ -118,7 +119,7 @@ function SelectWrapper({
}, [isSingle, value]);
const getData = useCallback(
async signal => {
async source => {
// Currently polymorphic relations are not handled
if (isMorph) {
setIsLoading(false);
@ -141,12 +142,11 @@ function SelectWrapper({
}
try {
const data = await request(endPoint, {
method: 'POST',
params,
signal,
body: { idsToOmit },
});
const { data } = await axiosInstance.post(
endPoint,
{ idsToOmit },
{ params, cancelToken: source.token }
);
const formattedData = data.map(obj => {
return { value: obj, label: obj[mainField.name] };
@ -166,6 +166,7 @@ function SelectWrapper({
setIsLoading(false);
} catch (err) {
// Silent
setIsLoading(false);
}
},
[
@ -182,14 +183,14 @@ function SelectWrapper({
);
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
if (isOpen) {
getData(signal);
getData(source);
}
return () => abortController.abort();
return () => source.cancel('Operation canceled by the user.');
}, [getData, isOpen]);
const handleInputChange = (inputValue, { action }) => {

View File

@ -1,8 +1,7 @@
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { get } from 'lodash';
import get from 'lodash/get';
import {
request,
useTracking,
formatComponentData,
useQueryParams,
@ -10,6 +9,8 @@ import {
} from '@strapi/helper-plugin';
import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import { createDefaultForm, getTrad, removePasswordFieldsFromData } from '../../utils';
import {
getData,
@ -94,25 +95,24 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
// Check if creation mode or editing mode
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const fetchData = async signal => {
const fetchData = async source => {
dispatch(getData());
setIsCreatingEntry(true);
try {
const data = await request(getRequestUrl(`${slug}${searchToSend}`), {
method: 'GET',
signal,
const { data } = await axiosInstance(getRequestUrl(`${slug}${searchToSend}`), {
cancelToken: source.token,
});
dispatch(getDataSucceeded(cleanReceivedData(data)));
setIsCreatingEntry(false);
} catch (err) {
if (err.name === 'AbortError') {
if (axios.isCancel(err)) {
return;
}
@ -134,9 +134,9 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
}
};
fetchData(signal);
fetchData(source);
return () => abortController.abort();
return () => source.cancel('Operation canceled by the user.');
}, [cleanReceivedData, push, slug, dispatch, searchToSend, rawQuery, toggleNotification]);
const displayErrors = useCallback(
@ -163,9 +163,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
try {
trackUsageRef.current('willDeleteEntry', trackerProperty);
const response = await request(getRequestUrl(`${slug}`), {
method: 'DELETE',
});
const { data } = await axiosInstance.delete(getRequestUrl(`${slug}`));
toggleNotification({
type: 'success',
@ -174,7 +172,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
trackUsageRef.current('didDeleteEntry', trackerProperty);
return Promise.resolve(response);
return Promise.resolve(data);
} catch (err) {
trackUsageRef.current('didNotDeleteEntry', { error: err, ...trackerProperty });
@ -197,7 +195,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
try {
dispatch(setStatus('submit-pending'));
const response = await request(endPoint, { method: 'PUT', body });
const { data } = await axiosInstance.put(endPoint, body);
trackUsageRef.current('didCreateEntry', trackerProperty);
toggleNotification({
@ -205,7 +203,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
message: { id: getTrad('success.record.save') },
});
dispatch(submitSucceeded(cleanReceivedData(response)));
dispatch(submitSucceeded(cleanReceivedData(data)));
setIsCreatingEntry(false);
dispatch(setStatus('resolved'));
@ -226,7 +224,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
dispatch(setStatus('publish-pending'));
const data = await request(endPoint, { method: 'POST' });
const { data } = await axiosInstance.post(endPoint);
trackUsageRef.current('didPublishEntry');
toggleNotification({
@ -253,7 +251,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
dispatch(setStatus('submit-pending'));
const response = await request(endPoint, { method: 'PUT', body });
const { data } = await axiosInstance.put(endPoint, body);
toggleNotification({
type: 'success',
@ -262,7 +260,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
trackUsageRef.current('didEditEntry', { trackerProperty });
dispatch(submitSucceeded(cleanReceivedData(response)));
dispatch(submitSucceeded(cleanReceivedData(data)));
dispatch(setStatus('resolved'));
} catch (err) {
@ -285,7 +283,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
try {
trackUsageRef.current('willUnpublishEntry');
const response = await request(endPoint, { method: 'POST' });
const { data } = await axiosInstance.post(endPoint);
trackUsageRef.current('didUnpublishEntry');
toggleNotification({
@ -293,7 +291,7 @@ const SingleTypeFormWrapper = ({ allLayoutData, children, slug }) => {
message: { id: getTrad('success.record.unpublish') },
});
dispatch(submitSucceeded(cleanReceivedData(response)));
dispatch(submitSucceeded(cleanReceivedData(data)));
dispatch(setStatus('resolved'));
} catch (err) {

View File

@ -1,9 +1,11 @@
import { useCallback, useEffect, useMemo, useReducer, useRef } from 'react';
import { useSelector, shallowEqual } from 'react-redux';
import { request } from '@strapi/helper-plugin';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import formatLayouts from './utils/formatLayouts';
import reducer, { initialState } from './reducer';
import { makeSelectModelAndComponentSchemas } from '../../pages/App/selectors';
import { getRequestUrl } from '../../utils';
const useFetchContentTypeLayout = contentTypeUID => {
const [{ error, isLoading, layout, layouts }, dispatch] = useReducer(reducer, initialState);
@ -12,9 +14,7 @@ const useFetchContentTypeLayout = contentTypeUID => {
const isMounted = useRef(true);
const getData = useCallback(
async (uid, abortSignal = false) => {
let signal = abortSignal || new AbortController().signal;
async (uid, source) => {
if (layouts[uid]) {
dispatch({ type: 'SET_LAYOUT_FROM_STATE', uid });
@ -23,21 +23,26 @@ const useFetchContentTypeLayout = contentTypeUID => {
dispatch({ type: 'GET_DATA' });
try {
const { data } = await request(`/content-manager/content-types/${uid}/configuration`, {
method: 'GET',
signal,
});
const endPoint = getRequestUrl(`content-types/${uid}/configuration`);
const {
data: { data },
} = await axiosInstance.get(endPoint, { cancelToken: source.token });
dispatch({
type: 'GET_DATA_SUCCEEDED',
data: formatLayouts(data, schemas),
});
} catch (error) {
if (axios.isCancel(error)) {
return;
}
if (isMounted.current) {
console.error(error);
}
if (isMounted.current && error.name !== 'AbortError') {
if (isMounted.current) {
dispatch({ type: 'GET_DATA_ERROR', error });
}
}
@ -52,13 +57,13 @@ const useFetchContentTypeLayout = contentTypeUID => {
}, []);
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
getData(contentTypeUID, signal);
getData(contentTypeUID, source);
return () => {
abortController.abort();
source.cancel('Operation canceled by the user.');
};
}, [contentTypeUID, getData]);

View File

@ -1,9 +1,11 @@
import { request, useNotification, useRBACProvider, useStrapiApp } from '@strapi/helper-plugin';
import { useNotification, useRBACProvider, useStrapiApp } from '@strapi/helper-plugin';
import { useEffect, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getData, resetProps, setContentTypeLinks } from './actions';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import { MUTATE_COLLECTION_TYPES_LINKS, MUTATE_SINGLE_TYPES_LINKS } from '../../../exposedHooks';
import { getRequestUrl } from '../../utils';
import { getData, resetProps, setContentTypeLinks } from './actions';
import { selectAppDomain } from './selectors';
import getContentTypeLinks from './utils/getContentTypeLinks';
@ -14,14 +16,23 @@ const useModels = () => {
const fetchDataRef = useRef();
const { allPermissions } = useRBACProvider();
const { runHookWaterfall } = useStrapiApp();
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const fetchData = async signal => {
const fetchData = async () => {
dispatch(getData());
try {
const [{ data: components }, { data: models }] = await Promise.all(
const [
{
data: { data: components },
},
{
data: { data: models },
},
] = await Promise.all(
['components', 'content-types'].map(endPoint =>
request(getRequestUrl(endPoint), { method: 'GET', signal })
axiosInstance.get(getRequestUrl(endPoint), { cancelToken: source.token })
)
);
@ -44,21 +55,23 @@ const useModels = () => {
dispatch(actionToDispatch);
} catch (err) {
if (axios.isCancel(err)) {
return;
}
console.error(err);
toggleNotification({ type: 'warning', message: { id: 'notification.error' } });
}
};
fetchDataRef.current = fetchData;
const abortController = new AbortController();
const { signal } = abortController;
useEffect(() => {
fetchDataRef.current(signal);
fetchDataRef.current();
return () => {
abortController.abort();
source.cancel('Operation canceled by the user.');
dispatch(resetProps());
};
// eslint-disable-next-line react-hooks/exhaustive-deps

View File

@ -1,12 +1,13 @@
import { request } from '@strapi/helper-plugin';
import { axiosInstance } from '../../../../core/utils';
import generateModelsLinks from './generateModelsLinks';
import checkPermissions from './checkPermissions';
import { getRequestUrl } from '../../../utils';
const getContentTypeLinks = async (models, userPermissions, toggleNotification) => {
try {
const {
data: contentTypeConfigurations,
} = await request('/content-manager/content-types-settings', { method: 'GET' });
data: { data: contentTypeConfigurations },
} = await axiosInstance.get(getRequestUrl('content-types-settings'));
const { collectionTypesSectionLinks, singleTypesSectionLinks } = generateModelsLinks(
models,

View File

@ -1,6 +1,7 @@
import { request, hasPermissions } from '@strapi/helper-plugin';
import getContentTypeLinks from '../getContentTypeLinks';
// FIXME
jest.mock('@strapi/helper-plugin');
describe('checkPermissions', () => {

View File

@ -1,7 +1,9 @@
import React, { memo, useEffect, useMemo, useReducer } from 'react';
import { useParams } from 'react-router-dom';
import { CheckPagePermissions, LoadingIndicatorPage, request } from '@strapi/helper-plugin';
import { CheckPagePermissions, LoadingIndicatorPage } from '@strapi/helper-plugin';
import { useSelector, shallowEqual } from 'react-redux';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import { getRequestUrl, mergeMetasWithSchema } from '../../utils';
import { makeSelectModelAndComponentSchemas } from '../App/selectors';
import pluginPermissions from '../../permissions';
@ -16,28 +18,33 @@ const ComponentSettingsView = () => {
const { uid } = useParams();
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const fetchData = async signal => {
const fetchData = async source => {
try {
dispatch(getData());
const { data } = await request(getRequestUrl(`components/${uid}/configuration`), {
method: 'GET',
signal,
const {
data: { data },
} = await axiosInstance.get(getRequestUrl(`components/${uid}/configuration`), {
cancelToken: source.token,
});
dispatch(getDataSucceeded(mergeMetasWithSchema(data, schemas, 'component')));
} catch (err) {
if (axios.isCancel(err)) {
return;
}
console.error(err);
}
};
fetchData(signal);
fetchData(source);
return () => {
abortController.abort();
source.cancel('Operation canceled by the user.');
};
}, [uid, schemas]);

View File

@ -3,9 +3,10 @@ import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import { useSelector, shallowEqual } from 'react-redux';
import { cloneDeep, flatMap, get, set, pick } from 'lodash';
import { request, useTracking, useNotification } from '@strapi/helper-plugin';
import { useTracking, useNotification } from '@strapi/helper-plugin';
import { Inputs as Input } from '@buffetjs/custom';
import { FormattedMessage } from 'react-intl';
import { axiosInstance } from '../../../core/utils';
import pluginId from '../../pluginId';
import { getRequestUrl } from '../../utils';
import FieldsReorder from '../../components/FieldsReorder';
@ -123,10 +124,12 @@ const EditSettingsView = ({ components, mainLayout, isContentTypeView, slug, upd
? getRequestUrl(`content-types/${slug}/configuration`)
: getRequestUrl(`components/${slug}/configuration`);
const response = await request(requestURL, { method: 'PUT', body });
const {
data: { data },
} = await axiosInstance.put(requestURL, body);
if (updateLayout) {
updateLayout(response.data);
updateLayout(data);
}
dispatch({

View File

@ -1,11 +1,12 @@
import React, { memo, useContext, useMemo, useReducer, useState } from 'react';
import PropTypes from 'prop-types';
import { get, pick } from 'lodash';
import { request, useNotification, useTracking } from '@strapi/helper-plugin';
import { useNotification, useTracking } from '@strapi/helper-plugin';
import { FormattedMessage, useIntl } from 'react-intl';
import { useDrop } from 'react-dnd';
import { DropdownItem } from 'reactstrap';
import { Inputs as Input } from '@buffetjs/custom';
import { axiosInstance } from '../../../core/utils';
import pluginId from '../../pluginId';
import { checkIfAttributeIsDisplayable, ItemTypes, getRequestUrl } from '../../utils';
import PopupForm from '../../components/PopupForm';
@ -94,12 +95,15 @@ const ListSettingsView = ({ layout, slug, updateLayout }) => {
try {
const body = pick(modifiedData, ['layouts', 'settings', 'metadatas']);
const response = await request(getRequestUrl(`content-types/${slug}/configuration`), {
method: 'PUT',
body,
});
const {
data: { data },
} = await axiosInstance.put(
getRequestUrl(`content-types/${slug}/configuration`),
updateLayout(response.data);
body
);
updateLayout(data);
dispatch({
type: 'SUBMIT_SUCCEEDED',

View File

@ -17,8 +17,9 @@ import {
useQueryParams,
useRBACProvider,
useStrapiApp,
request,
} from '@strapi/helper-plugin';
import axios from 'axios';
import { axiosInstance } from '../../../core/utils';
import { InjectionZone } from '../../../shared/components';
import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
import pluginId from '../../pluginId';
@ -138,15 +139,21 @@ function ListView({
const requestUrlRef = useRef('');
const fetchData = useCallback(
async (endPoint, abortSignal = false) => {
async (endPoint, source) => {
getData();
const signal = abortSignal || new AbortController().signal;
try {
const { results, pagination } = await request(endPoint, { method: 'GET', signal });
const opts = source ? { cancelToken: source.token } : null;
const {
data: { results, pagination },
} = await axiosInstance.get(endPoint, opts);
getDataSucceeded(pagination, results);
} catch (err) {
if (axios.isCancel(err)) {
return;
}
const resStatus = get(err, 'response.status', null);
console.log(err);
@ -163,13 +170,11 @@ function ListView({
return;
}
if (err.name !== 'AbortError') {
console.error(err);
toggleNotification({
type: 'warning',
message: { id: getTrad('error.model.fetch') },
});
}
console.error(err);
toggleNotification({
type: 'warning',
message: { id: getTrad('error.model.fetch') },
});
}
},
[getData, getDataSucceeded, push, toggleNotification]
@ -197,9 +202,8 @@ function ListView({
try {
setModalLoadingState();
await request(getRequestUrl(`collection-types/${slug}/actions/bulkDelete`), {
method: 'POST',
body: { ids: entriesToDelete },
await axiosInstance.post(getRequestUrl(`collection-types/${slug}/actions/bulkDelete`), {
ids: entriesToDelete,
});
onDeleteSeveralDataSucceeded();
@ -233,9 +237,7 @@ function ListView({
trackUsageRef.current('willDeleteEntry', trackerProperty);
setModalLoadingState();
await request(getRequestUrl(`collection-types/${slug}/${idToDelete}`), {
method: 'DELETE',
});
await axiosInstance.delete(getRequestUrl(`collection-types/${slug}/${idToDelete}`));
toggleNotification({
type: 'success',
@ -272,20 +274,22 @@ function ListView({
]);
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const shouldSendRequest = canRead;
const requestUrl = `/${pluginId}/collection-types/${slug}${params}`;
const requestUrl = getRequestUrl(`collection-types/${slug}${params}`);
if (shouldSendRequest && requestUrl.includes(requestUrlRef.current)) {
fetchData(requestUrl, signal);
fetchData(requestUrl, source);
}
return () => {
requestUrlRef.current = slug;
abortController.abort();
source.cancel('Operation canceled by the user.');
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [canRead, getData, slug, params, getDataSucceeded, fetchData]);
const handleClickDelete = id => {
@ -295,7 +299,7 @@ function ListView({
const handleModalClose = useCallback(() => {
if (didDeleteData) {
const requestUrl = `/${pluginId}/collection-types/${slug}${params}`;
const requestUrl = getRequestUrl(`collection-types/${slug}${params}`);
fetchData(requestUrl);
}

View File

@ -1,9 +1,10 @@
import { render } from '@testing-library/react';
import { fixtures } from '../../../../../admin-test-utils';
import { Components, Fields } from '../core/apis';
import StrapiApp from '../StrapiApp';
import appReducers from '../reducers';
const library = { fields: {}, components: {} };
const library = { fields: Fields(), components: Components() };
const middlewares = { middlewares: [] };
const reducers = { reducers: appReducers };
const locales = [];