strapi-plugin-content-manager
Version:
A powerful UI to easily manage your data.
377 lines (303 loc) • 10.6 kB
JavaScript
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import { get } from 'lodash';
import {
request,
useGlobalContext,
useQueryParams,
formatComponentData,
contentManagementUtilRemoveFieldsFromData,
} from 'strapi-helper-plugin';
import { useSelector, useDispatch } from 'react-redux';
import PropTypes from 'prop-types';
import isEqual from 'react-fast-compare';
import { createDefaultForm, getTrad, removePasswordFieldsFromData } from '../../utils';
import pluginId from '../../pluginId';
import { useFindRedirectionLink } from '../../hooks';
import {
getData,
getDataSucceeded,
initForm,
resetProps,
setDataStructures,
setStatus,
submitSucceeded,
} from '../../sharedReducers/crudReducer/actions';
import selectCrudReducer from '../../sharedReducers/crudReducer/selectors';
import { getRequestUrl } from './utils';
// This container is used to handle the CRUD
const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }) => {
const { emitEvent } = useGlobalContext();
const { push, replace } = useHistory();
const [{ rawQuery }] = useQueryParams();
const dispatch = useDispatch();
const {
componentsDataStructure,
contentTypeDataStructure,
data,
isLoading,
status,
} = useSelector(selectCrudReducer);
const redirectionLink = useFindRedirectionLink(slug);
const isMounted = useRef(true);
const emitEventRef = useRef(emitEvent);
const allLayoutDataRef = useRef(allLayoutData);
const isCreatingEntry = id === null;
const requestURL = useMemo(() => {
if (isCreatingEntry && !origin) {
return null;
}
return getRequestUrl(`${slug}/${origin || id}`);
}, [slug, id, isCreatingEntry, origin]);
const cleanClonedData = useCallback(
data => {
if (!origin) {
return data;
}
const cleaned = contentManagementUtilRemoveFieldsFromData(
data,
allLayoutDataRef.current.contentType,
allLayoutDataRef.current.components
);
return cleaned;
},
[origin]
);
const cleanReceivedData = useCallback(data => {
const cleaned = removePasswordFieldsFromData(
data,
allLayoutDataRef.current.contentType,
allLayoutDataRef.current.components
);
return formatComponentData(
cleaned,
allLayoutDataRef.current.contentType,
allLayoutDataRef.current.components
);
}, []);
// SET THE DEFAULT LAYOUT the effect is applied when the slug changes
useEffect(() => {
const componentsDataStructure = Object.keys(allLayoutData.components).reduce((acc, current) => {
const defaultComponentForm = createDefaultForm(
get(allLayoutData, ['components', current, 'attributes'], {}),
allLayoutData.components
);
acc[current] = formatComponentData(
defaultComponentForm,
allLayoutData.components[current],
allLayoutData.components
);
return acc;
}, {});
const contentTypeDataStructure = createDefaultForm(
allLayoutData.contentType.attributes,
allLayoutData.components
);
const contentTypeDataStructureFormatted = formatComponentData(
contentTypeDataStructure,
allLayoutData.contentType,
allLayoutData.components
);
dispatch(setDataStructures(componentsDataStructure, contentTypeDataStructureFormatted));
}, [allLayoutData, dispatch]);
useEffect(() => {
return () => {
dispatch(resetProps());
};
}, [dispatch]);
useEffect(() => {
const abortController = new AbortController();
const { signal } = abortController;
const fetchData = async signal => {
dispatch(getData());
try {
const data = await request(requestURL, { method: 'GET', signal });
dispatch(getDataSucceeded(cleanReceivedData(cleanClonedData(data))));
} catch (err) {
if (err.name === 'AbortError') {
return;
}
console.error(err);
const resStatus = get(err, 'response.status', null);
if (resStatus === 404) {
push(redirectionLink);
return;
}
// Not allowed to read a document
if (resStatus === 403) {
strapi.notification.info(getTrad('permissions.not-allowed.update'));
push(redirectionLink);
}
}
};
// This is needed in order to reset the form when the query changes
const init = async () => {
await dispatch(getData());
await dispatch(initForm(rawQuery));
};
if (!isMounted.current) {
return () => {};
}
if (requestURL) {
fetchData(signal);
} else {
init();
}
return () => {
abortController.abort();
};
}, [cleanClonedData, cleanReceivedData, push, requestURL, dispatch, rawQuery, redirectionLink]);
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 onDelete = useCallback(
async trackerProperty => {
try {
emitEventRef.current('willDeleteEntry', trackerProperty);
const response = await request(getRequestUrl(`${slug}/${id}`), {
method: 'DELETE',
});
strapi.notification.success(getTrad('success.record.delete'));
emitEventRef.current('didDeleteEntry', trackerProperty);
return Promise.resolve(response);
} catch (err) {
emitEventRef.current('didNotDeleteEntry', { error: err, ...trackerProperty });
return Promise.reject(err);
}
},
[id, slug]
);
const onDeleteSucceeded = useCallback(() => {
replace(redirectionLink);
}, [redirectionLink, replace]);
const onPost = useCallback(
async (body, trackerProperty) => {
const endPoint = `${getRequestUrl(slug)}${rawQuery}`;
try {
// Show a loading button in the EditView/Header.js && lock the app => no navigation
dispatch(setStatus('submit-pending'));
const response = await request(endPoint, { method: 'POST', body });
emitEventRef.current('didCreateEntry', trackerProperty);
strapi.notification.toggle({
type: 'success',
message: { id: getTrad('success.record.save') },
});
dispatch(submitSucceeded(cleanReceivedData(response)));
// Enable navigation and remove loaders
dispatch(setStatus('resolved'));
replace(`/plugins/${pluginId}/collectionType/${slug}/${response.id}${rawQuery}`);
} catch (err) {
emitEventRef.current('didNotCreateEntry', { error: err, trackerProperty });
displayErrors(err);
dispatch(setStatus('resolved'));
}
},
[cleanReceivedData, displayErrors, replace, slug, dispatch, rawQuery]
);
const onPublish = useCallback(async () => {
try {
emitEventRef.current('willPublishEntry');
const endPoint = getRequestUrl(`${slug}/${id}/actions/publish`);
dispatch(setStatus('publish-pending'));
const data = await request(endPoint, { method: 'POST' });
emitEventRef.current('didPublishEntry');
dispatch(submitSucceeded(cleanReceivedData(data)));
dispatch(setStatus('resolved'));
strapi.notification.toggle({
type: 'success',
message: { id: getTrad('success.record.publish') },
});
} catch (err) {
displayErrors(err);
dispatch(setStatus('resolved'));
}
}, [cleanReceivedData, displayErrors, id, slug, dispatch]);
const onPut = useCallback(
async (body, trackerProperty) => {
const endPoint = getRequestUrl(`${slug}/${id}`);
try {
emitEventRef.current('willEditEntry', trackerProperty);
dispatch(setStatus('submit-pending'));
const response = await request(endPoint, { method: 'PUT', body });
emitEventRef.current('didEditEntry', { trackerProperty });
strapi.notification.toggle({
type: 'success',
message: { id: getTrad('success.record.save') },
});
dispatch(submitSucceeded(cleanReceivedData(response)));
dispatch(setStatus('resolved'));
} catch (err) {
emitEventRef.current('didNotEditEntry', { error: err, trackerProperty });
displayErrors(err);
dispatch(setStatus('resolved'));
}
},
[cleanReceivedData, displayErrors, slug, id, dispatch]
);
const onUnpublish = useCallback(async () => {
const endPoint = getRequestUrl(`${slug}/${id}/actions/unpublish`);
dispatch(setStatus('unpublish-pending'));
try {
emitEventRef.current('willUnpublishEntry');
const response = await request(endPoint, { method: 'POST' });
emitEventRef.current('didUnpublishEntry');
strapi.notification.success(getTrad('success.record.unpublish'));
dispatch(submitSucceeded(cleanReceivedData(response)));
dispatch(setStatus('resolved'));
} catch (err) {
dispatch(setStatus('resolved'));
displayErrors(err);
}
}, [cleanReceivedData, displayErrors, id, slug, dispatch]);
return children({
componentsDataStructure,
contentTypeDataStructure,
data,
isCreatingEntry,
isLoadingForData: isLoading,
onDelete,
onDeleteSucceeded,
onPost,
onPublish,
onPut,
onUnpublish,
status,
redirectionLink,
});
};
CollectionTypeFormWrapper.defaultProps = {
id: null,
origin: null,
};
CollectionTypeFormWrapper.propTypes = {
allLayoutData: PropTypes.exact({
components: PropTypes.object.isRequired,
contentType: PropTypes.shape({
apiID: PropTypes.string.isRequired,
attributes: PropTypes.object.isRequired,
info: PropTypes.object.isRequired,
isDisplayed: PropTypes.bool.isRequired,
kind: PropTypes.string.isRequired,
layouts: PropTypes.object.isRequired,
metadatas: PropTypes.object.isRequired,
options: PropTypes.object.isRequired,
pluginOptions: PropTypes.object,
settings: PropTypes.object.isRequired,
uid: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
children: PropTypes.func.isRequired,
id: PropTypes.string,
origin: PropTypes.string,
slug: PropTypes.string.isRequired,
};
export default memo(CollectionTypeFormWrapper, isEqual);