394 lines
9.7 KiB
JavaScript
Raw Normal View History

2019-10-30 18:46:19 +01:00
import React, { useEffect, useReducer } from 'react';
2019-11-21 18:48:13 +01:00
import { useParams } from 'react-router-dom';
import PropTypes from 'prop-types';
import { cloneDeep, get, isEmpty, set } from 'lodash';
2019-11-28 15:57:39 +01:00
import {
request,
LoadingIndicatorPage,
useGlobalContext,
} from 'strapi-helper-plugin';
2019-10-30 18:46:19 +01:00
import pluginId from '../../pluginId';
2019-10-30 14:47:12 +01:00
import EditViewDataManagerContext from '../../contexts/EditViewDataManager';
2019-10-30 18:46:19 +01:00
import createYupSchema from './utils/schema';
2019-11-07 14:06:50 +01:00
import createDefaultForm from './utils/createDefaultForm';
import getFilesToUpload from './utils/getFilesToUpload';
import cleanData from './utils/cleanData';
import getYupInnerErrors from './utils/getYupInnerErrors';
import init from './init';
import reducer, { initialState } from './reducer';
2019-10-30 18:46:19 +01:00
const getRequestUrl = path => `/${pluginId}/explorer/${path}`;
2019-10-30 14:47:12 +01:00
2019-10-30 19:06:40 +01:00
const EditViewDataManagerProvider = ({
allLayoutData,
children,
redirectToPreviousPage,
slug,
}) => {
2019-10-30 18:46:19 +01:00
const { id } = useParams();
// Retrieve the search
const [reducerState, dispatch] = useReducer(reducer, initialState, init);
2019-10-30 18:46:19 +01:00
const {
formErrors,
initialData,
isLoading,
modifiedData,
shouldShowLoadingState,
shouldCheckErrors,
2019-10-30 18:46:19 +01:00
} = reducerState.toJS();
const currentContentTypeLayout = get(allLayoutData, ['contentType'], {});
const abortController = new AbortController();
const { signal } = abortController;
const isCreatingEntry = id === 'create';
useEffect(() => {
if (!isLoading) {
checkFormErrors();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [shouldCheckErrors]);
2019-10-30 18:46:19 +01:00
useEffect(() => {
const fetchData = async () => {
try {
const data = await request(getRequestUrl(`${slug}/${id}`), {
method: 'GET',
signal,
});
dispatch({
type: 'GET_DATA_SUCCEEDED',
data,
});
} catch (err) {
if (err.code !== 20) {
strapi.notification.error(`${pluginId}.error.record.fetch`);
}
}
};
2019-11-07 14:06:50 +01:00
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
);
2019-10-30 18:46:19 +01:00
// Force state to be cleared when navigation from one entry to another
dispatch({ type: 'RESET_PROPS' });
2019-11-07 14:06:50 +01:00
dispatch({
type: 'SET_DEFAULT_DATA_STRUCTURES',
componentsDataStructure,
contentTypeDataStructure,
});
2019-10-30 18:46:19 +01:00
if (!isCreatingEntry) {
fetchData();
} else {
// Will create default form
2019-11-07 14:06:50 +01:00
dispatch({
type: 'SET_DEFAULT_MODIFIED_DATA_STRUCTURE',
contentTypeDataStructure,
});
2019-10-30 18:46:19 +01:00
}
return () => {
abortController.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
2019-11-21 18:48:13 +01:00
}, [id, slug]);
const addComponentToDynamicZone = (
keys,
componentUid,
shouldCheckErrors = false
) => {
dispatch({
type: 'ADD_COMPONENT_TO_DYNAMIC_ZONE',
keys: keys.split('.'),
componentUid,
shouldCheckErrors,
});
};
const addNonRepeatableComponentToField = (keys, componentUid) => {
dispatch({
type: 'ADD_NON_REPEATABLE_COMPONENT_TO_FIELD',
keys: keys.split('.'),
componentUid,
});
};
2019-10-30 15:06:38 +01:00
const addRelation = ({ target: { name, value } }) => {
dispatch({
type: 'ADD_RELATION',
keys: name.split('.'),
value,
});
};
const addRepeatableComponentToField = (
keys,
componentUid,
shouldCheckErrors = false
) => {
2019-11-05 11:51:04 +01:00
dispatch({
type: 'ADD_REPEATABLE_COMPONENT_TO_FIELD',
keys: keys.split('.'),
2019-11-07 14:06:50 +01:00
componentUid,
shouldCheckErrors,
});
};
const checkFormErrors = async (dataToSet = {}) => {
const schema = createYupSchema(currentContentTypeLayout, {
components: get(allLayoutData, 'components', {}),
});
let errors = {};
const updatedData = cloneDeep(modifiedData);
if (!isEmpty(updatedData)) {
set(updatedData, dataToSet.path, dataToSet.value);
}
try {
// Validate the form using yup
await schema.validate(updatedData, { abortEarly: false });
} catch (err) {
errors = getYupInnerErrors(err);
}
dispatch({
type: 'SET_ERRORS',
errors,
2019-11-05 11:51:04 +01:00
});
};
2019-10-30 15:06:38 +01:00
const handleChange = ({ target: { name, value, type } }) => {
let inputValue = value;
// Empty string is not a valid date,
// Set the date to null when it's empty
if (type === 'date' && value === '') {
inputValue = null;
}
dispatch({
type: 'ON_CHANGE',
keys: name.split('.'),
value: inputValue,
});
};
2019-11-28 15:57:39 +01:00
const { emitEvent } = useGlobalContext();
2019-10-30 18:46:19 +01:00
const handleSubmit = async e => {
e.preventDefault();
2019-10-30 18:46:19 +01:00
2019-11-08 13:04:17 +01:00
// Create yup schema
2019-10-30 18:46:19 +01:00
const schema = createYupSchema(currentContentTypeLayout, {
components: get(allLayoutData, 'components', {}),
});
2019-10-30 18:46:19 +01:00
try {
// Validate the form using yup
await schema.validate(modifiedData, { abortEarly: false });
// Set the loading state in the plugin header
const filesToUpload = getFilesToUpload(modifiedData);
2019-11-08 13:04:17 +01:00
// Remove keys that are not needed
// Clean relations
const cleanedData = cleanData(
cloneDeep(modifiedData),
currentContentTypeLayout,
allLayoutData.components
);
const formData = new FormData();
formData.append('data', JSON.stringify(cleanedData));
Object.keys(filesToUpload).forEach(key => {
const files = filesToUpload[key];
files.forEach(file => {
formData.append(`files.${key}`, file);
});
});
// Change the request helper default headers so we can pass a FormData
const headers = {};
const method = isCreatingEntry ? 'POST' : 'PUT';
const endPoint = isCreatingEntry ? slug : `${slug}/${id}`;
2019-11-28 15:57:39 +01:00
emitEvent(isCreatingEntry ? 'willCreateEntry' : 'willEditEntry');
try {
// Time to actually send the data
await request(
getRequestUrl(endPoint),
{
method,
headers,
body: formData,
signal,
},
false,
false
);
2019-11-28 15:57:39 +01:00
emitEvent(isCreatingEntry ? 'didCreateEntry' : 'didEditEntry');
redirectToPreviousPage();
} catch (err) {
console.log({ err });
const error = get(
err,
['response', 'payload', 'message', '0', 'messages', '0', 'id'],
'SERVER ERROR'
);
setIsSubmitting(false);
2019-11-28 15:57:39 +01:00
emitEvent(isCreatingEntry ? 'didNotCreateEntry' : 'didNotEditEntry', {
error: err,
});
strapi.notification.error(error);
}
2019-10-30 18:46:19 +01:00
} catch (err) {
const errors = getYupInnerErrors(err);
2019-10-30 18:46:19 +01:00
dispatch({
type: 'SUBMIT_ERRORS',
errors,
});
}
};
2019-11-06 16:29:19 +01:00
const moveComponentDown = (dynamicZoneName, currentIndex) => {
dispatch({
type: 'MOVE_COMPONENT_DOWN',
dynamicZoneName,
currentIndex,
});
};
const moveComponentUp = (dynamicZoneName, currentIndex) => {
dispatch({
type: 'MOVE_COMPONENT_UP',
dynamicZoneName,
currentIndex,
});
};
2019-11-05 17:50:22 +01:00
const moveComponentField = (pathToComponent, dragIndex, hoverIndex) => {
dispatch({
type: 'MOVE_COMPONENT_FIELD',
pathToComponent,
dragIndex,
hoverIndex,
});
};
2019-10-30 15:06:38 +01:00
const moveRelation = (dragIndex, overIndex, name) => {
dispatch({
type: 'MOVE_FIELD',
dragIndex,
overIndex,
keys: name.split('.'),
});
};
const onRemoveRelation = keys => {
dispatch({
type: 'REMOVE_RELATION',
keys,
});
};
2019-11-06 16:29:19 +01:00
const removeComponentFromDynamicZone = (dynamicZoneName, index) => {
dispatch({
type: 'REMOVE_COMPONENT_FROM_DYNAMIC_ZONE',
dynamicZoneName,
index,
});
};
const removeComponentFromField = (keys, componentUid) => {
dispatch({
type: 'REMOVE_COMPONENT_FROM_FIELD',
keys: keys.split('.'),
componentUid,
});
};
2019-11-05 15:36:22 +01:00
const removeRepeatableField = (keys, componentUid) => {
dispatch({
type: 'REMOVE_REPEATABLE_FIELD',
keys: keys.split('.'),
componentUid,
});
};
2019-10-30 19:06:40 +01:00
const setIsSubmitting = (value = true) => {
dispatch({ type: 'IS_SUBMITTING', value });
};
2019-10-30 18:46:19 +01:00
const showLoader = !isCreatingEntry && isLoading;
return (
<EditViewDataManagerContext.Provider
2019-10-30 15:06:38 +01:00
value={{
addComponentToDynamicZone,
addNonRepeatableComponentToField,
2019-10-30 15:06:38 +01:00
addRelation,
2019-11-05 11:51:04 +01:00
addRepeatableComponentToField,
2019-11-04 09:00:59 +01:00
allLayoutData,
checkFormErrors,
2019-10-30 18:46:19 +01:00
formErrors,
2019-10-30 15:06:38 +01:00
initialData,
2019-10-30 18:46:19 +01:00
layout: currentContentTypeLayout,
2019-10-30 15:06:38 +01:00
modifiedData,
2019-11-06 16:29:19 +01:00
moveComponentDown,
2019-11-05 17:50:22 +01:00
moveComponentField,
2019-11-06 16:29:19 +01:00
moveComponentUp,
2019-10-30 15:06:38 +01:00
moveRelation,
onChange: handleChange,
onRemoveRelation,
2019-10-30 19:06:40 +01:00
redirectToPreviousPage,
2019-11-06 16:29:19 +01:00
removeComponentFromDynamicZone,
removeComponentFromField,
2019-11-05 15:36:22 +01:00
removeRepeatableField,
2019-10-30 19:06:40 +01:00
resetData: () => {
dispatch({
type: 'RESET_DATA',
});
},
setIsSubmitting,
2019-10-30 18:46:19 +01:00
shouldShowLoadingState,
2019-10-30 19:06:40 +01:00
slug,
2019-10-30 15:06:38 +01:00
}}
>
2019-10-30 18:46:19 +01:00
{showLoader ? (
<LoadingIndicatorPage />
) : (
<form onSubmit={handleSubmit}>{children}</form>
)}
</EditViewDataManagerContext.Provider>
);
};
2019-10-30 19:06:40 +01:00
EditViewDataManagerProvider.defaultProps = {
redirectToPreviousPage: () => {},
};
EditViewDataManagerProvider.propTypes = {
2019-10-30 18:46:19 +01:00
allLayoutData: PropTypes.object.isRequired,
children: PropTypes.node.isRequired,
2019-10-30 19:06:40 +01:00
redirectToPreviousPage: PropTypes.func,
2019-10-30 18:46:19 +01:00
slug: PropTypes.string.isRequired,
};
export default EditViewDataManagerProvider;