mirror of
https://github.com/strapi/strapi.git
synced 2025-08-06 07:50:02 +00:00
Split fetch data and data management
Signed-off-by: soupette <cyril.lpz@gmail.com>
This commit is contained in:
parent
b51603dca2
commit
d30f965d29
@ -0,0 +1,218 @@
|
|||||||
|
import { memo, useCallback, useEffect, useMemo, useRef, useReducer } from 'react';
|
||||||
|
import { useParams, useLocation, useHistory } from 'react-router-dom';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { request, useGlobalContext } from 'strapi-helper-plugin';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { createDefaultForm, getTrad, removePasswordFieldsFromData } from '../../utils';
|
||||||
|
import pluginId from '../../pluginId';
|
||||||
|
import { getRequestUrl } from './utils';
|
||||||
|
import reducer, { initialState } from './reducer';
|
||||||
|
|
||||||
|
const CollectionTypeWrapper = ({ allLayoutData, children, slug }) => {
|
||||||
|
const { emitEvent } = useGlobalContext();
|
||||||
|
const { push, replace } = useHistory();
|
||||||
|
const { state } = useLocation();
|
||||||
|
const { id } = useParams();
|
||||||
|
const [
|
||||||
|
{ componentsDataStructure, contentTypeDataStructure, data, isLoading, status },
|
||||||
|
dispatch,
|
||||||
|
] = useReducer(reducer, initialState);
|
||||||
|
|
||||||
|
const emitEventRef = useRef(emitEvent);
|
||||||
|
|
||||||
|
// Here in case of a 403 response when fetching data we will either redirect to the previous page
|
||||||
|
// Or to the homepage if there's no state in the history stack
|
||||||
|
const from = get(state, 'from', '/');
|
||||||
|
|
||||||
|
const isCreatingEntry = id === 'create';
|
||||||
|
|
||||||
|
const fetchURL = useMemo(() => {
|
||||||
|
if (isCreatingEntry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRequestUrl(`${slug}/${id}`);
|
||||||
|
}, [slug, id, isCreatingEntry]);
|
||||||
|
|
||||||
|
const cleanReceivedDataFromPasswords = useCallback(
|
||||||
|
data => {
|
||||||
|
return removePasswordFieldsFromData(
|
||||||
|
data,
|
||||||
|
allLayoutData.contentType,
|
||||||
|
allLayoutData.components
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[allLayoutData.components, allLayoutData.contentType]
|
||||||
|
);
|
||||||
|
|
||||||
|
// SET THE DEFAULT LAYOUT the effect is applied when the slug changes
|
||||||
|
useEffect(() => {
|
||||||
|
const componentsDataStructure = Object.keys(allLayoutData.components).reduce((acc, current) => {
|
||||||
|
acc[current] = createDefaultForm(
|
||||||
|
get(allLayoutData, ['components', current, 'schema', 'attributes'], {}),
|
||||||
|
allLayoutData.components
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const contentTypeDataStructure = createDefaultForm(
|
||||||
|
allLayoutData.contentType.schema.attributes,
|
||||||
|
allLayoutData.components
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_DATA_STRUCTURES',
|
||||||
|
componentsDataStructure,
|
||||||
|
contentTypeDataStructure,
|
||||||
|
});
|
||||||
|
}, [allLayoutData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const abortController = new AbortController();
|
||||||
|
const { signal } = abortController;
|
||||||
|
|
||||||
|
const getData = async signal => {
|
||||||
|
dispatch({ type: 'GET_DATA' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await request(fetchURL, { method: 'GET', signal });
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_DATA_SUCCEEDED',
|
||||||
|
data: cleanReceivedDataFromPasswords(data),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
const resStatus = get(err, 'response.status', null);
|
||||||
|
|
||||||
|
if (resStatus === 404) {
|
||||||
|
push(from);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not allowed to read a document
|
||||||
|
if (resStatus === 403) {
|
||||||
|
strapi.notification.info(getTrad('permissions.not-allowed.update'));
|
||||||
|
|
||||||
|
push(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fetchURL) {
|
||||||
|
getData(signal);
|
||||||
|
} else {
|
||||||
|
dispatch({ type: 'INIT_FORM' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
|
}, [fetchURL, push, from, cleanReceivedDataFromPasswords]);
|
||||||
|
|
||||||
|
const displayErrors = useCallback(err => {
|
||||||
|
const errorPayload = err.response.payload;
|
||||||
|
console.error(errorPayload);
|
||||||
|
|
||||||
|
let errorMessage = get(errorPayload, ['message'], 'Bad Request');
|
||||||
|
|
||||||
|
// TODO handle errors correctly when back-end ready
|
||||||
|
if (Array.isArray(errorMessage)) {
|
||||||
|
errorMessage = get(errorMessage, ['0', 'messages', '0', 'id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof errorMessage === 'string') {
|
||||||
|
strapi.notification.error(errorMessage);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onPost = useCallback(
|
||||||
|
async (formData, trackerProperty) => {
|
||||||
|
// const formData = createFormData(data);
|
||||||
|
const endPoint = getRequestUrl(slug);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Show a loading button in the EditView/Header.js && lock the app => no navigation
|
||||||
|
dispatch({ type: 'SET_STATUS', status: 'submit-pending' });
|
||||||
|
|
||||||
|
const response = await request(
|
||||||
|
endPoint,
|
||||||
|
{ method: 'POST', headers: {}, body: formData },
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
emitEventRef.current('didCreateEntry', trackerProperty);
|
||||||
|
strapi.notification.success(getTrad('success.record.save'));
|
||||||
|
|
||||||
|
dispatch({ type: 'SUBMIT_SUCCEEDED', data: response });
|
||||||
|
dispatch({ type: 'SET_STATUS', status: 'submit-pending' });
|
||||||
|
|
||||||
|
replace(`/plugins/${pluginId}/collectionType/${slug}/${response.id}`);
|
||||||
|
} catch (err) {
|
||||||
|
displayErrors(err);
|
||||||
|
emitEventRef.current('didNotCreateEntry', { error: err, trackerProperty });
|
||||||
|
// Enable navigation and remove loaders
|
||||||
|
dispatch({ type: 'SET_STATUS', status: 'resolved' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[displayErrors, replace, slug]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onPut = useCallback(
|
||||||
|
async (formData, trackerProperty) => {
|
||||||
|
const endPoint = getRequestUrl(`${slug}/${id}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Show a loading button in the EditView/Header.js && lock the app => no navigation
|
||||||
|
dispatch({ type: 'SET_STATUS', status: 'submit-pending' });
|
||||||
|
emitEventRef.current('willEditEntry', trackerProperty);
|
||||||
|
|
||||||
|
const response = await request(
|
||||||
|
endPoint,
|
||||||
|
{ method: 'PUT', headers: {}, body: formData },
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
emitEventRef.current('didEditEntry', { trackerProperty });
|
||||||
|
|
||||||
|
dispatch({ type: 'SUBMIT_SUCCEEDED', data: cleanReceivedDataFromPasswords(response) });
|
||||||
|
// Enable navigation and remove loaders
|
||||||
|
dispatch({ type: 'SET_STATUS', status: 'resolved' });
|
||||||
|
} catch (err) {
|
||||||
|
displayErrors(err);
|
||||||
|
|
||||||
|
emitEventRef.current('didNotEditEntry', { error: err, trackerProperty });
|
||||||
|
// Enable navigation and remove loaders
|
||||||
|
dispatch({ type: 'SET_STATUS', status: 'resolved' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[cleanReceivedDataFromPasswords, displayErrors, slug, id]
|
||||||
|
);
|
||||||
|
|
||||||
|
return children({
|
||||||
|
componentsDataStructure,
|
||||||
|
contentTypeDataStructure,
|
||||||
|
data,
|
||||||
|
isCreatingEntry,
|
||||||
|
isLoadingForData: isLoading,
|
||||||
|
onPost,
|
||||||
|
onPut,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CollectionTypeWrapper.propTypes = {
|
||||||
|
allLayoutData: PropTypes.shape({
|
||||||
|
components: PropTypes.object.isRequired,
|
||||||
|
contentType: PropTypes.object.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
// allowedActions: PropTypes.object.isRequired,
|
||||||
|
children: PropTypes.func.isRequired,
|
||||||
|
slug: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CollectionTypeWrapper);
|
@ -0,0 +1,50 @@
|
|||||||
|
/* eslint-disable consistent-return */
|
||||||
|
import produce from 'immer';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
componentsDataStructure: {},
|
||||||
|
contentTypeDataStructure: {},
|
||||||
|
isLoading: true,
|
||||||
|
data: {},
|
||||||
|
status: 'resolved',
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state, action) =>
|
||||||
|
produce(state, draftState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'GET_DATA': {
|
||||||
|
draftState.isLoading = true;
|
||||||
|
draftState.data = {};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'GET_DATA_SUCCEEDED': {
|
||||||
|
draftState.isLoading = false;
|
||||||
|
draftState.data = action.data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'INIT_FORM': {
|
||||||
|
draftState.isLoading = false;
|
||||||
|
draftState.data = state.contentTypeDataStructure;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SET_DATA_STRUCTURES': {
|
||||||
|
draftState.componentsDataStructure = action.componentsDataStructure;
|
||||||
|
draftState.contentTypeDataStructure = action.contentTypeDataStructure;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SET_STATUS': {
|
||||||
|
draftState.status = action.status;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'SUBMIT_SUCCEEDED': {
|
||||||
|
draftState.data = action.data;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return draftState;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reducer;
|
||||||
|
export { initialState };
|
@ -0,0 +1,5 @@
|
|||||||
|
import pluginId from '../../../pluginId';
|
||||||
|
|
||||||
|
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
||||||
|
|
||||||
|
export default getRequestUrl;
|
@ -0,0 +1,2 @@
|
|||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export { default as getRequestUrl } from './getRequestUrl';
|
@ -7,6 +7,7 @@ import {
|
|||||||
LiLink,
|
LiLink,
|
||||||
LoadingIndicatorPage,
|
LoadingIndicatorPage,
|
||||||
CheckPermissions,
|
CheckPermissions,
|
||||||
|
useUser,
|
||||||
useUserPermissions,
|
useUserPermissions,
|
||||||
} from 'strapi-helper-plugin';
|
} from 'strapi-helper-plugin';
|
||||||
import { Padded } from '@buffetjs/core';
|
import { Padded } from '@buffetjs/core';
|
||||||
@ -20,10 +21,11 @@ import SelectWrapper from '../../components/SelectWrapper';
|
|||||||
import { ContentTypeLayoutContext } from '../../contexts';
|
import { ContentTypeLayoutContext } from '../../contexts';
|
||||||
import { useFetchContentTypeLayout } from '../../hooks';
|
import { useFetchContentTypeLayout } from '../../hooks';
|
||||||
import { generatePermissionsObject, getInjectedComponents } from '../../utils';
|
import { generatePermissionsObject, getInjectedComponents } from '../../utils';
|
||||||
|
import CollectionTypeWrapper from '../CollectionTypeWrapper';
|
||||||
import EditViewDataManagerProvider from '../EditViewDataManagerProvider';
|
import EditViewDataManagerProvider from '../EditViewDataManagerProvider';
|
||||||
import EditViewProvider from '../EditViewProvider';
|
import EditViewProvider from '../EditViewProvider';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import { createAttributesLayout } from './utils';
|
import { createAttributesLayout, getFieldsActionMatchingPermissions } from './utils';
|
||||||
import { LinkWrapper, SubWrapper } from './components';
|
import { LinkWrapper, SubWrapper } from './components';
|
||||||
import DeleteLink from './DeleteLink';
|
import DeleteLink from './DeleteLink';
|
||||||
import InformationCard from './InformationCard';
|
import InformationCard from './InformationCard';
|
||||||
@ -38,6 +40,15 @@ const EditView = ({ components, currentEnvironment, models, plugins, slug }) =>
|
|||||||
const { allowedActions, isLoading: isLoadingForPermissions } = useUserPermissions(
|
const { allowedActions, isLoading: isLoadingForPermissions } = useUserPermissions(
|
||||||
viewPermissions
|
viewPermissions
|
||||||
);
|
);
|
||||||
|
const userPermissions = useUser();
|
||||||
|
|
||||||
|
const {
|
||||||
|
createActionAllowedFields,
|
||||||
|
readActionAllowedFields,
|
||||||
|
updateActionAllowedFields,
|
||||||
|
} = useMemo(() => {
|
||||||
|
return getFieldsActionMatchingPermissions(userPermissions, slug);
|
||||||
|
}, [userPermissions, slug]);
|
||||||
|
|
||||||
const currentContentTypeLayoutData = useMemo(() => get(layout, ['contentType'], {}), [layout]);
|
const currentContentTypeLayoutData = useMemo(() => get(layout, ['contentType'], {}), [layout]);
|
||||||
|
|
||||||
@ -77,13 +88,36 @@ const EditView = ({ components, currentEnvironment, models, plugins, slug }) =>
|
|||||||
models={models}
|
models={models}
|
||||||
>
|
>
|
||||||
<ContentTypeLayoutContext.Provider value={layout}>
|
<ContentTypeLayoutContext.Provider value={layout}>
|
||||||
|
<CollectionTypeWrapper allLayoutData={layout} slug={slug}>
|
||||||
|
{({
|
||||||
|
componentsDataStructure,
|
||||||
|
contentTypeDataStructure,
|
||||||
|
data,
|
||||||
|
isCreatingEntry,
|
||||||
|
isLoadingForData,
|
||||||
|
onPost,
|
||||||
|
onPut,
|
||||||
|
status,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
<EditViewDataManagerProvider
|
<EditViewDataManagerProvider
|
||||||
allowedActions={allowedActions}
|
allowedActions={allowedActions}
|
||||||
//
|
|
||||||
allLayoutData={layout}
|
allLayoutData={layout}
|
||||||
redirectToPreviousPage={goBack}
|
createActionAllowedFields={createActionAllowedFields}
|
||||||
|
componentsDataStructure={componentsDataStructure}
|
||||||
|
contentTypeDataStructure={contentTypeDataStructure}
|
||||||
|
initialValues={data}
|
||||||
|
isCreatingEntry={isCreatingEntry}
|
||||||
|
isLoadingForData={isLoadingForData}
|
||||||
isSingleType={false}
|
isSingleType={false}
|
||||||
|
onPost={onPost}
|
||||||
|
onPut={onPut}
|
||||||
|
readActionAllowedFields={readActionAllowedFields}
|
||||||
|
// TODO check if needed
|
||||||
|
redirectToPreviousPage={goBack}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
|
status={status}
|
||||||
|
updateActionAllowedFields={updateActionAllowedFields}
|
||||||
>
|
>
|
||||||
<BackHeader onClick={goBack} />
|
<BackHeader onClick={goBack} />
|
||||||
<Container className="container-fluid">
|
<Container className="container-fluid">
|
||||||
@ -118,7 +152,12 @@ const EditView = ({ components, currentEnvironment, models, plugins, slug }) =>
|
|||||||
const isComponent = fieldSchema.type === 'component';
|
const isComponent = fieldSchema.type === 'component';
|
||||||
|
|
||||||
if (isComponent) {
|
if (isComponent) {
|
||||||
const { component, max, min, repeatable = false } = fieldSchema;
|
const {
|
||||||
|
component,
|
||||||
|
max,
|
||||||
|
min,
|
||||||
|
repeatable = false,
|
||||||
|
} = fieldSchema;
|
||||||
const componentUid = fieldSchema.component;
|
const componentUid = fieldSchema.component;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -182,7 +221,9 @@ const EditView = ({ components, currentEnvironment, models, plugins, slug }) =>
|
|||||||
)}
|
)}
|
||||||
<LinkWrapper>
|
<LinkWrapper>
|
||||||
<ul>
|
<ul>
|
||||||
<CheckPermissions permissions={pluginPermissions.collectionTypesConfigurations}>
|
<CheckPermissions
|
||||||
|
permissions={pluginPermissions.collectionTypesConfigurations}
|
||||||
|
>
|
||||||
<LiLink
|
<LiLink
|
||||||
message={{
|
message={{
|
||||||
id: 'app.links.configure-view',
|
id: 'app.links.configure-view',
|
||||||
@ -208,6 +249,9 @@ const EditView = ({ components, currentEnvironment, models, plugins, slug }) =>
|
|||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</EditViewDataManagerProvider>
|
</EditViewDataManagerProvider>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</CollectionTypeWrapper>
|
||||||
</ContentTypeLayoutContext.Provider>
|
</ContentTypeLayoutContext.Provider>
|
||||||
</EditViewProvider>
|
</EditViewProvider>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { uniq, flatMap } from 'lodash';
|
||||||
|
import { findMatchingPermissions } from 'strapi-helper-plugin';
|
||||||
|
|
||||||
|
const getFieldsActionMatchingPermissions = (userPermissions, slug) => {
|
||||||
|
const getMatchingPermissions = action => {
|
||||||
|
const matched = findMatchingPermissions(userPermissions, [
|
||||||
|
{
|
||||||
|
action: `plugins::content-manager.explorer.${action}`,
|
||||||
|
subject: slug,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return uniq(flatMap(matched, 'fields'));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createActionAllowedFields: getMatchingPermissions('create'),
|
||||||
|
readActionAllowedFields: getMatchingPermissions('read'),
|
||||||
|
updateActionAllowedFields: getMatchingPermissions('update'),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getFieldsActionMatchingPermissions;
|
@ -1,2 +1,3 @@
|
|||||||
// eslint-disable-next-line import/prefer-default-export
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export { default as createAttributesLayout } from './createAttributesLayout';
|
export { default as createAttributesLayout } from './createAttributesLayout';
|
||||||
|
export { default as getFieldsActionMatchingPermissions } from './getFieldsActionMatchingPermissions';
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import getFieldsActionMatchingPermissions from '../getFieldsActionMatchingPermissions';
|
||||||
|
import { testData } from '../../../../testUtils';
|
||||||
|
|
||||||
|
const { permissions } = testData;
|
||||||
|
|
||||||
|
describe('CONTENT MANAGER | CONTAINERS | EditViewDataManager | utils | getFieldsActionMatchingPermissions', () => {
|
||||||
|
it('should return an object with all the allowed action for the fields', () => {
|
||||||
|
const expected = {
|
||||||
|
createActionAllowedFields: [],
|
||||||
|
readActionAllowedFields: ['name', 'description', 'test'],
|
||||||
|
updateActionAllowedFields: ['name', 'description'],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getFieldsActionMatchingPermissions(permissions, 'application::article.article')).toEqual(
|
||||||
|
expected
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,244 @@
|
|||||||
|
const testData = {
|
||||||
|
contentType: {
|
||||||
|
apiID: 'test',
|
||||||
|
schema: {
|
||||||
|
attributes: {
|
||||||
|
created_at: { type: 'timestamp' },
|
||||||
|
dz: { type: 'dynamiczone', components: ['compos.test-compo', 'compos.sub-compo'] },
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
notrepeatable: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: false,
|
||||||
|
component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
password: { type: 'password' },
|
||||||
|
repeatable: { type: 'component', repeatable: true, component: 'compos.test-compo' },
|
||||||
|
updated_at: { type: 'timestamp' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'compos.sub-compo': {
|
||||||
|
uid: 'compos.sub-compo',
|
||||||
|
category: 'compos',
|
||||||
|
schema: {
|
||||||
|
attributes: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
password: { type: 'password' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'compos.test-compo': {
|
||||||
|
uid: 'compos.test-compo',
|
||||||
|
category: 'compos',
|
||||||
|
schema: {
|
||||||
|
attributes: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
password: { type: 'password' },
|
||||||
|
subcomponotrepeatable: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: false,
|
||||||
|
component: 'compos.sub-compo',
|
||||||
|
},
|
||||||
|
subrepeatable: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: true,
|
||||||
|
component: 'compos.sub-compo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedData: {
|
||||||
|
created_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
dz: [
|
||||||
|
{ __component: 'compos.sub-compo', id: 7, name: 'name', password: 'password' },
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subcomponotrepeatable: { id: 9, name: 'name', password: 'password' },
|
||||||
|
subrepeatable: [{ id: 8, name: 'name', password: 'password' }],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: null,
|
||||||
|
password: null,
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
notrepeatable: {
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subcomponotrepeatable: { id: 4, name: 'name', password: 'password' },
|
||||||
|
subrepeatable: [
|
||||||
|
{ id: 1, name: 'name', password: 'password' },
|
||||||
|
{ id: 2, name: 'name', password: 'password' },
|
||||||
|
{ id: 3, name: 'name', password: 'password' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
password: 'password',
|
||||||
|
repeatable: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subrepeatable: [{ id: 5, name: 'name', password: 'password' }],
|
||||||
|
subcomponotrepeatable: { id: 6, name: 'name', password: 'password' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subrepeatable: [],
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updated_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
},
|
||||||
|
expectedModifiedData: {
|
||||||
|
created_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
dz: [
|
||||||
|
{ __component: 'compos.sub-compo', id: 7, name: 'name' },
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'name',
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'name',
|
||||||
|
subcomponotrepeatable: { id: 9, name: 'name' },
|
||||||
|
subrepeatable: [{ id: 8, name: 'name' }],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: null,
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
notrepeatable: {
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
subcomponotrepeatable: { id: 4, name: 'name' },
|
||||||
|
subrepeatable: [
|
||||||
|
{ id: 1, name: 'name' },
|
||||||
|
{ id: 2, name: 'name' },
|
||||||
|
{ id: 3, name: 'name' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
repeatable: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'name',
|
||||||
|
subrepeatable: [{ id: 5, name: 'name' }],
|
||||||
|
subcomponotrepeatable: { id: 6, name: 'name' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'name',
|
||||||
|
subrepeatable: [],
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updated_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissions = [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'application::article.article',
|
||||||
|
fields: ['name', 'description'],
|
||||||
|
conditions: ['admin::is-creator'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
action: 'plugins::content-manager.explorer.update',
|
||||||
|
subject: 'application::article.article',
|
||||||
|
fields: ['name', 'description'],
|
||||||
|
conditions: ['admin::is-creator'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 22,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'plugins::users-permissions.user',
|
||||||
|
fields: [
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'provider',
|
||||||
|
'password',
|
||||||
|
'resetPasswordToken',
|
||||||
|
'confirmed',
|
||||||
|
'blocked',
|
||||||
|
'role',
|
||||||
|
],
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 24,
|
||||||
|
action: 'plugins::content-manager.explorer.update',
|
||||||
|
subject: 'plugins::users-permissions.user',
|
||||||
|
fields: [
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'provider',
|
||||||
|
'password',
|
||||||
|
'resetPasswordToken',
|
||||||
|
'confirmed',
|
||||||
|
'blocked',
|
||||||
|
'role',
|
||||||
|
],
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 28,
|
||||||
|
action: 'plugins::upload.read',
|
||||||
|
subject: null,
|
||||||
|
fields: null,
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 39,
|
||||||
|
action: 'plugins::users-permissions.roles.update',
|
||||||
|
subject: null,
|
||||||
|
fields: null,
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 63,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'application::article.article',
|
||||||
|
fields: ['name', 'description', 'test'],
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default testData;
|
||||||
|
export { permissions };
|
@ -1,28 +1,19 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef, useReducer, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useReducer, useState } from 'react';
|
||||||
import { cloneDeep, get, isEmpty, isEqual, set } from 'lodash';
|
import { cloneDeep, get, isEmpty, isEqual, set } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Prompt, Redirect, useParams, useLocation, useHistory } from 'react-router-dom';
|
import { Prompt, Redirect, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
LoadingIndicatorPage,
|
LoadingIndicatorPage,
|
||||||
request,
|
request,
|
||||||
useGlobalContext,
|
useGlobalContext,
|
||||||
useUser,
|
|
||||||
OverlayBlocker,
|
OverlayBlocker,
|
||||||
} from 'strapi-helper-plugin';
|
} from 'strapi-helper-plugin';
|
||||||
import EditViewDataManagerContext from '../../contexts/EditViewDataManager';
|
import EditViewDataManagerContext from '../../contexts/EditViewDataManager';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad, removePasswordFieldsFromData } from '../../utils';
|
||||||
import pluginId from '../../pluginId';
|
import pluginId from '../../pluginId';
|
||||||
import init from './init';
|
import init from './init';
|
||||||
import reducer, { initialState } from './reducer';
|
import reducer, { initialState } from './reducer';
|
||||||
import {
|
import { cleanData, createYupSchema, getFilesToUpload, getYupInnerErrors } from './utils';
|
||||||
cleanData,
|
|
||||||
createDefaultForm,
|
|
||||||
createYupSchema,
|
|
||||||
getFieldsActionMatchingPermissions,
|
|
||||||
getFilesToUpload,
|
|
||||||
getYupInnerErrors,
|
|
||||||
removePasswordFieldsFromData,
|
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
|
||||||
|
|
||||||
@ -30,17 +21,26 @@ const EditViewDataManagerProvider = ({
|
|||||||
allLayoutData,
|
allLayoutData,
|
||||||
allowedActions: { canCreate, canRead, canUpdate },
|
allowedActions: { canCreate, canRead, canUpdate },
|
||||||
children,
|
children,
|
||||||
|
componentsDataStructure,
|
||||||
|
contentTypeDataStructure,
|
||||||
|
createActionAllowedFields,
|
||||||
|
initialValues,
|
||||||
|
isCreatingEntry,
|
||||||
|
isLoadingForData,
|
||||||
isSingleType,
|
isSingleType,
|
||||||
|
onPost,
|
||||||
|
onPut,
|
||||||
|
readActionAllowedFields,
|
||||||
|
// Not sure this is needed anymore
|
||||||
redirectToPreviousPage,
|
redirectToPreviousPage,
|
||||||
slug,
|
slug,
|
||||||
|
status,
|
||||||
|
updateActionAllowedFields,
|
||||||
}) => {
|
}) => {
|
||||||
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
|
||||||
|
|
||||||
const { id } = useParams();
|
|
||||||
const isCreatingEntry = id === 'create';
|
|
||||||
|
|
||||||
const { state } = useLocation();
|
const { state } = useLocation();
|
||||||
const { push, replace } = useHistory();
|
|
||||||
// Here in case of a 403 response when fetching data we will either redirect to the previous page
|
// Here in case of a 403 response when fetching data we will either redirect to the previous page
|
||||||
// Or to the homepage if there's no state in the history stack
|
// Or to the homepage if there's no state in the history stack
|
||||||
const from = get(state, 'from', '/');
|
const from = get(state, 'from', '/');
|
||||||
@ -48,16 +48,13 @@ const EditViewDataManagerProvider = ({
|
|||||||
const {
|
const {
|
||||||
formErrors,
|
formErrors,
|
||||||
initialData,
|
initialData,
|
||||||
isLoading,
|
|
||||||
modifiedData,
|
modifiedData,
|
||||||
modifiedDZName,
|
modifiedDZName,
|
||||||
shouldCheckErrors,
|
shouldCheckErrors,
|
||||||
} = reducerState.toJS();
|
} = reducerState.toJS();
|
||||||
|
|
||||||
// This isCreatingEntry logic will be needed, but it needs to be passed from the parent
|
|
||||||
|
|
||||||
// TODO: this should be in the reducer
|
// TODO: this should be in the reducer
|
||||||
const [status, setStatus] = useState('resolved');
|
const [, setStatus] = useState('resolved');
|
||||||
|
|
||||||
const currentContentTypeLayout = get(allLayoutData, ['contentType'], {});
|
const currentContentTypeLayout = get(allLayoutData, ['contentType'], {});
|
||||||
|
|
||||||
@ -71,15 +68,6 @@ const EditViewDataManagerProvider = ({
|
|||||||
|
|
||||||
const { emitEvent, formatMessage } = useGlobalContext();
|
const { emitEvent, formatMessage } = useGlobalContext();
|
||||||
const emitEventRef = useRef(emitEvent);
|
const emitEventRef = useRef(emitEvent);
|
||||||
const userPermissions = useUser();
|
|
||||||
|
|
||||||
const {
|
|
||||||
createActionAllowedFields,
|
|
||||||
readActionAllowedFields,
|
|
||||||
updateActionAllowedFields,
|
|
||||||
} = useMemo(() => {
|
|
||||||
return getFieldsActionMatchingPermissions(userPermissions, slug);
|
|
||||||
}, [userPermissions, slug]);
|
|
||||||
|
|
||||||
const cleanReceivedDataFromPasswords = useCallback(
|
const cleanReceivedDataFromPasswords = useCallback(
|
||||||
data => {
|
data => {
|
||||||
@ -93,7 +81,7 @@ const EditViewDataManagerProvider = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const shouldRedirectToHomepageWhenCreatingEntry = useMemo(() => {
|
const shouldRedirectToHomepageWhenCreatingEntry = useMemo(() => {
|
||||||
if (isLoading) {
|
if (isLoadingForData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,10 +94,10 @@ const EditViewDataManagerProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, [isCreatingEntry, canCreate, isLoading]);
|
}, [isCreatingEntry, canCreate, isLoadingForData]);
|
||||||
|
|
||||||
const shouldRedirectToHomepageWhenEditingEntry = useMemo(() => {
|
const shouldRedirectToHomepageWhenEditingEntry = useMemo(() => {
|
||||||
if (isLoading) {
|
if (isLoadingForData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,11 +110,11 @@ const EditViewDataManagerProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, [isLoading, isCreatingEntry, canRead, canUpdate]);
|
}, [isLoadingForData, isCreatingEntry, canRead, canUpdate]);
|
||||||
|
|
||||||
// TODO check this effect if it is really needed (not prio)
|
// TODO check this effect if it is really needed (not prio)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading) {
|
if (!isLoadingForData) {
|
||||||
checkFormErrors();
|
checkFormErrors();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -144,94 +132,20 @@ const EditViewDataManagerProvider = ({
|
|||||||
}
|
}
|
||||||
}, [shouldRedirectToHomepageWhenCreatingEntry]);
|
}, [shouldRedirectToHomepageWhenCreatingEntry]);
|
||||||
|
|
||||||
// Reset all props when changing from one ct to another
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: 'RESET_PROPS' });
|
|
||||||
}, [slug]);
|
|
||||||
|
|
||||||
// Reset all props when navigating from one entry to another in the same ct
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch({ type: 'RESET_FORM' });
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
// SET THE DEFAULT LAYOUT the effect is applied when the slug changes
|
|
||||||
useEffect(() => {
|
|
||||||
const componentsDataStructure = Object.keys(allLayoutData.components).reduce((acc, current) => {
|
|
||||||
acc[current] = createDefaultForm(
|
|
||||||
get(allLayoutData, ['components', current, 'schema', 'attributes'], {}),
|
|
||||||
allLayoutData.components
|
|
||||||
);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const contentTypeDataStructure = createDefaultForm(
|
|
||||||
currentContentTypeLayout.schema.attributes,
|
|
||||||
allLayoutData.components
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_DEFAULT_DATA_STRUCTURES',
|
type: 'SET_DEFAULT_DATA_STRUCTURES',
|
||||||
componentsDataStructure,
|
componentsDataStructure,
|
||||||
contentTypeDataStructure,
|
contentTypeDataStructure,
|
||||||
});
|
});
|
||||||
}, [allLayoutData, currentContentTypeLayout.schema.attributes]);
|
}, [componentsDataStructure, contentTypeDataStructure]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCreatingEntry) {
|
|
||||||
dispatch({ type: 'INITIALIZE_FORM' });
|
|
||||||
}
|
|
||||||
}, [isCreatingEntry]);
|
|
||||||
|
|
||||||
const fetchURL = useMemo(() => {
|
|
||||||
if (isCreatingEntry) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getRequestUrl(`${slug}/${id}`);
|
|
||||||
}, [slug, id, isCreatingEntry]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const abortController = new AbortController();
|
|
||||||
const { signal } = abortController;
|
|
||||||
|
|
||||||
const getData = async signal => {
|
|
||||||
dispatch({ type: 'GET_DATA' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await request(fetchURL, { method: 'GET', signal });
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'GET_DATA_SUCCEEDED',
|
type: 'INIT_FORM',
|
||||||
data: cleanReceivedDataFromPasswords(data),
|
initialValues,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
}, [initialValues]);
|
||||||
console.error(err);
|
|
||||||
const resStatus = get(err, 'response.status', null);
|
|
||||||
|
|
||||||
if (resStatus === 404) {
|
|
||||||
push(from);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not allowed to read a document
|
|
||||||
if (resStatus === 403) {
|
|
||||||
strapi.notification.info(getTrad('permissions.not-allowed.update'));
|
|
||||||
|
|
||||||
push(from);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (fetchURL) {
|
|
||||||
getData(signal);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
abortController.abort();
|
|
||||||
};
|
|
||||||
}, [fetchURL, push, from, cleanReceivedDataFromPasswords]);
|
|
||||||
|
|
||||||
const addComponentToDynamicZone = useCallback((keys, componentUid, shouldCheckErrors = false) => {
|
const addComponentToDynamicZone = useCallback((keys, componentUid, shouldCheckErrors = false) => {
|
||||||
emitEventRef.current('didAddComponentToDynamicZone');
|
emitEventRef.current('didAddComponentToDynamicZone');
|
||||||
@ -367,7 +281,7 @@ const EditViewDataManagerProvider = ({
|
|||||||
const createFormData = useCallback(
|
const createFormData = useCallback(
|
||||||
data => {
|
data => {
|
||||||
// Set the loading state in the plugin header
|
// Set the loading state in the plugin header
|
||||||
const filesToUpload = getFilesToUpload(modifiedData);
|
const filesToUpload = getFilesToUpload(data);
|
||||||
// Remove keys that are not needed
|
// Remove keys that are not needed
|
||||||
// Clean relations
|
// Clean relations
|
||||||
const cleanedData = cleanData(data, currentContentTypeLayout, allLayoutData.components);
|
const cleanedData = cleanData(data, currentContentTypeLayout, allLayoutData.components);
|
||||||
@ -389,7 +303,7 @@ const EditViewDataManagerProvider = ({
|
|||||||
|
|
||||||
return formData;
|
return formData;
|
||||||
},
|
},
|
||||||
[allLayoutData.components, currentContentTypeLayout, modifiedData]
|
[allLayoutData.components, currentContentTypeLayout]
|
||||||
);
|
);
|
||||||
|
|
||||||
const trackerProperty = useMemo(() => {
|
const trackerProperty = useMemo(() => {
|
||||||
@ -416,74 +330,6 @@ const EditViewDataManagerProvider = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onPost = useCallback(
|
|
||||||
async data => {
|
|
||||||
const formData = createFormData(data);
|
|
||||||
const endPoint = getRequestUrl(slug);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Show a loading button in the EditView/Header.js && lock the app => no navigation
|
|
||||||
setStatus('submit-pending');
|
|
||||||
|
|
||||||
const response = await request(
|
|
||||||
endPoint,
|
|
||||||
{ method: 'POST', headers: {}, body: formData },
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
emitEventRef.current('didCreateEntry', trackerProperty);
|
|
||||||
strapi.notification.success(getTrad('success.record.save'));
|
|
||||||
// Enable navigation and remove loaders
|
|
||||||
setStatus('resolved');
|
|
||||||
|
|
||||||
dispatch({ type: 'SUBMIT_SUCCEEDED', data: response });
|
|
||||||
|
|
||||||
replace(`/plugins/${pluginId}/collectionType/${slug}/${response.id}`);
|
|
||||||
} catch (err) {
|
|
||||||
displayErrors(err);
|
|
||||||
emitEventRef.current('didNotCreateEntry', { error: err, trackerProperty });
|
|
||||||
// Enable navigation and remove loaders
|
|
||||||
setStatus('resolved');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[createFormData, displayErrors, replace, slug, trackerProperty]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onPut = useCallback(
|
|
||||||
async data => {
|
|
||||||
const formData = createFormData(data);
|
|
||||||
const endPoint = getRequestUrl(`${slug}/${data.id}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Show a loading button in the EditView/Header.js && lock the app => no navigation
|
|
||||||
setStatus('submit-pending');
|
|
||||||
emitEventRef.current('willEditEntry', trackerProperty);
|
|
||||||
|
|
||||||
const response = await request(
|
|
||||||
endPoint,
|
|
||||||
{ method: 'PUT', headers: {}, body: formData },
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
emitEventRef.current('didEditEntry', { trackerProperty });
|
|
||||||
|
|
||||||
// Enable navigation and remove loaders
|
|
||||||
setStatus('resolved');
|
|
||||||
|
|
||||||
dispatch({ type: 'SUBMIT_SUCCEEDED', data: cleanReceivedDataFromPasswords(response) });
|
|
||||||
} catch (err) {
|
|
||||||
displayErrors(err);
|
|
||||||
|
|
||||||
emitEventRef.current('didNotEditEntry', { error: err, trackerProperty });
|
|
||||||
// Enable navigation and remove loaders
|
|
||||||
setStatus('resolved');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[cleanReceivedDataFromPasswords, createFormData, displayErrors, slug, trackerProperty]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async e => {
|
async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -492,11 +338,16 @@ const EditViewDataManagerProvider = ({
|
|||||||
// First validate the form
|
// First validate the form
|
||||||
try {
|
try {
|
||||||
await yupSchema.validate(modifiedData, { abortEarly: false });
|
await yupSchema.validate(modifiedData, { abortEarly: false });
|
||||||
|
console.log({ modifiedData });
|
||||||
|
|
||||||
|
const formData = createFormData(modifiedData);
|
||||||
|
|
||||||
|
// console.log()
|
||||||
|
|
||||||
if (isCreatingEntry) {
|
if (isCreatingEntry) {
|
||||||
onPost(modifiedData);
|
onPost(formData, trackerProperty);
|
||||||
} else {
|
} else {
|
||||||
onPut(modifiedData);
|
onPut(formData, trackerProperty);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('ValidationError');
|
console.error('ValidationError');
|
||||||
@ -510,7 +361,7 @@ const EditViewDataManagerProvider = ({
|
|||||||
errors,
|
errors,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[isCreatingEntry, modifiedData, onPost, onPut, yupSchema]
|
[createFormData, isCreatingEntry, modifiedData, onPost, onPut, trackerProperty, yupSchema]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePublish = useCallback(async () => {
|
const handlePublish = useCallback(async () => {
|
||||||
@ -802,7 +653,7 @@ const EditViewDataManagerProvider = ({
|
|||||||
isOpen={status !== 'resolved'}
|
isOpen={status !== 'resolved'}
|
||||||
{...overlayBlockerParams}
|
{...overlayBlockerParams}
|
||||||
/>
|
/>
|
||||||
{isLoading ? (
|
{isLoadingForData ? (
|
||||||
<LoadingIndicatorPage />
|
<LoadingIndicatorPage />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -825,10 +676,21 @@ EditViewDataManagerProvider.defaultProps = {
|
|||||||
EditViewDataManagerProvider.propTypes = {
|
EditViewDataManagerProvider.propTypes = {
|
||||||
allLayoutData: PropTypes.object.isRequired,
|
allLayoutData: PropTypes.object.isRequired,
|
||||||
allowedActions: PropTypes.object.isRequired,
|
allowedActions: PropTypes.object.isRequired,
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.arrayOf(PropTypes.element).isRequired,
|
||||||
|
componentsDataStructure: PropTypes.object.isRequired,
|
||||||
|
contentTypeDataStructure: PropTypes.object.isRequired,
|
||||||
|
createActionAllowedFields: PropTypes.array.isRequired,
|
||||||
|
initialValues: PropTypes.object.isRequired,
|
||||||
|
isCreatingEntry: PropTypes.bool.isRequired,
|
||||||
|
isLoadingForData: PropTypes.bool.isRequired,
|
||||||
isSingleType: PropTypes.bool.isRequired,
|
isSingleType: PropTypes.bool.isRequired,
|
||||||
|
onPost: PropTypes.func.isRequired,
|
||||||
|
onPut: PropTypes.func.isRequired,
|
||||||
|
readActionAllowedFields: PropTypes.array.isRequired,
|
||||||
redirectToPreviousPage: PropTypes.func,
|
redirectToPreviousPage: PropTypes.func,
|
||||||
slug: PropTypes.string.isRequired,
|
slug: PropTypes.string.isRequired,
|
||||||
|
status: PropTypes.string.isRequired,
|
||||||
|
updateActionAllowedFields: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EditViewDataManagerProvider;
|
export default EditViewDataManagerProvider;
|
||||||
|
@ -84,6 +84,15 @@ const reducer = (state, action) => {
|
|||||||
.update('initialData', () => fromJS(action.data))
|
.update('initialData', () => fromJS(action.data))
|
||||||
.update('modifiedData', () => fromJS(action.data))
|
.update('modifiedData', () => fromJS(action.data))
|
||||||
.update('isLoading', () => false);
|
.update('isLoading', () => false);
|
||||||
|
|
||||||
|
case 'INIT_FORM': {
|
||||||
|
return state
|
||||||
|
.update('formErrors', () => fromJS({}))
|
||||||
|
.update('initialData', () => fromJS(action.initialValues))
|
||||||
|
.update('modifiedData', () => fromJS(action.initialValues))
|
||||||
|
.update('modifiedDZName', () => null)
|
||||||
|
.update('shouldCheckErrors', () => false);
|
||||||
|
}
|
||||||
case 'INITIALIZE_FORM': {
|
case 'INITIALIZE_FORM': {
|
||||||
return state
|
return state
|
||||||
.update('isLoading', () => false)
|
.update('isLoading', () => false)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export { default as cleanData } from './cleanData';
|
export { default as cleanData } from './cleanData';
|
||||||
export { default as createDefaultForm } from './createDefaultForm';
|
// export { default as createDefaultForm } from './createDefaultForm';
|
||||||
export { default as getFieldsActionMatchingPermissions } from './getFieldsActionMatchingPermissions';
|
// export { default as getFieldsActionMatchingPermissions } from './getFieldsActionMatchingPermissions';
|
||||||
export { default as getFilesToUpload } from './getFilesToUpload';
|
export { default as getFilesToUpload } from './getFilesToUpload';
|
||||||
export { default as getYupInnerErrors } from './getYupInnerErrors';
|
export { default as getYupInnerErrors } from './getYupInnerErrors';
|
||||||
export { default as createYupSchema } from './schema';
|
export { default as createYupSchema } from './schema';
|
||||||
export { default as removePasswordFieldsFromData } from './removePasswordFieldsFromData';
|
// export { default as removePasswordFieldsFromData } from './removePasswordFieldsFromData';
|
||||||
|
@ -0,0 +1,244 @@
|
|||||||
|
const testData = {
|
||||||
|
contentType: {
|
||||||
|
apiID: 'test',
|
||||||
|
schema: {
|
||||||
|
attributes: {
|
||||||
|
created_at: { type: 'timestamp' },
|
||||||
|
dz: { type: 'dynamiczone', components: ['compos.test-compo', 'compos.sub-compo'] },
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
notrepeatable: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: false,
|
||||||
|
component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
password: { type: 'password' },
|
||||||
|
repeatable: { type: 'component', repeatable: true, component: 'compos.test-compo' },
|
||||||
|
updated_at: { type: 'timestamp' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'compos.sub-compo': {
|
||||||
|
uid: 'compos.sub-compo',
|
||||||
|
category: 'compos',
|
||||||
|
schema: {
|
||||||
|
attributes: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
password: { type: 'password' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'compos.test-compo': {
|
||||||
|
uid: 'compos.test-compo',
|
||||||
|
category: 'compos',
|
||||||
|
schema: {
|
||||||
|
attributes: {
|
||||||
|
id: { type: 'integer' },
|
||||||
|
name: { type: 'string' },
|
||||||
|
password: { type: 'password' },
|
||||||
|
subcomponotrepeatable: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: false,
|
||||||
|
component: 'compos.sub-compo',
|
||||||
|
},
|
||||||
|
subrepeatable: {
|
||||||
|
type: 'component',
|
||||||
|
repeatable: true,
|
||||||
|
component: 'compos.sub-compo',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedData: {
|
||||||
|
created_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
dz: [
|
||||||
|
{ __component: 'compos.sub-compo', id: 7, name: 'name', password: 'password' },
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subcomponotrepeatable: { id: 9, name: 'name', password: 'password' },
|
||||||
|
subrepeatable: [{ id: 8, name: 'name', password: 'password' }],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: null,
|
||||||
|
password: null,
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
notrepeatable: {
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subcomponotrepeatable: { id: 4, name: 'name', password: 'password' },
|
||||||
|
subrepeatable: [
|
||||||
|
{ id: 1, name: 'name', password: 'password' },
|
||||||
|
{ id: 2, name: 'name', password: 'password' },
|
||||||
|
{ id: 3, name: 'name', password: 'password' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
password: 'password',
|
||||||
|
repeatable: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subrepeatable: [{ id: 5, name: 'name', password: 'password' }],
|
||||||
|
subcomponotrepeatable: { id: 6, name: 'name', password: 'password' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'name',
|
||||||
|
password: 'password',
|
||||||
|
subrepeatable: [],
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updated_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
},
|
||||||
|
expectedModifiedData: {
|
||||||
|
created_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
dz: [
|
||||||
|
{ __component: 'compos.sub-compo', id: 7, name: 'name' },
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'name',
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'name',
|
||||||
|
subcomponotrepeatable: { id: 9, name: 'name' },
|
||||||
|
subrepeatable: [{ id: 8, name: 'name' }],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: null,
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
subrepeatable: [],
|
||||||
|
__component: 'compos.test-compo',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
notrepeatable: {
|
||||||
|
id: 1,
|
||||||
|
name: 'name',
|
||||||
|
subcomponotrepeatable: { id: 4, name: 'name' },
|
||||||
|
subrepeatable: [
|
||||||
|
{ id: 1, name: 'name' },
|
||||||
|
{ id: 2, name: 'name' },
|
||||||
|
{ id: 3, name: 'name' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
repeatable: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'name',
|
||||||
|
subrepeatable: [{ id: 5, name: 'name' }],
|
||||||
|
subcomponotrepeatable: { id: 6, name: 'name' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'name',
|
||||||
|
subrepeatable: [],
|
||||||
|
subcomponotrepeatable: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updated_at: '2020-04-28T13:22:13.033Z',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissions = [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'application::article.article',
|
||||||
|
fields: ['name', 'description'],
|
||||||
|
conditions: ['admin::is-creator'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 12,
|
||||||
|
action: 'plugins::content-manager.explorer.update',
|
||||||
|
subject: 'application::article.article',
|
||||||
|
fields: ['name', 'description'],
|
||||||
|
conditions: ['admin::is-creator'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 22,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'plugins::users-permissions.user',
|
||||||
|
fields: [
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'provider',
|
||||||
|
'password',
|
||||||
|
'resetPasswordToken',
|
||||||
|
'confirmed',
|
||||||
|
'blocked',
|
||||||
|
'role',
|
||||||
|
],
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 24,
|
||||||
|
action: 'plugins::content-manager.explorer.update',
|
||||||
|
subject: 'plugins::users-permissions.user',
|
||||||
|
fields: [
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'provider',
|
||||||
|
'password',
|
||||||
|
'resetPasswordToken',
|
||||||
|
'confirmed',
|
||||||
|
'blocked',
|
||||||
|
'role',
|
||||||
|
],
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 28,
|
||||||
|
action: 'plugins::upload.read',
|
||||||
|
subject: null,
|
||||||
|
fields: null,
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 39,
|
||||||
|
action: 'plugins::users-permissions.roles.update',
|
||||||
|
subject: null,
|
||||||
|
fields: null,
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: 63,
|
||||||
|
action: 'plugins::content-manager.explorer.read',
|
||||||
|
subject: 'application::article.article',
|
||||||
|
fields: ['name', 'description', 'test'],
|
||||||
|
conditions: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default testData;
|
||||||
|
export { permissions };
|
@ -0,0 +1,2 @@
|
|||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export { default as testData } from './data';
|
@ -0,0 +1,54 @@
|
|||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
const createDefaultForm = (attributes, allComponentsSchema) => {
|
||||||
|
return Object.keys(attributes).reduce((acc, current) => {
|
||||||
|
const attribute = get(attributes, [current], {});
|
||||||
|
const { default: defaultValue, component, type, required, min, repeatable } = attribute;
|
||||||
|
|
||||||
|
if (type === 'json') {
|
||||||
|
acc[current] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'json' && required === true) {
|
||||||
|
acc[current] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultValue !== undefined) {
|
||||||
|
acc[current] = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'component') {
|
||||||
|
const currentComponentSchema = get(
|
||||||
|
allComponentsSchema,
|
||||||
|
[component, 'schema', 'attributes'],
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
const currentComponentDefaultForm = createDefaultForm(
|
||||||
|
currentComponentSchema,
|
||||||
|
allComponentsSchema
|
||||||
|
);
|
||||||
|
|
||||||
|
if (required === true) {
|
||||||
|
acc[current] = repeatable === true ? [] : currentComponentDefaultForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min && repeatable === true && required) {
|
||||||
|
acc[current] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < min; i += 1) {
|
||||||
|
acc[current].push(currentComponentDefaultForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'dynamiczone') {
|
||||||
|
if (required === true) {
|
||||||
|
acc[current] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createDefaultForm;
|
@ -1,4 +1,5 @@
|
|||||||
export { default as checkIfAttributeIsDisplayable } from './checkIfAttributeIsDisplayable';
|
export { default as checkIfAttributeIsDisplayable } from './checkIfAttributeIsDisplayable';
|
||||||
|
export { default as createDefaultForm } from './createDefaultForm';
|
||||||
export { default as dateFormats } from './dateFormats';
|
export { default as dateFormats } from './dateFormats';
|
||||||
export { default as generatePermissionsObject } from './generatePermissionsObject';
|
export { default as generatePermissionsObject } from './generatePermissionsObject';
|
||||||
export { default as getInjectedComponents } from './getComponents';
|
export { default as getInjectedComponents } from './getComponents';
|
||||||
@ -6,3 +7,4 @@ export { default as getFieldName } from './getFieldName';
|
|||||||
export { default as getRequestUrl } from './getRequestUrl';
|
export { default as getRequestUrl } from './getRequestUrl';
|
||||||
export { default as getTrad } from './getTrad';
|
export { default as getTrad } from './getTrad';
|
||||||
export { default as ItemTypes } from './ItemTypes';
|
export { default as ItemTypes } from './ItemTypes';
|
||||||
|
export { default as removePasswordFieldsFromData } from './removePasswordFieldsFromData';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { testData } from '../../testUtils';
|
||||||
import removePasswordFieldsFromData from '../removePasswordFieldsFromData';
|
import removePasswordFieldsFromData from '../removePasswordFieldsFromData';
|
||||||
import testData from './testData';
|
|
||||||
|
|
||||||
describe('CONTENT MANAGER | containers | EditViewDataManager | utils', () => {
|
describe('CONTENT MANAGER | utils', () => {
|
||||||
describe('removePasswordFieldsFromData', () => {
|
describe('removePasswordFieldsFromData', () => {
|
||||||
it('should return an empty object', () => {
|
it('should return an empty object', () => {
|
||||||
const { components, contentType } = testData;
|
const { components, contentType } = testData;
|
Loading…
x
Reference in New Issue
Block a user